Merge "Small performance tweak"
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
deleted file mode 100644
index cbfbd0e..0000000
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.os.SystemClock;
-import android.provider.BaseColumns;
-import android.provider.ContactsContract.Contacts;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.keyboard.Keyboard;
-
-// TODO: This class is superseded by {@link ContactsBinaryDictionary}. Should be cleaned up.
-/**
- * An expandable dictionary that stores the words from Contacts provider.
- *
- * @deprecated Use {@link ContactsBinaryDictionary}.
- */
-@Deprecated
-public class ContactsDictionary extends ExpandableDictionary {
-
-    private static final String[] PROJECTION = {
-        BaseColumns._ID,
-        Contacts.DISPLAY_NAME,
-    };
-
-    private static final String TAG = "ContactsDictionary";
-
-    /**
-     * Frequency for contacts information into the dictionary
-     */
-    private static final int FREQUENCY_FOR_CONTACTS = 40;
-    private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90;
-
-    private static final int INDEX_NAME = 1;
-
-    private ContentObserver mObserver;
-
-    private long mLastLoadedContacts;
-
-    public ContactsDictionary(final Context context, final int dicTypeId) {
-        super(context, dicTypeId);
-        registerObserver(context);
-        loadDictionary();
-    }
-
-    private synchronized void registerObserver(final Context context) {
-        // Perform a managed query. The Activity will handle closing and requerying the cursor
-        // when needed.
-        if (mObserver != null) return;
-        ContentResolver cres = context.getContentResolver();
-        cres.registerContentObserver(
-                Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
-                    @Override
-                    public void onChange(boolean self) {
-                        setRequiresReload(true);
-                    }
-                });
-    }
-
-    public void reopen(final Context context) {
-        registerObserver(context);
-    }
-
-    @Override
-    public synchronized void close() {
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-            mObserver = null;
-        }
-        super.close();
-    }
-
-    @Override
-    public void startDictionaryLoadingTaskLocked() {
-        long now = SystemClock.uptimeMillis();
-        if (mLastLoadedContacts == 0
-                || now - mLastLoadedContacts > 30 * 60 * 1000 /* 30 minutes */) {
-            super.startDictionaryLoadingTaskLocked();
-        }
-    }
-
-    @Override
-    public void loadDictionaryAsync() {
-        try {
-            Cursor cursor = getContext().getContentResolver()
-                    .query(Contacts.CONTENT_URI, PROJECTION, null, null, null);
-            if (cursor != null) {
-                addWords(cursor);
-            }
-        } catch(IllegalStateException e) {
-            Log.e(TAG, "Contacts DB is having problems");
-        }
-        mLastLoadedContacts = SystemClock.uptimeMillis();
-    }
-
-    @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        // Do not return bigrams from Contacts when nothing was typed.
-        if (codes.size() <= 0) return;
-        super.getBigrams(codes, previousWord, callback);
-    }
-
-    private void addWords(Cursor cursor) {
-        clearDictionary();
-
-        final int maxWordLength = getMaxWordLength();
-        try {
-            if (cursor.moveToFirst()) {
-                while (!cursor.isAfterLast()) {
-                    String name = cursor.getString(INDEX_NAME);
-
-                    if (name != null && -1 == name.indexOf('@')) {
-                        int len = name.length();
-                        String prevWord = null;
-
-                        // TODO: Better tokenization for non-Latin writing systems
-                        for (int i = 0; i < len; i++) {
-                            if (Character.isLetter(name.charAt(i))) {
-                                int j;
-                                for (j = i + 1; j < len; j++) {
-                                    char c = name.charAt(j);
-
-                                    if (!(c == Keyboard.CODE_DASH
-                                            || c == Keyboard.CODE_SINGLE_QUOTE
-                                            || Character.isLetter(c))) {
-                                        break;
-                                    }
-                                }
-
-                                String word = name.substring(i, j);
-                                i = j - 1;
-
-                                // Safeguard against adding really long words. Stack
-                                // may overflow due to recursion
-                                // Also don't add single letter words, possibly confuses
-                                // capitalization of i.
-                                final int wordLen = word.length();
-                                if (wordLen < maxWordLength && wordLen > 1) {
-                                    super.addWord(word, null /* shortcut */,
-                                            FREQUENCY_FOR_CONTACTS);
-                                    if (!TextUtils.isEmpty(prevWord)) {
-                                        super.setBigramAndGetFrequency(prevWord, word,
-                                                FREQUENCY_FOR_CONTACTS_BIGRAM);
-                                    }
-                                    prevWord = word;
-                                }
-                            }
-                        }
-                    }
-                    cursor.moveToNext();
-                }
-            }
-            cursor.close();
-        } catch(IllegalStateException e) {
-            Log.e(TAG, "Contacts DB is having problems");
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f5025e5..c3db7a7 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -103,12 +103,6 @@
      */
     private static final String SCHEME_PACKAGE = "package";
 
-    /** Whether to use the binary version of the contacts dictionary */
-    public static final boolean USE_BINARY_CONTACTS_DICTIONARY = true;
-
-    /** Whether to use the binary version of the user dictionary */
-    public static final boolean USE_BINARY_USER_DICTIONARY = true;
-
     // TODO: migrate this to SettingsValues
     private int mSuggestionVisibility;
     private static final int SUGGESTION_VISIBILITY_SHOW_VALUE
@@ -162,8 +156,7 @@
     private boolean mShouldSwitchToLastSubtype = true;
 
     private boolean mIsMainDictionaryAvailable;
-    // TODO: revert this back to the concrete class after transition.
-    private Dictionary mUserDictionary;
+    private UserBinaryDictionary mUserDictionary;
     private UserHistoryDictionary mUserHistoryDictionary;
     private boolean mIsUserDictionaryAvailable;
 
@@ -469,7 +462,7 @@
         final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
         final String localeStr = subtypeLocale.toString();
 
-        final Dictionary oldContactsDictionary;
+        final ContactsBinaryDictionary oldContactsDictionary;
         if (mSuggest != null) {
             oldContactsDictionary = mSuggest.getContactsDictionary();
             mSuggest.close();
@@ -483,13 +476,8 @@
 
         mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
 
-        if (USE_BINARY_USER_DICTIONARY) {
-            mUserDictionary = new UserBinaryDictionary(this, localeStr);
-            mIsUserDictionaryAvailable = ((UserBinaryDictionary)mUserDictionary).isEnabled();
-        } else {
-            mUserDictionary = new UserDictionary(this, localeStr);
-            mIsUserDictionaryAvailable = ((UserDictionary)mUserDictionary).isEnabled();
-        }
+        mUserDictionary = new UserBinaryDictionary(this, localeStr);
+        mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
         mSuggest.setUserDictionary(mUserDictionary);
 
         resetContactsDictionary(oldContactsDictionary);
@@ -510,10 +498,10 @@
      *
      * @param oldContactsDictionary an optional dictionary to use, or null
      */
-    private void resetContactsDictionary(final Dictionary oldContactsDictionary) {
+    private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
         final boolean shouldSetDictionary = (null != mSuggest && mSettingsValues.mUseContactsDict);
 
-        final Dictionary dictionaryToUse;
+        final ContactsBinaryDictionary dictionaryToUse;
         if (!shouldSetDictionary) {
             // Make sure the dictionary is closed. If it is already closed, this is a no-op,
             // so it's safe to call it anyways.
@@ -522,32 +510,20 @@
         } else {
             final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
             if (null != oldContactsDictionary) {
-                if (USE_BINARY_CONTACTS_DICTIONARY) {
-                    ContactsBinaryDictionary oldContactsBinaryDictionary =
-                            (ContactsBinaryDictionary)oldContactsDictionary;
-                    if (!oldContactsBinaryDictionary.mLocale.equals(locale)) {
-                        // If the locale has changed then recreate the contacts dictionary. This
-                        // allows locale dependent rules for handling bigram name predictions.
-                        oldContactsDictionary.close();
-                        dictionaryToUse = new ContactsBinaryDictionary(
-                            this, Suggest.DIC_CONTACTS, locale);
-                    } else {
-                        // Make sure the old contacts dictionary is opened. If it is already open,
-                        // this is a no-op, so it's safe to call it anyways.
-                        oldContactsBinaryDictionary.reopen(this);
-                        dictionaryToUse = oldContactsDictionary;
-                    }
+                if (!oldContactsDictionary.mLocale.equals(locale)) {
+                    // If the locale has changed then recreate the contacts dictionary. This
+                    // allows locale dependent rules for handling bigram name predictions.
+                    oldContactsDictionary.close();
+                    dictionaryToUse = new ContactsBinaryDictionary(
+                        this, Suggest.DIC_CONTACTS, locale);
                 } else {
-                    ((ContactsDictionary)oldContactsDictionary).reopen(this);
+                    // Make sure the old contacts dictionary is opened. If it is already open,
+                    // this is a no-op, so it's safe to call it anyways.
+                    oldContactsDictionary.reopen(this);
                     dictionaryToUse = oldContactsDictionary;
                 }
             } else {
-                if (USE_BINARY_CONTACTS_DICTIONARY) {
-                    dictionaryToUse = new ContactsBinaryDictionary(this, Suggest.DIC_CONTACTS,
-                            locale);
-                } else {
-                    dictionaryToUse = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
-                }
+                dictionaryToUse = new ContactsBinaryDictionary(this, Suggest.DIC_CONTACTS, locale);
             }
         }
 
@@ -1173,11 +1149,7 @@
 
     @Override
     public boolean addWordToDictionary(String word) {
-        if (USE_BINARY_USER_DICTIONARY) {
-            ((UserBinaryDictionary)mUserDictionary).addWordToUserDictionary(word, 128);
-        } else {
-            ((UserDictionary)mUserDictionary).addWordToUserDictionary(word, 128);
-        }
+        mUserDictionary.addWordToUserDictionary(word, 128);
         // Suggestion strip should be updated after the operation of adding word to the
         // user dictionary
         mHandler.postUpdateSuggestions();
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 336a76f..68b7b91 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -66,7 +66,7 @@
     private static final boolean DBG = LatinImeLogger.sDBG;
 
     private boolean mHasMainDictionary;
-    private Dictionary mContactsDict;
+    private ContactsBinaryDictionary mContactsDict;
     private WhitelistDictionary mWhiteListDictionary;
     private final ConcurrentHashMap<String, Dictionary> mUnigramDictionaries =
             new ConcurrentHashMap<String, Dictionary>();
@@ -148,7 +148,7 @@
         return mHasMainDictionary;
     }
 
-    public Dictionary getContactsDictionary() {
+    public ContactsBinaryDictionary getContactsDictionary() {
         return mContactsDict;
     }
 
@@ -164,7 +164,7 @@
      * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
      * before the main dictionary, if set. This refers to the system-managed user dictionary.
      */
-    public void setUserDictionary(Dictionary userDictionary) {
+    public void setUserDictionary(UserBinaryDictionary userDictionary) {
         addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary);
     }
 
@@ -173,13 +173,13 @@
      * the contacts dictionary by passing null to this method. In this case no contacts dictionary
      * won't be used.
      */
-    public void setContactsDictionary(Dictionary contactsDictionary) {
+    public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) {
         mContactsDict = contactsDictionary;
         addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
         addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
     }
 
-    public void setUserHistoryDictionary(Dictionary userHistoryDictionary) {
+    public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) {
         addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_HISTORY_UNIGRAM,
                 userHistoryDictionary);
         addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_HISTORY_BIGRAM,
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsDictionary.java
deleted file mode 100644
index a8b871c..0000000
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsDictionary.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import android.content.Context;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-public class SynchronouslyLoadedContactsDictionary extends ContactsDictionary {
-    private boolean mClosed;
-
-    public SynchronouslyLoadedContactsDictionary(final Context context) {
-        super(context, Suggest.DIC_CONTACTS);
-        mClosed = false;
-    }
-
-    @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
-        blockingReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    @Override
-    public synchronized boolean isValidWord(CharSequence word) {
-        blockingReloadDictionaryIfRequired();
-        return getWordFrequency(word) > -1;
-    }
-
-    // Protect against multiple closing
-    @Override
-    public synchronized void close() {
-        // Actually with the current implementation of ContactsDictionary it's safe to close
-        // several times, so the following protection is really only for foolproofing
-        if (mClosed) return;
-        mClosed = true;
-        super.close();
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java
deleted file mode 100644
index 23a49c1..0000000
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserDictionary.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import android.content.Context;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-public class SynchronouslyLoadedUserDictionary extends UserDictionary {
-    private boolean mClosed;
-
-    public SynchronouslyLoadedUserDictionary(final Context context, final String locale) {
-        this(context, locale, false);
-    }
-
-    public SynchronouslyLoadedUserDictionary(final Context context, final String locale,
-            final boolean alsoUseMoreRestrictiveLocales) {
-        super(context, locale, alsoUseMoreRestrictiveLocales);
-    }
-
-    @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
-        blockingReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    @Override
-    public synchronized boolean isValidWord(CharSequence word) {
-        blockingReloadDictionaryIfRequired();
-        return super.isValidWord(word);
-    }
-
-    // Protect against multiple closing
-    @Override
-    public synchronized void close() {
-        if (mClosed) return;
-        mClosed = true;
-        super.close();
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
deleted file mode 100644
index c1efadd..0000000
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.provider.UserDictionary.Words;
-import android.text.TextUtils;
-
-import com.android.inputmethod.keyboard.ProximityInfo;
-
-import java.util.Arrays;
-
-// TODO: This class is superseded by {@link UserBinaryDictionary}. Should be cleaned up.
-/**
- * An expandable dictionary that stores the words in the user unigram dictionary.
- *
- * @deprecated Use {@link UserBinaryDictionary}.
- */
-@Deprecated
-public class UserDictionary extends ExpandableDictionary {
-
-    // TODO: use Words.SHORTCUT when it's public in the SDK
-    final static String SHORTCUT = "shortcut";
-    private static final String[] PROJECTION_QUERY = {
-        Words.WORD,
-        SHORTCUT,
-        Words.FREQUENCY,
-    };
-
-    // This is not exported by the framework so we pretty much have to write it here verbatim
-    private static final String ACTION_USER_DICTIONARY_INSERT =
-            "com.android.settings.USER_DICTIONARY_INSERT";
-
-    private ContentObserver mObserver;
-    final private String mLocale;
-    final private boolean mAlsoUseMoreRestrictiveLocales;
-
-    public UserDictionary(final Context context, final String locale) {
-        this(context, locale, false);
-    }
-
-    public UserDictionary(final Context context, final String locale,
-            final boolean alsoUseMoreRestrictiveLocales) {
-        super(context, Suggest.DIC_USER);
-        if (null == locale) throw new NullPointerException(); // Catch the error earlier
-        mLocale = locale;
-        mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
-        // Perform a managed query. The Activity will handle closing and re-querying the cursor
-        // when needed.
-        ContentResolver cres = context.getContentResolver();
-
-        mObserver = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean self) {
-                setRequiresReload(true);
-            }
-        };
-        cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
-
-        loadDictionary();
-    }
-
-    @Override
-    public synchronized void close() {
-        if (mObserver != null) {
-            getContext().getContentResolver().unregisterContentObserver(mObserver);
-            mObserver = null;
-        }
-        super.close();
-    }
-
-    @Override
-    public void loadDictionaryAsync() {
-        // Split the locale. For example "en" => ["en"], "de_DE" => ["de", "DE"],
-        // "en_US_foo_bar_qux" => ["en", "US", "foo_bar_qux"] because of the limit of 3.
-        // This is correct for locale processing.
-        // For this example, we'll look at the "en_US_POSIX" case.
-        final String[] localeElements =
-                TextUtils.isEmpty(mLocale) ? new String[] {} : mLocale.split("_", 3);
-        final int length = localeElements.length;
-
-        final StringBuilder request = new StringBuilder("(locale is NULL)");
-        String localeSoFar = "";
-        // At start, localeElements = ["en", "US", "POSIX"] ; localeSoFar = "" ;
-        // and request = "(locale is NULL)"
-        for (int i = 0; i < length; ++i) {
-            // i | localeSoFar    | localeElements
-            // 0 | ""             | ["en", "US", "POSIX"]
-            // 1 | "en_"          | ["en", "US", "POSIX"]
-            // 2 | "en_US_"       | ["en", "en_US", "POSIX"]
-            localeElements[i] = localeSoFar + localeElements[i];
-            localeSoFar = localeElements[i] + "_";
-            // i | request
-            // 0 | "(locale is NULL)"
-            // 1 | "(locale is NULL) or (locale=?)"
-            // 2 | "(locale is NULL) or (locale=?) or (locale=?)"
-            request.append(" or (locale=?)");
-        }
-        // At the end, localeElements = ["en", "en_US", "en_US_POSIX"]; localeSoFar = en_US_POSIX_"
-        // and request = "(locale is NULL) or (locale=?) or (locale=?) or (locale=?)"
-
-        final String[] requestArguments;
-        // If length == 3, we already have all the arguments we need (common prefix is meaningless
-        // inside variants
-        if (mAlsoUseMoreRestrictiveLocales && length < 3) {
-            request.append(" or (locale like ?)");
-            // The following creates an array with one more (null) position
-            final String[] localeElementsWithMoreRestrictiveLocalesIncluded =
-                    Arrays.copyOf(localeElements, length + 1);
-            localeElementsWithMoreRestrictiveLocalesIncluded[length] =
-                    localeElements[length - 1] + "_%";
-            requestArguments = localeElementsWithMoreRestrictiveLocalesIncluded;
-            // If for example localeElements = ["en"]
-            // then requestArguments = ["en", "en_%"]
-            // and request = (locale is NULL) or (locale=?) or (locale like ?)
-            // If localeElements = ["en", "en_US"]
-            // then requestArguments = ["en", "en_US", "en_US_%"]
-        } else {
-            requestArguments = localeElements;
-        }
-        final Cursor cursor = getContext().getContentResolver()
-                .query(Words.CONTENT_URI, PROJECTION_QUERY, request.toString(),
-                        requestArguments, null);
-        try {
-            addWords(cursor);
-        } finally {
-            if (null != cursor) cursor.close();
-        }
-    }
-
-    public boolean isEnabled() {
-        final ContentResolver cr = getContext().getContentResolver();
-        final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
-        if (client != null) {
-            client.release();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Adds a word to the user dictionary and makes it persistent.
-     *
-     * This will call upon the system interface to do the actual work through the intent
-     * readied by the system to this effect.
-     *
-     * @param word the word to add. If the word is capitalized, then the dictionary will
-     * recognize it as a capitalized word when searched.
-     * @param frequency the frequency of occurrence of the word. A frequency of 255 is considered
-     * the highest.
-     * @TODO use a higher or float range for frequency
-     */
-    public synchronized void addWordToUserDictionary(final String word, final int frequency) {
-        // Force load the dictionary here synchronously
-        if (getRequiresReload()) loadDictionaryAsync();
-        // TODO: do something for the UI. With the following, any sufficiently long word will
-        // look like it will go to the user dictionary but it won't.
-        // Safeguard against adding long words. Can cause stack overflow.
-        if (word.length() >= getMaxWordLength()) return;
-
-        // TODO: Add an argument to the intent to specify the frequency.
-        Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
-        intent.putExtra(Words.WORD, word);
-        intent.putExtra(Words.LOCALE, mLocale);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(intent);
-    }
-
-    @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
-        super.getWords(codes, prevWordForBigrams, callback, proximityInfo);
-    }
-
-    @Override
-    public synchronized boolean isValidWord(CharSequence word) {
-        return super.isValidWord(word);
-    }
-
-    private void addWords(Cursor cursor) {
-        clearDictionary();
-        if (cursor == null) return;
-        final int maxWordLength = getMaxWordLength();
-        if (cursor.moveToFirst()) {
-            final int indexWord = cursor.getColumnIndex(Words.WORD);
-            final int indexShortcut = cursor.getColumnIndex(SHORTCUT);
-            final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
-            while (!cursor.isAfterLast()) {
-                String word = cursor.getString(indexWord);
-                String shortcut = cursor.getString(indexShortcut);
-                int frequency = cursor.getInt(indexFrequency);
-                // Safeguard against adding really long words. Stack may overflow due
-                // to recursion
-                if (word.length() < maxWordLength) {
-                    super.addWord(word, null, frequency);
-                }
-                if (null != shortcut && shortcut.length() < maxWordLength) {
-                    super.addWord(shortcut, word, frequency);
-                }
-                cursor.moveToNext();
-            }
-        }
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index d34cad2..7fffc31 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -30,18 +30,17 @@
 import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.ContactsBinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.Dictionary.WordCallback;
 import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
-import com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
-import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
+import com.android.inputmethod.latin.UserBinaryDictionary;
 import com.android.inputmethod.latin.WhitelistDictionary;
 import com.android.inputmethod.latin.WordComposer;
 
@@ -73,11 +72,11 @@
     private final static String[] EMPTY_STRING_ARRAY = new String[0];
     private Map<String, DictionaryPool> mDictionaryPools =
             Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
-    private Map<String, Dictionary> mUserDictionaries =
-            Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+    private Map<String, UserBinaryDictionary> mUserDictionaries =
+            Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
     private Map<String, Dictionary> mWhitelistDictionaries =
             Collections.synchronizedMap(new TreeMap<String, Dictionary>());
-    private Dictionary mContactsDictionary;
+    private ContactsBinaryDictionary mContactsDictionary;
 
     // The threshold for a candidate to be offered as a suggestion.
     private float mSuggestionThreshold;
@@ -154,13 +153,9 @@
 
     private void startUsingContactsDictionaryLocked() {
         if (null == mContactsDictionary) {
-            if (LatinIME.USE_BINARY_CONTACTS_DICTIONARY) {
-                // TODO: use the right locale for each session
-                mContactsDictionary =
-                        new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault());
-            } else {
-                mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
-            }
+            // TODO: use the right locale for each session
+            mContactsDictionary =
+                    new SynchronouslyLoadedContactsBinaryDictionary(this, Locale.getDefault());
         }
         final Iterator<WeakReference<DictionaryCollection>> iterator =
                 mDictionaryCollectionsList.iterator();
@@ -368,8 +363,9 @@
     private void closeAllDictionaries() {
         final Map<String, DictionaryPool> oldPools = mDictionaryPools;
         mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
-        final Map<String, Dictionary> oldUserDictionaries = mUserDictionaries;
-        mUserDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
+        final Map<String, UserBinaryDictionary> oldUserDictionaries = mUserDictionaries;
+        mUserDictionaries =
+                Collections.synchronizedMap(new TreeMap<String, UserBinaryDictionary>());
         final Map<String, Dictionary> oldWhitelistDictionaries = mWhitelistDictionaries;
         mWhitelistDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
         new Thread("spellchecker_close_dicts") {
@@ -389,7 +385,7 @@
                         // The synchronously loaded contacts dictionary should have been in one
                         // or several pools, but it is shielded against multiple closing and it's
                         // safe to call it several times.
-                        final Dictionary dictToClose = mContactsDictionary;
+                        final ContactsBinaryDictionary dictToClose = mContactsDictionary;
                         // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY
                         // is no longer needed
                         mContactsDictionary = null;
@@ -421,13 +417,9 @@
                 DictionaryFactory.createMainDictionaryFromManager(this, locale,
                         true /* useFullEditDistance */);
         final String localeStr = locale.toString();
-        Dictionary userDictionary = mUserDictionaries.get(localeStr);
+        UserBinaryDictionary userDictionary = mUserDictionaries.get(localeStr);
         if (null == userDictionary) {
-            if (LatinIME.USE_BINARY_USER_DICTIONARY) {
-                userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
-            } else {
-                userDictionary = new SynchronouslyLoadedUserDictionary(this, localeStr, true);
-            }
+            userDictionary = new SynchronouslyLoadedUserBinaryDictionary(this, localeStr, true);
             mUserDictionaries.put(localeStr, userDictionary);
         }
         dictionaryCollection.addDictionary(userDictionary);
@@ -440,17 +432,11 @@
         synchronized (mUseContactsLock) {
             if (mUseContactsDictionary) {
                 if (null == mContactsDictionary) {
-                    // TODO: revert to the concrete type when USE_BINARY_CONTACTS_DICTIONARY is no
-                    // longer needed
-                    if (LatinIME.USE_BINARY_CONTACTS_DICTIONARY) {
-                        // TODO: use the right locale. We can't do it right now because the
-                        // spell checker is reusing the contacts dictionary across sessions
-                        // without regard for their locale, so we need to fix that first.
-                        mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this,
-                                Locale.getDefault());
-                    } else {
-                        mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
-                    }
+                    // TODO: use the right locale. We can't do it right now because the
+                    // spell checker is reusing the contacts dictionary across sessions
+                    // without regard for their locale, so we need to fix that first.
+                    mContactsDictionary = new SynchronouslyLoadedContactsBinaryDictionary(this,
+                            Locale.getDefault());
                 }
             }
             dictionaryCollection.addDictionary(mContactsDictionary);