Merge "Prepare to implement distracter filter"
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index e3455c1..d2031d1 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -35,6 +35,7 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
@@ -67,8 +68,8 @@
     private int mEdgeSlop;
 
     /** The most recently set keyboard mode. */
-    private int mLastKeyboardMode;
-    private static final int NOT_A_KEYBOARD_MODE = -1;
+    private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
+    private static final int KEYBOARD_IS_HIDDEN = -1;
 
     public static void init(final InputMethodService inputMethod) {
         sInstance.initInternal(inputMethod);
@@ -124,20 +125,27 @@
         if (keyboard == null) {
             return;
         }
-        mKeyboard = keyboard;
         if (mAccessibilityNodeProvider != null) {
             mAccessibilityNodeProvider.setKeyboard(keyboard);
         }
-        final int keyboardMode = keyboard.mId.mMode;
+        final Keyboard lastKeyboard = mKeyboard;
+        final int lastKeyboardMode = mLastKeyboardMode;
+        mKeyboard = keyboard;
+        mLastKeyboardMode = keyboard.mId.mMode;
 
         // Since this method is called even when accessibility is off, make sure
-        // to check the state before announcing anything. Also, don't announce
-        // changes within the same mode.
-        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()
-                && (mLastKeyboardMode != keyboardMode)) {
-            announceKeyboardMode(keyboardMode);
+        // to check the state before announcing anything.
+        if (!AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+            return;
         }
-        mLastKeyboardMode = keyboardMode;
+        // Announce the language name only when the language is changed.
+        if (lastKeyboard == null || !lastKeyboard.mId.mSubtype.equals(keyboard.mId.mSubtype)) {
+            announceKeyboardLanguage(keyboard);
+        }
+        // Announce the mode only when the mode is changed.
+        if (lastKeyboardMode != keyboard.mId.mMode) {
+            announceKeyboardMode(keyboard);
+        }
     }
 
     /**
@@ -148,23 +156,35 @@
             return;
         }
         announceKeyboardHidden();
-        mLastKeyboardMode = NOT_A_KEYBOARD_MODE;
+        mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     }
 
     /**
-     * Announces which type of keyboard is being displayed. If the keyboard type
-     * is unknown, no announcement is made.
+     * Announces which language of keyboard is being displayed.
      *
-     * @param mode The new keyboard mode.
+     * @param keyboard The new keyboard.
      */
-    private void announceKeyboardMode(final int mode) {
-        final int resId = KEYBOARD_MODE_RES_IDS.get(mode);
-        if (resId == 0) {
+    private void announceKeyboardLanguage(final Keyboard keyboard) {
+        final String languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(
+                keyboard.mId.mSubtype);
+        sendWindowStateChanged(languageText);
+    }
+
+    /**
+     * Announces which type of keyboard is being displayed.
+     * If the keyboard type is unknown, no announcement is made.
+     *
+     * @param keyboard The new keyboard.
+     */
+    private void announceKeyboardMode(final Keyboard keyboard) {
+        final int mode = keyboard.mId.mMode;
+        final Context context = mView.getContext();
+        final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(mode);
+        if (modeTextResId == 0) {
             return;
         }
-        final Context context = mView.getContext();
-        final String keyboardMode = context.getString(resId);
-        final String text = context.getString(R.string.announce_keyboard_mode, keyboardMode);
+        final String modeText = context.getString(modeTextResId);
+        final String text = context.getString(R.string.announce_keyboard_mode, modeText);
         sendWindowStateChanged(text);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index be81ed3..5238395 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -69,78 +69,68 @@
         public final Locale mLocale;
         public final ConcurrentHashMap<String, Dictionary> mDictMap =
                 CollectionUtils.newConcurrentHashMap();
-        // Main dictionary will be asynchronously loaded.
-        public Dictionary mMainDictionary;
-        public final ContactsBinaryDictionary mContactsDictionary;
+        public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
+                CollectionUtils.newConcurrentHashMap();
+        // TODO: Remove sub dictionary members and use mSubDictMap.
         public final UserBinaryDictionary mUserDictionary;
-        public final UserHistoryDictionary mUserHistoryDictionary;
-        public final PersonalizationDictionary mPersonalizationDictionary;
 
         public Dictionaries() {
             mLocale = null;
-            mMainDictionary = null;
-            mContactsDictionary = null;
             mUserDictionary = null;
-            mUserHistoryDictionary = null;
-            mPersonalizationDictionary = null;
         }
 
         public Dictionaries(final Locale locale, final Dictionary mainDict,
-            final ContactsBinaryDictionary contactsDict, final UserBinaryDictionary userDict,
-            final UserHistoryDictionary userHistoryDict,
-            final PersonalizationDictionary personalizationDict) {
+            final ExpandableBinaryDictionary contactsDict, final UserBinaryDictionary userDict,
+            final ExpandableBinaryDictionary userHistoryDict,
+            final ExpandableBinaryDictionary personalizationDict) {
             mLocale = locale;
+            // Main dictionary can be asynchronously loaded.
             setMainDict(mainDict);
-            mContactsDictionary = contactsDict;
-            if (mContactsDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_CONTACTS, mContactsDictionary);
-            }
+            setSubDict(Dictionary.TYPE_CONTACTS, contactsDict);
             mUserDictionary = userDict;
-            if (mUserDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_USER, mUserDictionary);
-            }
-            mUserHistoryDictionary = userHistoryDict;
-            if (mUserHistoryDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_USER_HISTORY, mUserHistoryDictionary);
-            }
-            mPersonalizationDictionary = personalizationDict;
-            if (mPersonalizationDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_PERSONALIZATION, mPersonalizationDictionary);
+            setSubDict(Dictionary.TYPE_USER, mUserDictionary);
+            setSubDict(Dictionary.TYPE_USER_HISTORY, userHistoryDict);
+            setSubDict(Dictionary.TYPE_PERSONALIZATION, personalizationDict);
+        }
+
+        private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
+            if (dict != null) {
+                mDictMap.put(dictType, dict);
+                mSubDictMap.put(dictType, dict);
             }
         }
 
         public void setMainDict(final Dictionary mainDict) {
-            mMainDictionary = mainDict;
             // Close old dictionary if exists. Main dictionary can be assigned multiple times.
             final Dictionary oldDict;
-            if (mMainDictionary != null) {
-                oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mMainDictionary);
+            if (mainDict != null) {
+                oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mainDict);
             } else {
                 oldDict = mDictMap.remove(Dictionary.TYPE_MAIN);
             }
-            if (oldDict != null && mMainDictionary != oldDict) {
+            if (oldDict != null && mainDict != oldDict) {
                 oldDict.close();
             }
         }
 
-        public boolean hasMainDict() {
-            return mMainDictionary != null;
+        public Dictionary getMainDict() {
+            return mDictMap.get(Dictionary.TYPE_MAIN);
         }
 
-        public boolean hasContactsDict() {
-            return mContactsDictionary != null;
+        public ExpandableBinaryDictionary getSubDict(final String dictType) {
+            return mSubDictMap.get(dictType);
         }
 
-        public boolean hasUserDict() {
-            return mUserDictionary != null;
+        public boolean hasDict(final String dictType) {
+            return mDictMap.containsKey(dictType);
         }
 
-        public boolean hasUserHistoryDict() {
-            return mUserHistoryDictionary != null;
-        }
-
-        public boolean hasPersonalizationDict() {
-            return mPersonalizationDictionary != null;
+        public void closeDict(final String dictType) {
+            final Dictionary dict = mDictMap.remove(dictType);
+            mSubDictMap.remove(dictType);
+            if (dict != null) {
+                dict.close();
+            }
         }
     }
 
@@ -172,13 +162,13 @@
             // The main dictionary will be asynchronously loaded.
             newMainDict = null;
         } else {
-            newMainDict = mDictionaries.mMainDictionary;
+            newMainDict = mDictionaries.getMainDict();
         }
 
         // Open or move contacts dictionary.
-        final ContactsBinaryDictionary newContactsDict;
-        if (!closeContactsDictionary && mDictionaries.hasContactsDict()) {
-            newContactsDict = mDictionaries.mContactsDictionary;
+        final ExpandableBinaryDictionary newContactsDict;
+        if (!closeContactsDictionary && mDictionaries.hasDict(Dictionary.TYPE_CONTACTS)) {
+            newContactsDict = mDictionaries.getSubDict(Dictionary.TYPE_CONTACTS);
         } else if (useContactsDict) {
             newContactsDict = new ContactsBinaryDictionary(context, newLocale);
         } else {
@@ -187,16 +177,16 @@
 
         // Open or move user dictionary.
         final UserBinaryDictionary newUserDictionary;
-        if (!closeUserDictionary && mDictionaries.hasUserDict()) {
+        if (!closeUserDictionary && mDictionaries.hasDict(Dictionary.TYPE_USER)) {
             newUserDictionary = mDictionaries.mUserDictionary;
         } else {
             newUserDictionary = new UserBinaryDictionary(context, newLocale);
         }
 
         // Open or move user history dictionary.
-        final UserHistoryDictionary newUserHistoryDict;
-        if (!closeUserHistoryDictionary && mDictionaries.hasUserHistoryDict()) {
-            newUserHistoryDict = mDictionaries.mUserHistoryDictionary;
+        final ExpandableBinaryDictionary newUserHistoryDict;
+        if (!closeUserHistoryDictionary && mDictionaries.hasDict(Dictionary.TYPE_USER_HISTORY)) {
+            newUserHistoryDict = mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         } else if (usePersonalizedDicts) {
             newUserHistoryDict = PersonalizationHelper.getUserHistoryDictionary(context, newLocale);
         } else {
@@ -204,9 +194,10 @@
         }
 
         // Open or move personalization dictionary.
-        final PersonalizationDictionary newPersonalizationDict;
-        if (!closePersonalizationDictionary && mDictionaries.hasPersonalizationDict()) {
-            newPersonalizationDict = mDictionaries.mPersonalizationDictionary;
+        final ExpandableBinaryDictionary newPersonalizationDict;
+        if (!closePersonalizationDictionary
+                && mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION)) {
+            newPersonalizationDict = mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
         } else if (usePersonalizedDicts) {
             newPersonalizationDict =
                     PersonalizationHelper.getPersonalizationDictionary(context, newLocale);
@@ -230,22 +221,23 @@
         }
 
         // Clean up old dictionaries.
+        if (reloadMainDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_MAIN);
+        }
+        if (closeContactsDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_CONTACTS);
+        }
+        if (closeUserDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_USER);
+        }
+        if (closeUserHistoryDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_USER_HISTORY);
+        }
+        if (closePersonalizationDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_PERSONALIZATION);
+        }
         oldDictionaries.mDictMap.clear();
-        if (reloadMainDictionary && oldDictionaries.hasMainDict()) {
-            oldDictionaries.mMainDictionary.close();
-        }
-        if (closeContactsDictionary && oldDictionaries.hasContactsDict()) {
-            oldDictionaries.mContactsDictionary.close();
-        }
-        if (closeUserDictionary && oldDictionaries.hasUserDict()) {
-            oldDictionaries.mUserDictionary.close();
-        }
-        if (closeUserHistoryDictionary && oldDictionaries.hasUserHistoryDict()) {
-            oldDictionaries.mUserHistoryDictionary.close();
-        }
-        if (closePersonalizationDictionary && oldDictionaries.hasPersonalizationDict()) {
-            oldDictionaries.mPersonalizationDictionary.close();
-        }
+        oldDictionaries.mSubDictMap.clear();
     }
 
     private void asyncReloadMainDictionary(final Context context, final Locale locale,
@@ -332,39 +324,27 @@
             dictionaries = mDictionaries;
             mDictionaries = new Dictionaries();
         }
-        if (dictionaries.hasMainDict()) {
-            dictionaries.mMainDictionary.close();
-        }
-        if (dictionaries.hasContactsDict()) {
-            dictionaries.mContactsDictionary.close();
-        }
-        if (dictionaries.hasUserDict()) {
-            dictionaries.mUserDictionary.close();
-        }
-        if (dictionaries.hasUserHistoryDict()) {
-            dictionaries.mUserHistoryDictionary.close();
-        }
-        if (dictionaries.hasPersonalizationDict()) {
-            dictionaries.mPersonalizationDictionary.close();
+        for (final Dictionary dict : dictionaries.mDictMap.values()) {
+            dict.close();
         }
     }
 
     // The main dictionary could have been loaded asynchronously.  Don't cache the return value
     // of this method.
     public boolean hasInitializedMainDictionary() {
-        final Dictionaries dictionaries = mDictionaries;
-        return dictionaries.hasMainDict() && dictionaries.mMainDictionary.isInitialized();
+        final Dictionary mainDict = mDictionaries.getMainDict();
+        return mainDict != null && mainDict.isInitialized();
     }
 
     public boolean hasPersonalizationDictionary() {
-        return mDictionaries.hasPersonalizationDict();
+        return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION);
     }
 
     public void flushPersonalizationDictionary() {
-        final PersonalizationDictionary personalizationDict =
-                mDictionaries.mPersonalizationDictionary;
+        final ExpandableBinaryDictionary personalizationDict =
+                mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
         if (personalizationDict != null) {
-            personalizationDict.flush();
+            personalizationDict.asyncFlushBinaryDictionary();
         }
     }
 
@@ -377,18 +357,9 @@
     public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
             throws InterruptedException {
         waitForLoadingMainDictionary(timeout, unit);
-        final Dictionaries dictionaries = mDictionaries;
-        if (dictionaries.hasContactsDict()) {
-            dictionaries.mContactsDictionary.waitAllTasksForTests();
-        }
-        if (dictionaries.hasUserDict()) {
-            dictionaries.mUserDictionary.waitAllTasksForTests();
-        }
-        if (dictionaries.hasUserHistoryDict()) {
-            dictionaries.mUserHistoryDictionary.waitAllTasksForTests();
-        }
-        if (dictionaries.hasPersonalizationDict()) {
-            dictionaries.mPersonalizationDictionary.waitAllTasksForTests();
+        final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaries.mSubDictMap;
+        for (final ExpandableBinaryDictionary dict : dictMap.values()) {
+            dict.waitAllTasksForTests();
         }
     }
 
@@ -411,7 +382,9 @@
     public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
             final String previousWord, final int timeStampInSeconds) {
         final Dictionaries dictionaries = mDictionaries;
-        if (!dictionaries.hasUserHistoryDict()) {
+        final ExpandableBinaryDictionary userHistoryDictionary =
+                dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
+        if (userHistoryDictionary == null) {
             return;
         }
         final int maxFreq = getMaxFrequency(suggestion);
@@ -439,8 +412,8 @@
             // History dictionary in order to avoid suggesting them until the dictionary
             // consolidation is done.
             // TODO: Remove this hack when ready.
-            final int lowerCaseFreqInMainDict = dictionaries.hasMainDict() ?
-                    dictionaries.mMainDictionary.getFrequency(suggestionLowerCase) :
+            final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ?
+                    dictionaries.getMainDict().getFrequency(suggestionLowerCase) :
                             Dictionary.NOT_A_PROBABILITY;
             if (maxFreq < lowerCaseFreqInMainDict
                     && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
@@ -453,14 +426,15 @@
         // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
         // We don't add words with 0-frequency (assuming they would be profanity etc.).
         final boolean isValid = maxFreq > 0;
-        dictionaries.mUserHistoryDictionary.addToDictionary(
-                previousWord, secondWord, isValid, timeStampInSeconds);
+        UserHistoryDictionary.addToDictionary(userHistoryDictionary, previousWord, secondWord,
+                isValid, timeStampInSeconds);
     }
 
     public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
-        final UserHistoryDictionary userHistoryDictionary = mDictionaries.mUserHistoryDictionary;
+        final ExpandableBinaryDictionary userHistoryDictionary =
+                mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDictionary != null) {
-            userHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+            userHistoryDictionary.removeBigramDynamically(previousWord, committedWord);
         }
     }
 
@@ -492,10 +466,10 @@
 
     public boolean isValidMainDictWord(final String word) {
         final Dictionaries dictionaries = mDictionaries;
-        if (TextUtils.isEmpty(word) || !dictionaries.hasMainDict()) {
+        if (TextUtils.isEmpty(word) || !dictionaries.hasDict(Dictionary.TYPE_MAIN)) {
             return false;
         }
-        return dictionaries.mMainDictionary.isValidWord(word);
+        return dictionaries.getMainDict().isValidWord(word);
     }
 
     public boolean isValidWord(final String word, final boolean ignoreCase) {
@@ -537,51 +511,42 @@
     }
 
     public void clearUserHistoryDictionary() {
-        final UserHistoryDictionary userHistoryDict = mDictionaries.mUserHistoryDictionary;
+        final ExpandableBinaryDictionary userHistoryDict =
+                mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDict == null) {
             return;
         }
-        userHistoryDict.clearAndFlushDictionary();
+        userHistoryDict.clear();
     }
 
     // This method gets called only when the IME receives a notification to remove the
     // personalization dictionary.
     public void clearPersonalizationDictionary() {
-        final PersonalizationDictionary personalizationDict =
-                mDictionaries.mPersonalizationDictionary;
+        final ExpandableBinaryDictionary personalizationDict =
+                mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
         if (personalizationDict == null) {
             return;
         }
-        personalizationDict.clearAndFlushDictionary();
+        personalizationDict.clear();
     }
 
     public void addMultipleDictionaryEntriesToPersonalizationDictionary(
             final ArrayList<LanguageModelParam> languageModelParams,
             final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
-        final PersonalizationDictionary personalizationDict =
-                mDictionaries.mPersonalizationDictionary;
-        if (personalizationDict == null) {
+        final ExpandableBinaryDictionary personalizationDict =
+                mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
+        if (personalizationDict == null || languageModelParams == null
+                || languageModelParams.isEmpty()) {
             if (callback != null) {
                 callback.onFinished();
             }
             return;
         }
-        personalizationDict.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback);
+        personalizationDict.addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
     }
 
     public void dumpDictionaryForDebug(final String dictName) {
-        final ExpandableBinaryDictionary dictToDump;
-        if (dictName.equals(Dictionary.TYPE_CONTACTS)) {
-            dictToDump = mDictionaries.mContactsDictionary;
-        } else if (dictName.equals(Dictionary.TYPE_USER)) {
-            dictToDump = mDictionaries.mUserDictionary;
-        } else if (dictName.equals(Dictionary.TYPE_USER_HISTORY)) {
-            dictToDump = mDictionaries.mUserHistoryDictionary;
-        } else if (dictName.equals(Dictionary.TYPE_PERSONALIZATION)) {
-            dictToDump = mDictionaries.mPersonalizationDictionary;
-        } else {
-            dictToDump = null;
-        }
+        final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName);
         if (dictToDump == null) {
             Log.e(TAG, "Cannot dump " + dictName + ". "
                     + "The dictionary is not being used for suggestion or cannot be dumped.");
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index a6a7354..837fe4f 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -27,7 +27,6 @@
 import com.android.inputmethod.latin.makedict.WordProperty;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
-import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CombinedFormatUtils;
 import com.android.inputmethod.latin.utils.ExecutorUtils;
@@ -233,24 +232,24 @@
         mBinaryDictionary = null;
     }
 
-    private void createBinaryDictionaryLocked() {
-        BinaryDictionaryUtils.createEmptyDictFile(mDictFile.getAbsolutePath(),
-                DICTIONARY_FORMAT_VERSION, mLocale, getHeaderAttributeMap());
-    }
-
     private void openBinaryDictionaryLocked() {
         mBinaryDictionary = new BinaryDictionary(
                 mDictFile.getAbsolutePath(), 0 /* offset */, mDictFile.length(),
                 true /* useFullEditDistance */, mLocale, mDictType, true /* isUpdatable */);
     }
 
-    protected void clear() {
+    private void createOnMemoryBinaryDictionaryLocked() {
+        mBinaryDictionary = new BinaryDictionary(
+                mDictFile.getAbsolutePath(), true /* useFullEditDistance */, mLocale, mDictType,
+                DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
+    }
+
+    public void clear() {
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
                 removeBinaryDictionaryLocked();
-                createBinaryDictionaryLocked();
-                openBinaryDictionaryLocked();
+                createOnMemoryBinaryDictionaryLocked();
             }
         });
     }
@@ -298,7 +297,7 @@
     /**
      * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
      */
-    protected void addWordDynamically(final String word, final int frequency,
+    public void addWordDynamically(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
         reloadDictionaryIfRequired();
@@ -325,7 +324,7 @@
     /**
      * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
      */
-    protected void addBigramDynamically(final String word0, final String word1,
+    public void addBigramDynamically(final String word0, final String word1,
             final int frequency, final int timestamp) {
         reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@@ -348,7 +347,7 @@
     /**
      * Dynamically remove a word bigram in the dictionary.
      */
-    protected void removeBigramDynamically(final String word0, final String word1) {
+    public void removeBigramDynamically(final String word0, final String word1) {
         reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
@@ -369,7 +368,7 @@
     /**
      * Dynamically add multiple entries to the dictionary.
      */
-    protected void addMultipleDictionaryEntriesDynamically(
+    public void addMultipleDictionaryEntriesDynamically(
             final ArrayList<LanguageModelParam> languageModelParams,
             final AddMultipleDictionaryEntriesCallback callback) {
         reloadDictionaryIfRequired();
@@ -512,8 +511,7 @@
                     + mDictNameDictionaryUpdateController.mLastUpdateTime);
         }
         removeBinaryDictionaryLocked();
-        createBinaryDictionaryLocked();
-        openBinaryDictionaryLocked();
+        createOnMemoryBinaryDictionaryLocked();
         loadInitialContentsLocked();
         // Run GC and flush to file when initial contents have been loaded.
         mBinaryDictionary.flushWithGCIfHasUpdated();
@@ -634,7 +632,7 @@
     /**
      * Flush binary dictionary to dictionary file.
      */
-    protected void asyncFlushBinaryDictionary() {
+    public void asyncFlushBinaryDictionary() {
         final Runnable newTask = new Runnable() {
             @Override
             public void run() {
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 712e314..352288f 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -65,12 +65,8 @@
             dumpAllWordsForDebug();
         }
         // Flush pending writes.
-        flush();
-        super.close();
-    }
-
-    public void flush() {
         asyncFlushBinaryDictionary();
+        super.close();
     }
 
     @Override
@@ -91,63 +87,16 @@
         return false;
     }
 
-    public void addMultipleDictionaryEntriesToDictionary(
-            final ArrayList<LanguageModelParam> languageModelParams,
-            final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
-        if (languageModelParams == null || languageModelParams.isEmpty()) {
-            if (callback != null) {
-                callback.onFinished();
-            }
-            return;
-        }
-        addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
-    }
-
-    /**
-     * Pair will be added to the decaying dictionary.
-     *
-     * The first word may be null. That means we don't know the context, in other words,
-     * it's only a unigram. The first word may also be an empty string : this means start
-     * context, as in beginning of a sentence for example.
-     * The second word may not be null (a NullPointerException would be thrown).
-     */
-    public void addToDictionary(final String word0, final String word1, final boolean isValid,
-            final int timestamp) {
-        if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
-                (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
-            return;
-        }
-        final int frequency = isValid ?
-                FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
-        addWordDynamically(word1, frequency, null /* shortcutTarget */, 0 /* shortcutFreq */,
-                false /* isNotAWord */, false /* isBlacklisted */, timestamp);
-        // Do not insert a word as a bigram of itself
-        if (word1.equals(word0)) {
-            return;
-        }
-        if (null != word0) {
-            addBigramDynamically(word0, word1, frequency, timestamp);
-        }
-    }
-
     @Override
     protected void loadInitialContentsLocked() {
         // No initial contents.
     }
 
     @UsedForTesting
-    public void clearAndFlushDictionary() {
-        // Clear the node structure on memory
-        clear();
-        // Then flush the cleared state of the dictionary on disk.
-        asyncFlushBinaryDictionary();
-    }
-
-    @UsedForTesting
     public void clearAndFlushDictionaryWithAdditionalAttributes(
             final Map<String, String> attributeMap) {
         mAdditionalAttributeMap = attributeMap;
-        clearAndFlushDictionary();
+        clear();
     }
 
     /* package */ void runGCIfRequired() {
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 385b525..7c43182 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -135,7 +135,7 @@
                 if (entry.getValue() != null) {
                     final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get();
                     if (dict != null) {
-                        dict.clearAndFlushDictionary();
+                        dict.clear();
                     }
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 504e9b2..8a29c35 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -18,7 +18,9 @@
 
 import android.content.Context;
 
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 
 import java.io.File;
 import java.util.Locale;
@@ -40,13 +42,36 @@
                 dictFile);
     }
 
-    public void cancelAddingUserHistory(final String word0, final String word1) {
-        removeBigramDynamically(word0, word1);
-    }
-
     @Override
     public boolean isValidWord(final String word) {
         // Strings out of this dictionary should not be considered existing words.
         return false;
     }
+
+    /**
+     * Pair will be added to the user history dictionary.
+     *
+     * The first word may be null. That means we don't know the context, in other words,
+     * it's only a unigram. The first word may also be an empty string : this means start
+     * context, as in beginning of a sentence for example.
+     * The second word may not be null (a NullPointerException would be thrown).
+     */
+    public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
+            final String word0, final String word1, final boolean isValid, final int timestamp) {
+        if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+                (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+            return;
+        }
+        final int frequency = isValid ?
+                FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
+        userHistoryDictionary.addWordDynamically(word1, frequency, null /* shortcutTarget */,
+                0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, timestamp);
+        // Do not insert a word as a bigram of itself
+        if (word1.equals(word0)) {
+            return;
+        }
+        if (null != word0) {
+            userHistoryDictionary.addBigramDynamically(word0, word1, frequency, timestamp);
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
index b4658b5..5d7deba 100644
--- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -91,6 +91,7 @@
         return false;
     }
 
+    @UsedForTesting
     public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
             final Locale locale, final Map<String, String> attributeMap) {
         final String[] keyArray = new String[attributeMap.size()];
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 60599f6..4399ba0 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -111,7 +111,7 @@
     private static void addToDict(final UserHistoryDictionary dict, final List<String> words) {
         String prevWord = null;
         for (String word : words) {
-            dict.addToDictionary(prevWord, word, true,
+            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true,
                     (int)TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
             prevWord = word;
         }
@@ -147,7 +147,7 @@
         final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary(
                 mContext, locale);
         dict.waitAllTasksForTests();
-        dict.clearAndFlushDictionary();
+        dict.clear();
         dict.close();
         dict.waitAllTasksForTests();
     }
@@ -262,7 +262,7 @@
         dict.waitAllTasksForTests();
         String prevWord = null;
         for (final String word : words) {
-            dict.addToDictionary(prevWord, word, true, mCurrentTime);
+            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime);
             prevWord = word;
             assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word));
         }