diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa0265d8694a2e47d9a713a8b48638b6460f2f80
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 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 java.util.HashSet;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.LruCache;
+
+/**
+ * Cache for dictionary facilitators of multiple locales.
+ * This class automatically creates and releases facilitator instances using LRU policy.
+ */
+public class DictionaryFacilitatorLruCache {
+    private static final String TAG = DictionaryFacilitatorLruCache.class.getSimpleName();
+    private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
+    private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
+
+    /**
+     * Class extends LruCache. This class tracks cached locales and closes evicted dictionaries by
+     * overriding entryRemoved.
+     */
+    private static class DictionaryFacilitatorLruCacheInner extends
+            LruCache<Locale, DictionaryFacilitator> {
+        private final HashSet<Locale> mCachedLocales;
+        public DictionaryFacilitatorLruCacheInner(final HashSet<Locale> cachedLocales,
+                final int maxSize) {
+            super(maxSize);
+            mCachedLocales = cachedLocales;
+        }
+
+        @Override
+        protected void entryRemoved(boolean evicted, Locale key,
+                DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
+            if (oldValue != null && oldValue != newValue) {
+                oldValue.closeDictionaries();
+            }
+            if (key != null && newValue == null) {
+                // Remove locale from the cache when the dictionary facilitator for the locale is
+                // evicted and new facilitator is not set for the locale.
+                mCachedLocales.remove(key);
+                if (size() >= maxSize()) {
+                    Log.w(TAG, "DictionaryFacilitator for " + key.toString()
+                            + " has been evicted due to cache size limit."
+                            + " size: " + size() + ", maxSize: " + maxSize());
+                }
+            }
+        }
+    }
+
+    private final Context mContext;
+    private final HashSet<Locale> mCachedLocales = new HashSet<>();
+    private final String mDictionaryNamePrefix;
+    private final DictionaryFacilitatorLruCacheInner mLruCache;
+    private final Object mLock = new Object();
+    private boolean mUseContactsDictionary = false;
+
+    public DictionaryFacilitatorLruCache(final Context context, final int maxSize,
+            final String dictionaryNamePrefix) {
+        mContext = context;
+        mLruCache = new DictionaryFacilitatorLruCacheInner(mCachedLocales, maxSize);
+        mDictionaryNamePrefix = dictionaryNamePrefix;
+    }
+
+    private void waitForLoadingMainDictionary(final DictionaryFacilitator dictionaryFacilitator) {
+        for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
+            try {
+                dictionaryFacilitator.waitForLoadingMainDictionary(
+                        WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+                return;
+            } catch (final InterruptedException e) {
+                Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
+                if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
+                    Log.i(TAG, "Retry", e);
+                } else {
+                    Log.w(TAG, "Give up retrying. Retried "
+                            + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
+                }
+            }
+        }
+    }
+
+    private void resetDictionariesForLocaleLocked(final DictionaryFacilitator dictionaryFacilitator,
+            final Locale locale) {
+        dictionaryFacilitator.resetDictionariesWithDictNamePrefix(mContext, locale,
+                mUseContactsDictionary, false /* usePersonalizedDicts */,
+                false /* forceReloadMainDictionary */, null /* listener */,
+                mDictionaryNamePrefix);
+    }
+
+    public void setUseContactsDictionary(final boolean useContectsDictionary) {
+        if (mUseContactsDictionary == useContectsDictionary) {
+            // The value has not been changed.
+            return;
+        }
+        synchronized (mLock) {
+            mUseContactsDictionary = useContectsDictionary;
+            for (final Locale locale : mCachedLocales) {
+                final DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
+                resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
+                waitForLoadingMainDictionary(dictionaryFacilitator);
+            }
+        }
+    }
+
+    public DictionaryFacilitator get(final Locale locale) {
+        DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
+        if (dictionaryFacilitator != null) {
+            // dictionary falicitator for the locale is in the cache.
+            return dictionaryFacilitator;
+        }
+        synchronized (mLock) {
+            dictionaryFacilitator = mLruCache.get(locale);
+            if (dictionaryFacilitator != null) {
+                return dictionaryFacilitator;
+            }
+            dictionaryFacilitator = new DictionaryFacilitator();
+            resetDictionariesForLocaleLocked(dictionaryFacilitator, locale);
+            waitForLoadingMainDictionary(dictionaryFacilitator);
+            mLruCache.put(locale, dictionaryFacilitator);
+            mCachedLocales.add(locale);
+            return dictionaryFacilitator;
+        }
+    }
+
+    public void evictAll() {
+        synchronized (mLock) {
+            mLruCache.evictAll();
+            mCachedLocales.clear();
+        }
+    }
+
+    @UsedForTesting
+    HashSet<Locale> getCachedLocalesForTesting() {
+        return mCachedLocales;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 47bff3ebb2d3c70f98e0c8280410496eec9a6063..49b34d391b02c7ec09ec8dbfcf58570ca0facb87 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -16,14 +16,11 @@
 
 package com.android.inputmethod.latin.spellcheck;
 
-import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.service.textservice.SpellCheckerService;
 import android.text.InputType;
-import android.util.Log;
-import android.util.LruCache;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.textservice.SuggestionsInfo;
@@ -32,40 +29,21 @@ import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet;
 import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.ContactsBinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
 import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.UserBinaryDictionary;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SuggestionResults;
 import com.android.inputmethod.latin.WordComposer;
 
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Service for spell checking, using LatinIME's dictionaries and mechanisms.
@@ -81,61 +59,28 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
     private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 368;
 
     private static final String DICTIONARY_NAME_PREFIX = "spellcheck_";
-    private static final int WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS = 1000;
-    private static final int MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT = 5;
 
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    private final HashSet<Locale> mCachedLocales = new HashSet<>();
-
     private final int MAX_NUM_OF_THREADS_READ_DICTIONARY = 2;
     private final Semaphore mSemaphore = new Semaphore(MAX_NUM_OF_THREADS_READ_DICTIONARY,
             true /* fair */);
     // TODO: Make each spell checker session has its own session id.
     private final ConcurrentLinkedQueue<Integer> mSessionIdPool = new ConcurrentLinkedQueue<>();
 
-    private static class DictionaryFacilitatorLruCache extends
-            LruCache<Locale, DictionaryFacilitator> {
-        private final HashSet<Locale> mCachedLocales;
-        public DictionaryFacilitatorLruCache(final HashSet<Locale> cachedLocales, int maxSize) {
-            super(maxSize);
-            mCachedLocales = cachedLocales;
-        }
-
-        @Override
-        protected void entryRemoved(boolean evicted, Locale key,
-                DictionaryFacilitator oldValue, DictionaryFacilitator newValue) {
-            if (oldValue != null && oldValue != newValue) {
-                oldValue.closeDictionaries();
-            }
-            if (key != null && newValue == null) {
-                // Remove locale from the cache when the dictionary facilitator for the locale is
-                // evicted and new facilitator is not set for the locale.
-                mCachedLocales.remove(key);
-                if (size() >= maxSize()) {
-                    Log.w(TAG, "DictionaryFacilitator for " + key.toString()
-                            + " has been evicted due to cache size limit."
-                            + " size: " + size() + ", maxSize: " + maxSize());
-                }
-            }
-        }
-    }
-
     private static final int MAX_DICTIONARY_FACILITATOR_COUNT = 3;
-    private final LruCache<Locale, DictionaryFacilitator> mDictionaryFacilitatorCache =
-            new DictionaryFacilitatorLruCache(mCachedLocales, MAX_DICTIONARY_FACILITATOR_COUNT);
+    private final DictionaryFacilitatorLruCache mDictionaryFacilitatorCache =
+            new DictionaryFacilitatorLruCache(this /* context */, MAX_DICTIONARY_FACILITATOR_COUNT,
+                    DICTIONARY_NAME_PREFIX);
     private final ConcurrentHashMap<Locale, Keyboard> mKeyboardCache = new ConcurrentHashMap<>();
 
     // The threshold for a suggestion to be considered "recommended".
     private float mRecommendedThreshold;
-    // Whether to use the contacts dictionary
-    private boolean mUseContactsDictionary;
     // TODO: make a spell checker option to block offensive words or not
     private final SettingsValuesForSuggestion mSettingsValuesForSuggestion =
             new SettingsValuesForSuggestion(true /* blockPotentiallyOffensive */,
                     true /* spaceAwareGestureEnabled */,
                     null /* additionalFeaturesSettingValues */);
-    private final Object mDictionaryLock = new Object();
 
     public static final String SINGLE_QUOTE = "\u0027";
     public static final String APOSTROPHE = "\u2019";
@@ -177,20 +122,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
     public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
         if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
             final boolean useContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
-            if (useContactsDictionary != mUseContactsDictionary) {
-                mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
-                try {
-                    mUseContactsDictionary = useContactsDictionary;
-                    for (final Locale locale : mCachedLocales) {
-                        final DictionaryFacilitator dictionaryFacilitator =
-                                mDictionaryFacilitatorCache.get(locale);
-                        resetDictionariesForLocale(this /* context  */,
-                                dictionaryFacilitator, locale, mUseContactsDictionary);
-                    }
-                } finally {
-                    mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
-                }
-            }
+            mDictionaryFacilitatorCache.setUseContactsDictionary(useContactsDictionary);
     }
 
     @Override
@@ -223,7 +155,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
         mSemaphore.acquireUninterruptibly();
         try {
             DictionaryFacilitator dictionaryFacilitatorForLocale =
-                    getDictionaryFacilitatorForLocaleLocked(locale);
+                    mDictionaryFacilitatorCache.get(locale);
             return dictionaryFacilitatorForLocale.isValidWord(word, false /* igroreCase */);
         } finally {
             mSemaphore.release();
@@ -237,7 +169,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
         try {
             sessionId = mSessionIdPool.poll();
             DictionaryFacilitator dictionaryFacilitatorForLocale =
-                    getDictionaryFacilitatorForLocaleLocked(locale);
+                    mDictionaryFacilitatorCache.get(locale);
             return dictionaryFacilitatorForLocale.getSuggestionResults(composer, prevWordsInfo,
                     proximityInfo, mSettingsValuesForSuggestion, sessionId);
         } finally {
@@ -252,56 +184,18 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
         mSemaphore.acquireUninterruptibly();
         try {
             final DictionaryFacilitator dictionaryFacilitator =
-                    getDictionaryFacilitatorForLocaleLocked(locale);
+                    mDictionaryFacilitatorCache.get(locale);
             return dictionaryFacilitator.hasInitializedMainDictionary();
         } finally {
             mSemaphore.release();
         }
     }
 
-    private DictionaryFacilitator getDictionaryFacilitatorForLocaleLocked(final Locale locale) {
-        DictionaryFacilitator dictionaryFacilitatorForLocale =
-                mDictionaryFacilitatorCache.get(locale);
-        if (dictionaryFacilitatorForLocale == null) {
-            dictionaryFacilitatorForLocale = new DictionaryFacilitator();
-            mDictionaryFacilitatorCache.put(locale, dictionaryFacilitatorForLocale);
-            mCachedLocales.add(locale);
-            resetDictionariesForLocale(this /* context */, dictionaryFacilitatorForLocale,
-                    locale, mUseContactsDictionary);
-        }
-        return dictionaryFacilitatorForLocale;
-    }
-
-    private static void resetDictionariesForLocale(final Context context,
-            final DictionaryFacilitator dictionaryFacilitator, final Locale locale,
-            final boolean useContactsDictionary) {
-        dictionaryFacilitator.resetDictionariesWithDictNamePrefix(context, locale,
-                useContactsDictionary, false /* usePersonalizedDicts */,
-                false /* forceReloadMainDictionary */, null /* listener */,
-                DICTIONARY_NAME_PREFIX);
-        for (int i = 0; i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT; i++) {
-            try {
-                dictionaryFacilitator.waitForLoadingMainDictionary(
-                        WAIT_FOR_LOADING_MAIN_DICT_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
-                return;
-            } catch (final InterruptedException e) {
-                Log.i(TAG, "Interrupted during waiting for loading main dictionary.", e);
-                if (i < MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT - 1) {
-                    Log.i(TAG, "Retry", e);
-                } else {
-                    Log.w(TAG, "Give up retrying. Retried "
-                            + MAX_RETRY_COUNT_FOR_WAITING_FOR_LOADING_DICT + " times.", e);
-                }
-            }
-        }
-    }
-
     @Override
     public boolean onUnbind(final Intent intent) {
         mSemaphore.acquireUninterruptibly(MAX_NUM_OF_THREADS_READ_DICTIONARY);
         try {
             mDictionaryFacilitatorCache.evictAll();
-            mCachedLocales.clear();
         } finally {
             mSemaphore.release(MAX_NUM_OF_THREADS_READ_DICTIONARY);
         }
diff --git a/tests/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCacheTests.java b/tests/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCacheTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..5af30e5b00b7f5538edefbd882e710a820e81d76
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCacheTests.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014 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 java.util.Locale;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+@LargeTest
+public class DictionaryFacilitatorLruCacheTests extends AndroidTestCase {
+    static final int MAX_CACHE_SIZE = 2;
+    static final int MAX_CACHE_SIZE_LARGE = 5;
+
+    public void testCacheSize() {
+        final DictionaryFacilitatorLruCache cache =
+                new DictionaryFacilitatorLruCache(getContext(), MAX_CACHE_SIZE, "");
+
+        assertEquals(0, cache.getCachedLocalesForTesting().size());
+        assertNotNull(cache.get(Locale.US));
+        assertEquals(1, cache.getCachedLocalesForTesting().size());
+        assertNotNull(cache.get(Locale.UK));
+        assertEquals(2, cache.getCachedLocalesForTesting().size());
+        assertNotNull(cache.get(Locale.FRENCH));
+        assertEquals(2, cache.getCachedLocalesForTesting().size());
+        cache.evictAll();
+        assertEquals(0, cache.getCachedLocalesForTesting().size());
+    }
+
+    public void testGetFacilitator() {
+        testGetFacilitator(new DictionaryFacilitatorLruCache(getContext(), MAX_CACHE_SIZE, ""));
+        testGetFacilitator(new DictionaryFacilitatorLruCache(
+                getContext(), MAX_CACHE_SIZE_LARGE, ""));
+    }
+
+    private void testGetFacilitator(final DictionaryFacilitatorLruCache cache) {
+        final DictionaryFacilitator dictionaryFacilitatorEnUs = cache.get(Locale.US);
+        assertNotNull(dictionaryFacilitatorEnUs);
+        assertEquals(Locale.US, dictionaryFacilitatorEnUs.getLocale());
+
+        final DictionaryFacilitator dictionaryFacilitatorFr = cache.get(Locale.FRENCH);
+        assertNotNull(dictionaryFacilitatorEnUs);
+        assertEquals(Locale.FRENCH, dictionaryFacilitatorFr.getLocale());
+
+        final DictionaryFacilitator dictionaryFacilitatorDe = cache.get(Locale.GERMANY);
+        assertNotNull(dictionaryFacilitatorDe);
+        assertEquals(Locale.GERMANY, dictionaryFacilitatorDe.getLocale());
+    }
+
+    public void testSetUseContactsDictionary() {
+        testSetUseContactsDictionary(new DictionaryFacilitatorLruCache(
+                getContext(), MAX_CACHE_SIZE, ""));
+        testSetUseContactsDictionary(new DictionaryFacilitatorLruCache(
+                getContext(), MAX_CACHE_SIZE_LARGE, ""));
+    }
+
+    private void testSetUseContactsDictionary(final DictionaryFacilitatorLruCache cache) {
+        assertNull(cache.get(Locale.US).getSubDictForTesting(Dictionary.TYPE_CONTACTS));
+        cache.setUseContactsDictionary(true /* useContactsDictionary */);
+        assertNotNull(cache.get(Locale.US).getSubDictForTesting(Dictionary.TYPE_CONTACTS));
+        assertNotNull(cache.get(Locale.FRENCH).getSubDictForTesting(Dictionary.TYPE_CONTACTS));
+        assertNotNull(cache.get(Locale.GERMANY).getSubDictForTesting(Dictionary.TYPE_CONTACTS));
+        cache.setUseContactsDictionary(false /* useContactsDictionary */);
+        assertNull(cache.get(Locale.GERMANY).getSubDictForTesting(Dictionary.TYPE_CONTACTS));
+        assertNull(cache.get(Locale.US).getSubDictForTesting(Dictionary.TYPE_CONTACTS));
+    }
+}