Merge "Move binary_format.h to policyimpl."
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index a5205d8..43baf61 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -117,9 +117,6 @@
         PointerTracker.DrawingProxy, MoreKeysPanel.Controller {
     private static final String TAG = MainKeyboardView.class.getSimpleName();
 
-    // TODO: Kill process when the usability study mode was changed.
-    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
-
     /** Listener for {@link KeyboardActionListener}. */
     private KeyboardActionListener mKeyboardActionListener;
 
@@ -1069,8 +1066,8 @@
         final int y = (int)me.getY(index);
 
         // TODO: This might be moved to the tracker.processMotionEvent() call below.
-        if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) {
-            writeUsabilityStudyLog(me, action, eventTime, index, id, x, y);
+        if (LatinImeLogger.sUsabilityStudy) {
+            UsabilityStudyLogUtils.writeMotionEvent(me);
         }
         // TODO: This should be moved to the tracker.processMotionEvent() call below.
         // Currently the same "move" event is being logged twice.
@@ -1131,15 +1128,6 @@
                 final int px = (int)me.getX(i);
                 final int py = (int)me.getY(i);
                 tracker.onMoveEvent(px, py, eventTime, me);
-                if (ENABLE_USABILITY_STUDY_LOG) {
-                    writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py);
-                }
-                // TODO: This seems to be no longer necessary, and confusing because it leads to
-                // duplicate MotionEvents being recorded.
-                // if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                //     ResearchLogger.mainKeyboardView_processMotionEvent(
-                //             me, action, eventTime, i, pointerId, px, py);
-                // }
             }
         } else {
             final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
@@ -1149,35 +1137,6 @@
         return true;
     }
 
-    private static void writeUsabilityStudyLog(final MotionEvent me, final int action,
-            final long eventTime, final int index, final int id, final int x, final int y) {
-        final String eventTag;
-        switch (action) {
-        case MotionEvent.ACTION_UP:
-            eventTag = "[Up]";
-            break;
-        case MotionEvent.ACTION_DOWN:
-            eventTag = "[Down]";
-            break;
-        case MotionEvent.ACTION_POINTER_UP:
-            eventTag = "[PointerUp]";
-            break;
-        case MotionEvent.ACTION_POINTER_DOWN:
-            eventTag = "[PointerDown]";
-            break;
-        case MotionEvent.ACTION_MOVE:
-            eventTag = "[Move]";
-            break;
-        default:
-            eventTag = "[Action" + action + "]";
-            break;
-        }
-        final float size = me.getSize(index);
-        final float pressure = me.getPressure(index);
-        UsabilityStudyLogUtils.getInstance().write(
-                eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure);
-    }
-
     public void cancelAllOngoingEvents() {
         mKeyTimerHandler.cancelAllMessages();
         mDrawingHandler.cancelAllMessages();
@@ -1370,4 +1329,8 @@
             drawIcon(canvas, mSpaceIcon, x, y, iconWidth, iconHeight);
         }
     }
+
+    public void deallocateMemory() {
+        mGestureTrailsPreview.deallocateMemory();
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
index dff5177..d4c2594 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
@@ -104,7 +104,12 @@
         freeOffscreenBuffer();
     }
 
+    public void deallocateMemory() {
+        freeOffscreenBuffer();
+    }
+
     private void freeOffscreenBuffer() {
+        mOffscreenCanvas.setBitmap(null);
         if (mOffscreenBuffer != null) {
             mOffscreenBuffer.recycle();
             mOffscreenBuffer = null;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 1211ea5..614c143 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -76,7 +76,7 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsActivity;
 import com.android.inputmethod.latin.settings.SettingsValues;
@@ -169,7 +169,7 @@
 
     private boolean mIsMainDictionaryAvailable;
     private UserBinaryDictionary mUserDictionary;
-    private UserHistoryDictionary mUserHistoryDictionary;
+    private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -539,35 +539,32 @@
         final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
         final String localeStr = subtypeLocale.toString();
 
-        final ContactsBinaryDictionary oldContactsDictionary;
-        if (mSuggest != null) {
-            oldContactsDictionary = mSuggest.getContactsDictionary();
-            mSuggest.close();
-        } else {
-            oldContactsDictionary = null;
-        }
-        mSuggest = new Suggest(this /* Context */, subtypeLocale,
+        final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
                 this /* SuggestInitializationListener */);
         final SettingsValues settingsValues = mSettings.getCurrent();
         if (settingsValues.mCorrectionEnabled) {
-            mSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
+            newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
         }
 
         mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.getInstance().initSuggest(mSuggest);
+            ResearchLogger.getInstance().initSuggest(newSuggest);
         }
 
         mUserDictionary = new UserBinaryDictionary(this, localeStr);
         mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
-        mSuggest.setUserDictionary(mUserDictionary);
-
-        resetContactsDictionary(oldContactsDictionary);
+        newSuggest.setUserDictionary(mUserDictionary);
 
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-        mUserHistoryDictionary =
-                PersonalizationDictionaryHelper.getUserHistoryDictionary(this, localeStr, prefs);
-        mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
+
+        mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
+                .getUserHistoryPredictionDictionary(this, localeStr, prefs);
+        newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+
+        final Suggest oldSuggest = mSuggest;
+        resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
+        mSuggest = newSuggest;
+        if (oldSuggest != null) oldSuggest.close();
     }
 
     /**
@@ -579,8 +576,9 @@
      * @param oldContactsDictionary an optional dictionary to use, or null
      */
     private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
+        final Suggest suggest = mSuggest;
         final boolean shouldSetDictionary =
-                (null != mSuggest && mSettings.getCurrent().mUseContactsDict);
+                (null != suggest && mSettings.getCurrent().mUseContactsDict);
 
         final ContactsBinaryDictionary dictionaryToUse;
         if (!shouldSetDictionary) {
@@ -607,8 +605,8 @@
             }
         }
 
-        if (null != mSuggest) {
-            mSuggest.setContactsDictionary(dictionaryToUse);
+        if (null != suggest) {
+            suggest.setContactsDictionary(dictionaryToUse);
         }
     }
 
@@ -620,8 +618,9 @@
 
     @Override
     public void onDestroy() {
-        if (mSuggest != null) {
-            mSuggest.close();
+        final Suggest suggest = mSuggest;
+        if (suggest != null) {
+            suggest.close();
             mSuggest = null;
         }
         mSettings.onDestroy();
@@ -795,7 +794,8 @@
 
         // Note: the following does a round-trip IPC on the main thread: be careful
         final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
-        if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) {
+        final Suggest suggest = mSuggest;
+        if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) {
             initSuggest();
         }
         if (mSuggestionStripView != null) {
@@ -813,8 +813,8 @@
             loadSettings();
             currentSettingsValues = mSettings.getCurrent();
 
-            if (mSuggest != null && currentSettingsValues.mCorrectionEnabled) {
-                mSuggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
+            if (suggest != null && currentSettingsValues.mCorrectionEnabled) {
+                suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
             }
 
             switcher.loadKeyboard(editorInfo, currentSettingsValues);
@@ -894,6 +894,7 @@
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
             mainKeyboardView.cancelAllOngoingEvents();
+            mainKeyboardView.deallocateMemory();
         }
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestionStrip();
@@ -2447,12 +2448,13 @@
         // AND it's in none of our current dictionaries (main, user or otherwise).
         // Please note that if mSuggest is null, it means that everything is off: suggestion
         // and correction, so we shouldn't try to show the hint
+        final Suggest suggest = mSuggest;
         final boolean showingAddToDictionaryHint =
                 (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
                         || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
-                        && mSuggest != null
+                        && suggest != null
                         // If the suggestion is not in the dictionary, the hint should be shown.
-                        && !AutoCorrectionUtils.isValidWord(mSuggest, suggestion, true);
+                        && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
 
         if (currentSettings.mIsInternal) {
             LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
@@ -2498,7 +2500,8 @@
 
     private String addToUserHistoryDictionary(final String suggestion) {
         if (TextUtils.isEmpty(suggestion)) return null;
-        if (mSuggest == null) return null;
+        final Suggest suggest = mSuggest;
+        if (suggest == null) return null;
 
         // If correction is not enabled, we don't add words to the user history dictionary.
         // That's to avoid unintended additions in some sensitive fields, or fields that
@@ -2506,12 +2509,10 @@
         final SettingsValues currentSettings = mSettings.getCurrent();
         if (!currentSettings.mCorrectionEnabled) return null;
 
-        final Suggest suggest = mSuggest;
-        final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
-        if (suggest == null || userHistoryDictionary == null) {
-            // Avoid concurrent issue
-            return null;
-        }
+        final UserHistoryPredictionDictionary userHistoryDictionary =
+                mUserHistoryPredictionDictionary;
+        if (userHistoryDictionary == null) return null;
+
         final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
         final String secondWord;
         if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
@@ -2657,7 +2658,7 @@
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
         if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
-            mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+            mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord);
         }
         mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
         if (mSettings.isInternal()) {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 647c6f6..6b01667 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,7 +22,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.BoundedTreeSet;
 import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -168,8 +168,10 @@
         addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
     }
 
-    public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
-        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
+    public void setUserHistoryPredictionDictionary(
+            final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY,
+                userHistoryPredictionDictionary);
     }
 
     public void setAutoCorrectionThreshold(float threshold) {
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
similarity index 83%
rename from java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
rename to java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index c76dea0..9d041f4 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableDictionary;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -47,16 +46,17 @@
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
- * Locally gathers stats about the words user types and various other signals like auto-correction
- * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
+ * This class is a base class of a dictionary for the personalized prediction language model.
  */
-public class UserHistoryDictionary extends ExpandableDictionary {
-    private static final String TAG = UserHistoryDictionary.class.getSimpleName();
-    private static final String NAME = UserHistoryDictionary.class.getSimpleName();
+public abstract class DynamicPredictionDictionaryBase extends ExpandableDictionary {
+    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
+        // TODO: Implement
+    }
+
+    private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName();
     public static final boolean DBG_SAVE_RESTORE = false;
-    public static final boolean DBG_STRESS_TEST = false;
-    public static final boolean DBG_ALWAYS_WRITE = false;
-    public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+    private static final boolean DBG_STRESS_TEST = false;
+    private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
 
     private static final FormatOptions VERSION3 = new FormatOptions(3,
             true /* supportsDynamicUpdate */);
@@ -65,14 +65,7 @@
     private static final int FREQUENCY_FOR_TYPED = 2;
 
     /** Maximum number of pairs. Pruning will start when databases goes above this number. */
-    public static final int MAX_HISTORY_BIGRAMS = 10000;
-
-    /**
-     * When it hits maximum bigram pair, it will delete until you are left with
-     * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
-     * Do not keep this number small to avoid deleting too often.
-     */
-    public static final int DELETE_HISTORY_BIGRAMS = 1000;
+    private static final int MAX_HISTORY_BIGRAMS = 10000;
 
     /** Locale for which this user history dictionary is storing words */
     private final String mLocale;
@@ -85,9 +78,9 @@
     // Should always be false except when we use this class for test
     @UsedForTesting boolean isTest = false;
 
-    /* package */ UserHistoryDictionary(final Context context, final String locale,
-            final SharedPreferences sp) {
-        super(context, Dictionary.TYPE_USER_HISTORY);
+    /* package */ DynamicPredictionDictionaryBase(final Context context, final String locale,
+            final SharedPreferences sp, final String dictionaryType) {
+        super(context, dictionaryType);
         mLocale = locale;
         mPrefs = sp;
         if (mLocale != null && mLocale.length() > 1) {
@@ -102,8 +95,8 @@
         // Also, the database is written to somewhat frequently, so it needs to be kept alive
         // throughout the life of the process.
         // mOpenHelper.close();
-        // Ignore close because we cache UserHistoryDictionary for each language. See getInstance()
-        // above.
+        // Ignore close because we cache PersonalizationPredictionDictionary for each language.
+        // See getInstance() above.
         // super.close();
     }
 
@@ -184,7 +177,7 @@
     }
 
     @Override
-    public void loadDictionaryAsync() {
+    public final void loadDictionaryAsync() {
         // This must be run on non-main thread
         mBigramListLock.lock();
         try {
@@ -194,48 +187,47 @@
         }
     }
 
-    private int profTotal;
-
     private void loadDictionaryAsyncLocked() {
+        final int[] profTotalCount = { 0 };
+        final String locale = getLocale();
         if (DBG_STRESS_TEST) {
             try {
-                Log.w(TAG, "Start stress in loading: " + mLocale);
+                Log.w(TAG, "Start stress in loading: " + locale);
                 Thread.sleep(15000);
                 Log.w(TAG, "End stress in loading");
             } catch (InterruptedException e) {
             }
         }
-        final long last = Settings.readLastUserHistoryWriteTime(mPrefs, mLocale);
+        final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
         final boolean initializing = last == 0;
         final long now = System.currentTimeMillis();
-        profTotal = 0;
-        final String fileName = NAME + "." + mLocale + ".dict";
+        final String fileName = getDictionaryFileName();
         final ExpandableDictionary dictionary = this;
         final OnAddWordListener listener = new OnAddWordListener() {
             @Override
             public void setUnigram(final String word, final String shortcutTarget,
                     final int frequency) {
-                profTotal++;
                 if (DBG_SAVE_RESTORE) {
                     Log.d(TAG, "load unigram: " + word + "," + frequency);
                 }
                 dictionary.addWord(word, shortcutTarget, frequency);
-                mBigramList.addBigram(null, word, (byte)frequency);
+                ++profTotalCount[0];
+                addToBigramListLocked(null, word, (byte)frequency);
             }
 
             @Override
             public void setBigram(final String word1, final String word2, final int frequency) {
                 if (word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
                         && word2.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
-                    profTotal++;
                     if (DBG_SAVE_RESTORE) {
                         Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
                     }
+                    ++profTotalCount[0];
                     dictionary.setBigramAndGetFrequency(
                             word1, word2, initializing ? new ForgettingCurveParams(true)
                             : new ForgettingCurveParams(frequency, now, last));
                 }
-                mBigramList.addBigram(word1, word2, (byte)frequency);
+                addToBigramListLocked(word1, word2, (byte)frequency);
             }
         };
 
@@ -264,11 +256,21 @@
             if (PROFILE_SAVE_RESTORE) {
                 final long diff = System.currentTimeMillis() - now;
                 Log.d(TAG, "PROF: Load UserHistoryDictionary: "
-                        + mLocale + ", " + diff + "ms. load " + profTotal + "entries.");
+                        + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries.");
             }
         }
     }
 
+    protected abstract String getDictionaryFileName();
+
+    protected String getLocale() {
+        return mLocale;
+    }
+
+    private void addToBigramListLocked(String word0, String word1, byte fcValue) {
+        mBigramList.addBigram(word0, word1, fcValue);
+    }
+
     /**
      * Async task to write pending words to the binarydicts.
      */
@@ -277,16 +279,16 @@
         private final UserHistoryDictionaryBigramList mBigramList;
         private final boolean mAddLevel0Bigrams;
         private final String mLocale;
-        private final UserHistoryDictionary mUserHistoryDictionary;
+        private final DynamicPredictionDictionaryBase mDynamicPredictionDictionary;
         private final SharedPreferences mPrefs;
         private final Context mContext;
 
         public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites,
-                final String locale, final UserHistoryDictionary dict,
+                final String locale, final DynamicPredictionDictionaryBase dict,
                 final SharedPreferences prefs, final Context context) {
             mBigramList = pendingWrites;
             mLocale = locale;
-            mUserHistoryDictionary = dict;
+            mDynamicPredictionDictionary = dict;
             mPrefs = prefs;
             mContext = context;
             mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS;
@@ -294,19 +296,19 @@
 
         @Override
         protected Void doInBackground(final Void... v) {
-            if (mUserHistoryDictionary.isTest) {
+            if (mDynamicPredictionDictionary.isTest) {
                 // If isTest == true, wait until the lock is released.
-                mUserHistoryDictionary.mBigramListLock.lock();
+                mDynamicPredictionDictionary.mBigramListLock.lock();
                 try {
                     doWriteTaskLocked();
                 } finally {
-                    mUserHistoryDictionary.mBigramListLock.unlock();
+                    mDynamicPredictionDictionary.mBigramListLock.unlock();
                 }
-            } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
+            } else if (mDynamicPredictionDictionary.mBigramListLock.tryLock()) {
                 try {
                     doWriteTaskLocked();
                 } finally {
-                    mUserHistoryDictionary.mBigramListLock.unlock();
+                    mDynamicPredictionDictionary.mBigramListLock.unlock();
                 }
             }
             return null;
@@ -324,7 +326,8 @@
             }
 
             final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
-            final String fileName = NAME + "." + mLocale + ".dict";
+            final String fileName =
+                    mDynamicPredictionDictionary.getDictionaryFileName();
             final File file = new File(mContext.getFilesDir(), fileName);
             FileOutputStream out = null;
 
@@ -360,7 +363,8 @@
                 freq = FREQUENCY_FOR_TYPED;
                 final byte prevFc = mBigramList.getBigrams(word1).get(word2);
             } else { // bigram
-                final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
+                final NextWord nw =
+                        mDynamicPredictionDictionary.getBigramWord(word1, word2);
                 if (nw != null) {
                     final ForgettingCurveParams fcp = nw.getFcParams();
                     final byte prevFc = mBigramList.getBigrams(word1).get(word2);
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
index e09e834..f5dae99 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -29,15 +29,16 @@
     private static final String TAG = PersonalizationDictionaryHelper.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
+    private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
             sLangDictCache = CollectionUtils.newConcurrentHashMap();
 
-    public static UserHistoryDictionary getUserHistoryDictionary(
+    public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
         synchronized (sLangDictCache) {
             if (sLangDictCache.containsKey(locale)) {
-                final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale);
-                final UserHistoryDictionary dict = ref == null ? null : ref.get();
+                final SoftReference<UserHistoryPredictionDictionary> ref =
+                        sLangDictCache.get(locale);
+                final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
                 if (dict != null) {
                     if (DEBUG) {
                         Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
@@ -45,8 +46,9 @@
                     return dict;
                 }
             }
-            final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale, sp);
-            sLangDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
+            final UserHistoryPredictionDictionary dict =
+                    new UserHistoryPredictionDictionary(context, locale, sp);
+            sLangDictCache.put(locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
             return dict;
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
index 2ec0dc0..c78e5a9 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
@@ -16,6 +16,6 @@
 
 package com.android.inputmethod.latin.personalization;
 
-public class PersonalizationDictionaryUpdateListener {
+public interface PersonalizationDictionaryUpdateListener {
     // TODO: Implement
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
index 7bce979..955bd27 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
@@ -17,30 +17,20 @@
 package com.android.inputmethod.latin.personalization;
 
 import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.ExpandableDictionary;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 
-/**
- * This class is a dictionary for the personalized prediction language model implemented in Java.
- */
-public class PersonalizationPredictionDictionary extends ExpandableDictionary {
-    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
-        // TODO: Implement
-    }
+public class PersonalizationPredictionDictionary extends DynamicPredictionDictionaryBase {
+    private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
 
-    /** Locale for which this user history dictionary is storing words */
-    private final String mLocale;
-    private final SharedPreferences mPrefs;
-
-    // Singleton
-    private PersonalizationPredictionDictionary(final Context context, final String locale,
+    /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
             final SharedPreferences sp) {
-        super(context, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
-        mLocale = locale;
-        mPrefs = sp;
+        super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
     }
 
-    // TODO: Implement
+    @Override
+    protected String getDictionaryFileName() {
+        return NAME + "." + getLocale() + ".dict";
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
index b93630a..f21db25 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
@@ -53,7 +53,7 @@
      * Called when loaded from the SQL DB.
      */
     public void addBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
@@ -73,7 +73,7 @@
      * Called when inserted to the SQL DB.
      */
     public void updateBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
new file mode 100644
index 0000000..d117844
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Locally gathers stats about the words user types and various other signals like auto-correction
+ * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
+ */
+public class UserHistoryPredictionDictionary extends DynamicPredictionDictionaryBase {
+    private static final String NAME = UserHistoryPredictionDictionary.class.getSimpleName();
+    /* package */ UserHistoryPredictionDictionary(final Context context, final String locale,
+            final SharedPreferences sp) {
+        super(context, locale, sp, Dictionary.TYPE_USER_HISTORY);
+    }
+
+    @Override
+    protected String getDictionaryFileName() {
+        return NAME + "." + getLocale() + ".dict";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
index 729a872..06826da 100644
--- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
@@ -25,6 +25,7 @@
 import android.os.HandlerThread;
 import android.os.Process;
 import android.util.Log;
+import android.view.MotionEvent;
 
 import com.android.inputmethod.latin.LatinImeLogger;
 
@@ -109,6 +110,43 @@
         LatinImeLogger.onPrintAllUsabilityStudyLogs();
     }
 
+    public static void writeMotionEvent(final MotionEvent me) {
+        final int action = me.getActionMasked();
+        final long eventTime = me.getEventTime();
+        final int pointerCount = me.getPointerCount();
+        for (int index = 0; index < pointerCount; index++) {
+            final int id = me.getPointerId(index);
+            final int x = (int)me.getX(index);
+            final int y = (int)me.getY(index);
+            final float size = me.getSize(index);
+            final float pressure = me.getPressure(index);
+
+            final String eventTag;
+            switch (action) {
+            case MotionEvent.ACTION_UP:
+                eventTag = "[Up]";
+                break;
+            case MotionEvent.ACTION_DOWN:
+                eventTag = "[Down]";
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                eventTag = "[PointerUp]";
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                eventTag = "[PointerDown]";
+                break;
+            case MotionEvent.ACTION_MOVE:
+                eventTag = "[Move]";
+                break;
+            default:
+                eventTag = "[Action" + action + "]";
+                break;
+            }
+            getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size
+                    + "," + pressure);
+        }
+    }
+
     public void write(final String log) {
         mLoggingHandler.post(new Runnable() {
             @Override
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 7af83d0..8f9ef1d 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -70,7 +70,7 @@
         return new ArrayList<String>(wordSet);
     }
 
-    private void addToDict(final UserHistoryDictionary dict, final List<String> words) {
+    private void addToDict(final UserHistoryPredictionDictionary dict, final List<String> words) {
         String prevWord = null;
         for (String word : words) {
             dict.forceAddWordForTest(prevWord, word, true);
@@ -90,8 +90,8 @@
             final String locale = "testRandomWords";
             final String fileName = "UserHistoryDictionary." + locale + ".dict";
             dictFile = new File(getContext().getFilesDir(), fileName);
-            final UserHistoryDictionary dict =
-                    PersonalizationDictionaryHelper.getUserHistoryDictionary(
+            final UserHistoryPredictionDictionary dict =
+                    PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
                             getContext(), locale, mPrefs);
             dict.isTest = true;
 
@@ -142,8 +142,8 @@
             for (int i = 0; i < numberOfLanguageSwitching; i++) {
                 final int index = i % numberOfLanguages;
                 // Switch languages to locales[index].
-                final UserHistoryDictionary dict =
-                        PersonalizationDictionaryHelper.getUserHistoryDictionary(
+                final UserHistoryPredictionDictionary dict =
+                        PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
                                 getContext(), locales[index], mPrefs);
                 final List<String> words = generateWords(
                         numberOfWordsIntertedForEachLanguageSwitch, random);