Merge "Add unigram/bigram counting."
diff --git a/dictionaries/en_GB_wordlist.combined.gz b/dictionaries/en_GB_wordlist.combined.gz
index d28ef48..afef676 100644
--- a/dictionaries/en_GB_wordlist.combined.gz
+++ b/dictionaries/en_GB_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/en_US_wordlist.combined.gz b/dictionaries/en_US_wordlist.combined.gz
index b656f88..eafbc9d 100644
--- a/dictionaries/en_US_wordlist.combined.gz
+++ b/dictionaries/en_US_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/en_wordlist.combined.gz b/dictionaries/en_wordlist.combined.gz
index 8aa40e9..9cbca0b 100644
--- a/dictionaries/en_wordlist.combined.gz
+++ b/dictionaries/en_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/fr_wordlist.combined.gz b/dictionaries/fr_wordlist.combined.gz
index 1d988d6..1815e47 100644
--- a/dictionaries/fr_wordlist.combined.gz
+++ b/dictionaries/fr_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/pt_BR_wordlist.combined.gz b/dictionaries/pt_BR_wordlist.combined.gz
index 221ea75..876eb71 100644
--- a/dictionaries/pt_BR_wordlist.combined.gz
+++ b/dictionaries/pt_BR_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/pt_PT_wordlist.combined.gz b/dictionaries/pt_PT_wordlist.combined.gz
index 6a041d9..4068690 100644
--- a/dictionaries/pt_PT_wordlist.combined.gz
+++ b/dictionaries/pt_PT_wordlist.combined.gz
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
index 87211a5..fa2cb85 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
index c1e16a6..8a6336a 100644
--- a/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
index 26d0684..edf1379 100644
--- a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
index f98653e..8e9a349 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
index 16be37d..0795fcc 100644
--- a/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
index 95d718a..f76da57 100644
--- a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
index 738316d..a2f6ac0 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
index 944a852..b2bb9b8 100644
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
index 2016caf..23e75bf 100644
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
index b35c29f..17f0a7a 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
index 6809f07..f04cadf 100644
--- a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo_dark.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
index 6bd506a..e74d523 100644
--- a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo_dark.png
Binary files differ
diff --git a/java/res/raw/main_en.dict b/java/res/raw/main_en.dict
index 6564d47..8660c28 100644
--- a/java/res/raw/main_en.dict
+++ b/java/res/raw/main_en.dict
Binary files differ
diff --git a/java/res/raw/main_fr.dict b/java/res/raw/main_fr.dict
index 10adad0..0d2e518 100644
--- a/java/res/raw/main_fr.dict
+++ b/java/res/raw/main_fr.dict
Binary files differ
diff --git a/java/res/raw/main_pt_br.dict b/java/res/raw/main_pt_br.dict
index f9ae9b5..98a27c7 100644
--- a/java/res/raw/main_pt_br.dict
+++ b/java/res/raw/main_pt_br.dict
Binary files differ
diff --git a/java/res/values-kk/strings-appname.xml b/java/res/values-kk/strings-appname.xml
new file mode 100644
index 0000000..1e201fa
--- /dev/null
+++ b/java/res/values-kk/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Android пернетақтасы (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android емлені тексеру құралы (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android пернетақтасының параметрлері (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android емлені тексеру құралының параметрлері (AOSP)"</string>
+</resources>
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 99859de..d3da068 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -34,6 +34,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -69,14 +70,15 @@
             FormatSpec.FileHeader.ATTRIBUTE_VALUE_TRUE;
 
     /**
-     * A static map of time recorders, each of which records the time of accesses to a single binary
-     * dictionary file. The key for this map is the filename and the value is the shared dictionary
-     * time recorder associated with that filename.
+     * A static map of update controllers, each of which records the time of accesses to a single
+     * binary dictionary file and tracks whether the file is regenerating. The key for this map is
+     * the filename and the value is the shared dictionary time recorder associated with that
+     * filename.
      */
-    private static volatile ConcurrentHashMap<String, DictionaryTimeRecorder>
-            sFilenameDictionaryTimeRecorderMap = CollectionUtils.newConcurrentHashMap();
+    private static final ConcurrentHashMap<String, DictionaryUpdateController>
+            sFilenameDictionaryUpdateControllerMap = CollectionUtils.newConcurrentHashMap();
 
-    private static volatile ConcurrentHashMap<String, PrioritizedSerialExecutor>
+    private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
             sFilenameExecutorMap = CollectionUtils.newConcurrentHashMap();
 
     /** The application context. */
@@ -103,13 +105,13 @@
     private final boolean mIsUpdatable;
 
     // TODO: remove, once dynamic operations is serialized
-    /** Records access to the shared binary dictionary file across multiple instances. */
-    private final DictionaryTimeRecorder mFilenameDictionaryTimeRecorder;
+    /** Controls updating the shared binary dictionary file across multiple instances. */
+    private final DictionaryUpdateController mFilenameDictionaryUpdateController;
 
     // TODO: remove, once dynamic operations is serialized
-    /** Records access to the local binary dictionary for this instance. */
-    private final DictionaryTimeRecorder mPerInstanceDictionaryTimeRecorder =
-            new DictionaryTimeRecorder();
+    /** Controls updating the local binary dictionary for this instance. */
+    private final DictionaryUpdateController mPerInstanceDictionaryUpdateController =
+            new DictionaryUpdateController();
 
     /* A extension for a binary dictionary file. */
     public static final String DICT_FILE_EXTENSION = ".dict";
@@ -131,15 +133,15 @@
     protected abstract boolean hasContentChanged();
 
     /**
-     * Gets the dictionary time recorder for the given filename.
+     * Gets the dictionary update controller for the given filename.
      */
-    private static DictionaryTimeRecorder getDictionaryTimeRecorder(
+    private static DictionaryUpdateController getDictionaryUpdateController(
             String filename) {
-        DictionaryTimeRecorder recorder = sFilenameDictionaryTimeRecorderMap.get(filename);
+        DictionaryUpdateController recorder = sFilenameDictionaryUpdateControllerMap.get(filename);
         if (recorder == null) {
-            synchronized(sFilenameDictionaryTimeRecorderMap) {
-                recorder = new DictionaryTimeRecorder();
-                sFilenameDictionaryTimeRecorderMap.put(filename, recorder);
+            synchronized(sFilenameDictionaryUpdateControllerMap) {
+                recorder = new DictionaryUpdateController();
+                sFilenameDictionaryUpdateControllerMap.put(filename, recorder);
             }
         }
         return recorder;
@@ -189,7 +191,7 @@
         mContext = context;
         mIsUpdatable = isUpdatable;
         mBinaryDictionary = null;
-        mFilenameDictionaryTimeRecorder = getDictionaryTimeRecorder(filename);
+        mFilenameDictionaryUpdateController = getDictionaryUpdateController(filename);
         // Currently, only dynamic personalization dictionary is updatable.
         mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable);
     }
@@ -347,6 +349,9 @@
             final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
             final int sessionId) {
         reloadDictionaryIfRequired();
+        if (isRegenerating()) {
+            return null;
+        }
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         final AsyncResultHolder<ArrayList<SuggestedWordInfo>> holder =
                 new AsyncResultHolder<ArrayList<SuggestedWordInfo>>();
@@ -407,6 +412,9 @@
     }
 
     protected boolean isValidWordInner(final String word) {
+        if (isRegenerating()) {
+            return false;
+        }
         final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
         getExecutor(mFilename).executePrioritized(new Runnable() {
             @Override
@@ -432,7 +440,7 @@
      * dictionary exists, this method will generate one.
      */
     protected void loadDictionary() {
-        mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = SystemClock.uptimeMillis();
+        mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = SystemClock.uptimeMillis();
         reloadDictionaryIfRequired();
     }
 
@@ -443,8 +451,8 @@
     private void loadBinaryDictionary() {
         if (DEBUG) {
             Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
-                    + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
-                    + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
+                    + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+                    + mFilenameDictionaryUpdateController.mLastUpdateTime);
         }
 
         final File file = new File(mContext.getFilesDir(), mFilename);
@@ -482,8 +490,8 @@
     private void writeBinaryDictionary() {
         if (DEBUG) {
             Log.d(TAG, "Generating binary dictionary: " + mFilename + " request="
-                    + mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime + " update="
-                    + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
+                    + mFilenameDictionaryUpdateController.mLastUpdateRequestTime + " update="
+                    + mFilenameDictionaryUpdateController.mLastUpdateTime);
         }
         if (needsToReloadBeforeWriting()) {
             mDictionaryWriter.clear();
@@ -517,11 +525,11 @@
      */
     protected void setRequiresReload(final boolean requiresRebuild) {
         final long time = SystemClock.uptimeMillis();
-        mPerInstanceDictionaryTimeRecorder.mLastUpdateRequestTime = time;
-        mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime = time;
+        mPerInstanceDictionaryUpdateController.mLastUpdateRequestTime = time;
+        mFilenameDictionaryUpdateController.mLastUpdateRequestTime = time;
         if (DEBUG) {
             Log.d(TAG, "Reload request: " + mFilename + ": request=" + time + " update="
-                    + mFilenameDictionaryTimeRecorder.mLastUpdateTime);
+                    + mFilenameDictionaryUpdateController.mLastUpdateTime);
         }
     }
 
@@ -530,14 +538,26 @@
      */
     public final void reloadDictionaryIfRequired() {
         if (!isReloadRequired()) return;
-        reloadDictionary();
+        if (setIsRegeneratingIfNotRegenerating()) {
+            reloadDictionary();
+        }
     }
 
     /**
      * Returns whether a dictionary reload is required.
      */
     private boolean isReloadRequired() {
-        return mBinaryDictionary == null || mPerInstanceDictionaryTimeRecorder.isOutOfDate();
+        return mBinaryDictionary == null || mPerInstanceDictionaryUpdateController.isOutOfDate();
+    }
+
+    private boolean isRegenerating() {
+        return mFilenameDictionaryUpdateController.mIsRegenerating.get();
+    }
+
+    // Returns whether the dictionary can be regenerated.
+    private boolean setIsRegeneratingIfNotRegenerating() {
+        return mFilenameDictionaryUpdateController.mIsRegenerating.compareAndSet(
+                false /* expect */ , true /* update */);
     }
 
     /**
@@ -550,39 +570,44 @@
         getExecutor(mFilename).execute(new Runnable() {
             @Override
             public void run() {
-                final long time = SystemClock.uptimeMillis();
-                final boolean dictionaryFileExists = dictionaryFileExists();
-                if (mFilenameDictionaryTimeRecorder.isOutOfDate() || !dictionaryFileExists) {
-                    // If the shared dictionary file does not exist or is out of date, the first
-                    // instance that acquires the lock will generate a new one.
-                    if (hasContentChanged() || !dictionaryFileExists) {
-                        // If the source content has changed or the dictionary does not exist,
-                        // rebuild the binary dictionary. Empty dictionaries are supported (in the
-                        // case where loadDictionaryAsync() adds nothing) in order to provide a
-                        // uniform framework.
-                        mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
+                try {
+                    final long time = SystemClock.uptimeMillis();
+                    final boolean dictionaryFileExists = dictionaryFileExists();
+                    if (mFilenameDictionaryUpdateController.isOutOfDate()
+                            || !dictionaryFileExists) {
+                        // If the shared dictionary file does not exist or is out of date, the
+                        // first instance that acquires the lock will generate a new one.
+                        if (hasContentChanged() || !dictionaryFileExists) {
+                            // If the source content has changed or the dictionary does not exist,
+                            // rebuild the binary dictionary. Empty dictionaries are supported (in
+                            // the case where loadDictionaryAsync() adds nothing) in order to
+                            // provide a uniform framework.
+                            mFilenameDictionaryUpdateController.mLastUpdateTime = time;
+                            writeBinaryDictionary();
+                            loadBinaryDictionary();
+                        } else {
+                            // If not, the reload request was unnecessary so revert
+                            // LastUpdateRequestTime to LastUpdateTime.
+                            mFilenameDictionaryUpdateController.mLastUpdateRequestTime =
+                                    mFilenameDictionaryUpdateController.mLastUpdateTime;
+                        }
+                    } else if (mBinaryDictionary == null ||
+                            mPerInstanceDictionaryUpdateController.mLastUpdateTime
+                                    < mFilenameDictionaryUpdateController.mLastUpdateTime) {
+                        // Otherwise, if the local dictionary is older than the shared dictionary,
+                        // load the shared dictionary.
+                        loadBinaryDictionary();
+                    }
+                    if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
+                        // Binary dictionary is not valid. Regenerate the dictionary file.
+                        mFilenameDictionaryUpdateController.mLastUpdateTime = time;
                         writeBinaryDictionary();
                         loadBinaryDictionary();
-                    } else {
-                        // If not, the reload request was unnecessary so revert
-                        // LastUpdateRequestTime to LastUpdateTime.
-                        mFilenameDictionaryTimeRecorder.mLastUpdateRequestTime =
-                                mFilenameDictionaryTimeRecorder.mLastUpdateTime;
                     }
-                } else if (mBinaryDictionary == null ||
-                        mPerInstanceDictionaryTimeRecorder.mLastUpdateTime
-                                < mFilenameDictionaryTimeRecorder.mLastUpdateTime) {
-                    // Otherwise, if the local dictionary is older than the shared dictionary, load
-                    // the shared dictionary.
-                    loadBinaryDictionary();
+                    mPerInstanceDictionaryUpdateController.mLastUpdateTime = time;
+                } finally {
+                    mFilenameDictionaryUpdateController.mIsRegenerating.set(false);
                 }
-                if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
-                    // Binary dictionary is not valid. Regenerate the dictionary file.
-                    mFilenameDictionaryTimeRecorder.mLastUpdateTime = time;
-                    writeBinaryDictionary();
-                    loadBinaryDictionary();
-                }
-                mPerInstanceDictionaryTimeRecorder.mLastUpdateTime = time;
             }
         });
     }
@@ -622,59 +647,19 @@
     }
 
     /**
-     * Time recorder for tracking whether the dictionary is out of date.
+     * For tracking whether the dictionary is out of date and the dictionary is regenerating.
      * Can be shared across multiple dictionary instances that access the same filename.
      */
-    private static class DictionaryTimeRecorder {
-        private volatile long mLastUpdateTime = 0;
-        private volatile long mLastUpdateRequestTime = 0;
+    private static class DictionaryUpdateController {
+        public volatile long mLastUpdateTime = 0;
+        public volatile long mLastUpdateRequestTime = 0;
+        public volatile AtomicBoolean mIsRegenerating = new AtomicBoolean();
 
-        private boolean isOutOfDate() {
+        public boolean isOutOfDate() {
             return (mLastUpdateRequestTime > mLastUpdateTime);
         }
     }
 
-    /**
-     * Dynamically adds a word unigram to the dictionary for testing with blocking-lock.
-     */
-    @UsedForTesting
-    protected void addWordDynamicallyForTests(final String word, final String shortcutTarget,
-            final int frequency, final boolean isNotAWord) {
-        getExecutor(mFilename).executePrioritized(new Runnable() {
-            @Override
-            public void run() {
-                addWordDynamically(word, shortcutTarget, frequency, isNotAWord);
-            }
-        });
-    }
-
-    /**
-     * Dynamically adds a word bigram in the dictionary for testing with blocking-lock.
-     */
-    @UsedForTesting
-    protected void addBigramDynamicallyForTests(final String word0, final String word1,
-            final int frequency, final boolean isValid) {
-        getExecutor(mFilename).executePrioritized(new Runnable() {
-            @Override
-            public void run() {
-                addBigramDynamically(word0, word1, frequency, isValid);
-            }
-        });
-    }
-
-    /**
-     * Dynamically remove a word bigram in the dictionary for testing with blocking-lock.
-     */
-    @UsedForTesting
-    protected void removeBigramDynamicallyForTests(final String word0, final String word1) {
-        getExecutor(mFilename).executePrioritized(new Runnable() {
-            @Override
-            public void run() {
-                removeBigramDynamically(word0, word1);
-            }
-        });
-    }
-
     // TODO: Implement native binary methods once the dynamic dictionary implementation is done.
     @UsedForTesting
     public boolean isInDictionaryForTests(final String word) {
@@ -687,7 +672,7 @@
                         holder.set(mBinaryDictionary.isValidWord(word));
                     } else {
                         holder.set(((DynamicPersonalizationDictionaryWriter) mDictionaryWriter)
-                                .isInDictionaryForTests(word));
+                                .isInBigramListForTests(word));
                     }
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 21b103e..8caf6f1 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -103,6 +103,10 @@
         }
     }
 
+    public boolean isTypeNull() {
+        return InputType.TYPE_NULL == mInputType;
+    }
+
     public boolean isSameInputType(final EditorInfo editorInfo) {
         return editorInfo.inputType == mInputType;
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 270dc4c..5fbbc17 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1521,7 +1521,7 @@
         mSubtypeState.switchSubtype(token, mRichImm);
     }
 
-    private void sendDownUpKeyEventForBackwardCompatibility(final int code) {
+    private void sendDownUpKeyEvent(final int code) {
         final long eventTime = SystemClock.uptimeMillis();
         mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
                 KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
@@ -1538,7 +1538,7 @@
         // TODO: Remove this special handling of digit letters.
         // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
         if (code >= '0' && code <= '9') {
-            sendDownUpKeyEventForBackwardCompatibility(code - '0' + KeyEvent.KEYCODE_0);
+            sendDownUpKeyEvent(code - '0' + KeyEvent.KEYCODE_0);
             return;
         }
 
@@ -1547,7 +1547,7 @@
             // a hardware keyboard event on pressing enter or delete. This is bad for many
             // reasons (there are race conditions with commits) but some applications are
             // relying on this behavior so we continue to support it for older apps.
-            sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_ENTER);
+            sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
         } else {
             final String text = new String(new int[] { code }, 0, 1);
             mConnection.commitText(text, text.length());
@@ -2104,12 +2104,16 @@
                 }
                 final int lengthToDelete = Character.isSupplementaryCodePoint(
                         mConnection.getCodePointBeforeCursor()) ? 2 : 1;
-                if (mAppWorkAroundsUtils.isBeforeJellyBean()) {
-                    // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
-                    // a hardware keyboard event on pressing enter or delete. This is bad for many
-                    // reasons (there are race conditions with commits) but some applications are
-                    // relying on this behavior so we continue to support it for older apps.
-                    sendDownUpKeyEventForBackwardCompatibility(KeyEvent.KEYCODE_DEL);
+                if (mAppWorkAroundsUtils.isBeforeJellyBean() ||
+                        currentSettings.mInputAttributes.isTypeNull()) {
+                    // There are two possible reasons to send a key event: either the field has
+                    // type TYPE_NULL, in which case the keyboard should send events, or we are
+                    // running in backward compatibility mode. Before Jelly bean, the keyboard
+                    // would simulate a hardware keyboard event on pressing enter or delete. This
+                    // is bad for many reasons (there are race conditions with commits) but some
+                    // applications are relying on this behavior so we continue to support it for
+                    // older apps, so we retain this behavior if the app has target SDK < JellyBean.
+                    sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
                 } else {
                     mConnection.deleteSurroundingText(lengthToDelete, 0);
                 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
index 3b1d242..6cc0bfb 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictEncoderUtils.java
@@ -225,6 +225,26 @@
         return position;
     }
 
+    static void writeUIntToStream(final OutputStream stream, final int value, final int size)
+            throws IOException {
+        switch(size) {
+            case 4:
+                stream.write((value >> 24) & 0xFF);
+                /* fall through */
+            case 3:
+                stream.write((value >> 16) & 0xFF);
+                /* fall through */
+            case 2:
+                stream.write((value >> 8) & 0xFF);
+                /* fall through */
+            case 1:
+                stream.write(value & 0xFF);
+                break;
+            default:
+                /* nop */
+        }
+    }
+
     // End utility methods
 
     // This method is responsible for finding a nice ordering of the nodes that favors run-time
diff --git a/java/src/com/android/inputmethod/latin/makedict/SparseTable.java b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
new file mode 100644
index 0000000..0b9cf91
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/makedict/SparseTable.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * SparseTable is an extensible map from integer to integer.
+ * This holds one value for every mBlockSize keys, so it uses 1/mBlockSize'th of the full index
+ * memory.
+ */
+@UsedForTesting
+public class SparseTable {
+
+    /**
+     * mLookupTable is indexed by terminal ID, containing exactly one entry for every mBlockSize
+     * terminals.
+     * It contains at index i = j / mBlockSize the index in mContentsTable where the values for
+     * terminals with IDs j to j + mBlockSize - 1 are stored as an mBlockSize-sized integer array.
+     */
+    private final ArrayList<Integer> mLookupTable;
+    private final ArrayList<Integer> mContentTable;
+
+    private final int mBlockSize;
+    public static final int NOT_EXIST = -1;
+
+    @UsedForTesting
+    public SparseTable(final int initialCapacity, final int blockSize) {
+        mBlockSize = blockSize;
+        final int lookupTableSize = initialCapacity / mBlockSize
+                + (initialCapacity % mBlockSize > 0 ? 1 : 0);
+        mLookupTable = new ArrayList<Integer>(Collections.nCopies(lookupTableSize, NOT_EXIST));
+        mContentTable = new ArrayList<Integer>();
+    }
+
+    @UsedForTesting
+    public SparseTable(final int[] lookupTable, final int[] contentTable, final int blockSize) {
+        mBlockSize = blockSize;
+        mLookupTable = new ArrayList<Integer>(lookupTable.length);
+        for (int i = 0; i < lookupTable.length; ++i) {
+            mLookupTable.add(lookupTable[i]);
+        }
+        mContentTable = new ArrayList<Integer>(contentTable.length);
+        for (int i = 0; i < contentTable.length; ++i) {
+            mContentTable.add(contentTable[i]);
+        }
+    }
+
+    /**
+     * Converts an byte array to an int array considering each set of 4 bytes is an int stored in
+     * big-endian.
+     * The length of byteArray must be a multiple of four.
+     * Otherwise, IndexOutOfBoundsException will be raised.
+     */
+    @UsedForTesting
+    private static void convertByteArrayToIntegerArray(final byte[] byteArray,
+            final ArrayList<Integer> integerArray) {
+        for (int i = 0; i < byteArray.length; i += 4) {
+            int value = 0;
+            for (int j = i; j < i + 4; ++j) {
+                value <<= 8;
+                value |= byteArray[j] & 0xFF;
+             }
+            integerArray.add(value);
+        }
+    }
+
+    @UsedForTesting
+    public SparseTable(final byte[] lookupTable, final byte[] contentTable, final int blockSize) {
+        mBlockSize = blockSize;
+        mLookupTable = new ArrayList<Integer>(lookupTable.length / 4);
+        mContentTable = new ArrayList<Integer>(contentTable.length / 4);
+        convertByteArrayToIntegerArray(lookupTable, mLookupTable);
+        convertByteArrayToIntegerArray(contentTable, mContentTable);
+    }
+
+    @UsedForTesting
+    public int get(final int index) {
+        if (index < 0 || index / mBlockSize >= mLookupTable.size()
+                || mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
+            return NOT_EXIST;
+        }
+        return mContentTable.get(mLookupTable.get(index / mBlockSize) + (index % mBlockSize));
+    }
+
+    @UsedForTesting
+    public void set(final int index, final int value) {
+        if (mLookupTable.get(index / mBlockSize) == NOT_EXIST) {
+            mLookupTable.set(index / mBlockSize, mContentTable.size());
+            for (int i = 0; i < mBlockSize; ++i) {
+                mContentTable.add(NOT_EXIST);
+            }
+        }
+        mContentTable.set(mLookupTable.get(index / mBlockSize) + (index % mBlockSize), value);
+    }
+
+    public void remove(final int index) {
+        set(index, NOT_EXIST);
+    }
+
+    @UsedForTesting
+    public int size() {
+        return mLookupTable.size() * mBlockSize;
+    }
+
+    @UsedForTesting
+    /* package */ int getContentTableSize() {
+        return mContentTable.size();
+    }
+
+    @UsedForTesting
+    /* package */ int getLookupTableSize() {
+        return mLookupTable.size();
+    }
+
+    public boolean contains(final int index) {
+        return get(index) != NOT_EXIST;
+    }
+
+    @UsedForTesting
+    public void write(final OutputStream lookupOutStream, final OutputStream contentOutStream)
+            throws IOException {
+        for (final int index : mLookupTable) {
+          BinaryDictEncoderUtils.writeUIntToStream(lookupOutStream, index, 4);
+        }
+
+        for (final int index : mContentTable) {
+            BinaryDictEncoderUtils.writeUIntToStream(contentOutStream, index, 4);
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
index 0af028a..3050885 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
@@ -79,7 +79,7 @@
     public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
             final boolean isNotAWord) {
         if (mBigramList.size() > mMaxHistoryBigrams * 2) {
-            // Too many entries: just stop adding new vocabrary and wait next refresh.
+            // Too many entries: just stop adding new vocabulary and wait next refresh.
             return;
         }
         mExpandableDictionary.addWord(word, shortcutTarget, frequency);
@@ -90,7 +90,7 @@
     public void addBigramWords(final String word0, final String word1, final int frequency,
             final boolean isValid, final long lastModifiedTime) {
         if (mBigramList.size() > mMaxHistoryBigrams * 2) {
-            // Too many entries: just stop adding new vocabrary and wait next refresh.
+            // Too many entries: just stop adding new vocabulary and wait next refresh.
             return;
         }
         if (lastModifiedTime > 0) {
@@ -176,8 +176,8 @@
     }
 
     @UsedForTesting
-    public boolean isInDictionaryForTests(final String word) {
+    public boolean isInBigramListForTests(final String word) {
         // TODO: Use native method to determine whether the word is in dictionary or not
-        return mBigramList.containsKey(word);
+        return mBigramList.containsKey(word) || mBigramList.getBigrams(null).containsKey(word);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
index 5dc0b58..201a70d 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -16,8 +16,11 @@
 
 package com.android.inputmethod.latin.utils;
 
-import java.util.ArrayDeque;
 import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * An object that executes submitted tasks using a thread.
@@ -27,19 +30,20 @@
 
     private final Object mLock = new Object();
 
-    // The default value of capacities of task queues.
-    private static final int TASK_QUEUE_CAPACITY = 1000;
     private final Queue<Runnable> mTasks;
     private final Queue<Runnable> mPrioritizedTasks;
     private boolean mIsShutdown;
+    private final ThreadPoolExecutor mThreadPoolExecutor;
 
     // The task which is running now.
     private Runnable mActive;
 
     public PrioritizedSerialExecutor() {
-        mTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
-        mPrioritizedTasks = new ArrayDeque<Runnable>(TASK_QUEUE_CAPACITY);
+        mTasks = new ConcurrentLinkedQueue<Runnable>();
+        mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>();
         mIsShutdown = false;
+        mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
+                0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
     }
 
     /**
@@ -59,7 +63,16 @@
     public void execute(final Runnable r) {
         synchronized(mLock) {
             if (!mIsShutdown) {
-                mTasks.offer(r);
+                mTasks.offer(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            r.run();
+                        } finally {
+                            scheduleNext();
+                        }
+                    }
+                });
                 if (mActive == null) {
                     scheduleNext();
                 }
@@ -74,45 +87,36 @@
     public void executePrioritized(final Runnable r) {
         synchronized(mLock) {
             if (!mIsShutdown) {
-                mPrioritizedTasks.offer(r);
-                if (mActive ==  null) {
+                mPrioritizedTasks.offer(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            r.run();
+                        } finally {
+                            scheduleNext();
+                        }
+                    }
+                });
+                if (mActive == null) {
                     scheduleNext();
                 }
             }
         }
     }
 
-    private boolean fetchNextTasks() {
-        synchronized(mLock) {
-            mActive = mPrioritizedTasks.poll();
-            if (mActive == null) {
-                mActive = mTasks.poll();
-            }
-            return mActive != null;
+    private boolean fetchNextTasksLocked() {
+        mActive = mPrioritizedTasks.poll();
+        if (mActive == null) {
+            mActive = mTasks.poll();
         }
+        return mActive != null;
     }
 
     private void scheduleNext() {
         synchronized(mLock) {
-            if (!fetchNextTasks()) {
-                return;
+            if (fetchNextTasksLocked()) {
+                mThreadPoolExecutor.execute(mActive);
             }
-            new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        do {
-                            synchronized(mLock) {
-                                if (mActive != null) {
-                                    mActive.run();
-                                }
-                            }
-                        } while (fetchNextTasks());
-                    } finally {
-                        scheduleNext();
-                    }
-                }
-            }).start();
         }
     }
 
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index b6aa858..9f0a331 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -74,7 +74,8 @@
         // Note: min() required since length can be MAX_POINT_TO_KEY_LENGTH for characters not on
         // the keyboard (like accented letters)
         const float normalizedSquaredLength = traverseSession->getProximityInfoState(0)
-                ->getPointToKeyLength(pointIndex, dicNode->getNodeCodePoint());
+                ->getPointToKeyLength(pointIndex,
+                        CharUtils::toBaseLowerCase(dicNode->getNodeCodePoint()));
         const float normalizedDistance = TouchPositionCorrectionUtils::getSweetSpotFactor(
                 traverseSession->isTouchPositionCorrectionEnabled(), normalizedSquaredLength);
         const float weightedDistance = ScoringParams::DISTANCE_WEIGHT_LENGTH * normalizedDistance;
@@ -113,10 +114,10 @@
         const int16_t parentPointIndex = parentDicNode->getInputIndex(0);
         const int prevCodePoint = parentDicNode->getNodeCodePoint();
         const float distance1 = traverseSession->getProximityInfoState(0)->getPointToKeyLength(
-                parentPointIndex + 1, prevCodePoint);
+                parentPointIndex + 1, CharUtils::toBaseLowerCase(prevCodePoint));
         const int codePoint = dicNode->getNodeCodePoint();
         const float distance2 = traverseSession->getProximityInfoState(0)->getPointToKeyLength(
-                parentPointIndex, codePoint);
+                parentPointIndex, CharUtils::toBaseLowerCase(codePoint));
         const float distance = distance1 + distance2;
         const float weightedLengthDistance =
                 distance * ScoringParams::DISTANCE_WEIGHT_LENGTH;
@@ -133,7 +134,7 @@
         const bool existsAdjacentProximityChars = traverseSession->getProximityInfoState(0)
                 ->existsAdjacentProximityChars(insertedPointIndex);
         const float dist = traverseSession->getProximityInfoState(0)->getPointToKeyLength(
-                insertedPointIndex + 1, dicNode->getNodeCodePoint());
+                insertedPointIndex + 1, CharUtils::toBaseLowerCase(dicNode->getNodeCodePoint()));
         const float weightedDistance = dist * ScoringParams::DISTANCE_WEIGHT_LENGTH;
         const bool singleChar = dicNode->getNodeCodePointCount() == 1;
         float cost = (singleChar ? ScoringParams::INSERTION_COST_FIRST_CHAR : 0.0f);
diff --git a/tests/src/com/android/inputmethod/latin/makedict/SparseTableTests.java b/tests/src/com/android/inputmethod/latin/makedict/SparseTableTests.java
new file mode 100644
index 0000000..132483d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/makedict/SparseTableTests.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * Unit tests for SparseTable.
+ */
+@LargeTest
+public class SparseTableTests extends AndroidTestCase {
+    private static final String TAG = SparseTableTests.class.getSimpleName();
+
+    private static final int[] SMALL_INDEX = { SparseTable.NOT_EXIST, 0 };
+    private static final int[] BIG_INDEX = { SparseTable.NOT_EXIST, 1, 2, 3, 4, 5, 6, 7};
+
+    private final Random mRandom;
+    private final ArrayList<Integer> mRandomIndex;
+
+    private static final int DEFAULT_SIZE = 10000;
+    private static final int BLOCK_SIZE = 8;
+
+    public SparseTableTests() {
+        this(System.currentTimeMillis(), DEFAULT_SIZE);
+    }
+
+    public SparseTableTests(final long seed, final int tableSize) {
+        super();
+        Log.d(TAG, "Seed for test is " + seed + ", size is " + tableSize);
+        mRandom = new Random(seed);
+        mRandomIndex = new ArrayList<Integer>(tableSize);
+        for (int i = 0; i < tableSize; ++i) {
+            mRandomIndex.add(SparseTable.NOT_EXIST);
+        }
+    }
+
+    public void testInitializeWithArray() {
+        final SparseTable table = new SparseTable(SMALL_INDEX, BIG_INDEX, BLOCK_SIZE);
+        for (int i = 0; i < 8; ++i) {
+            assertEquals(SparseTable.NOT_EXIST, table.get(i));
+        }
+        assertEquals(SparseTable.NOT_EXIST, table.get(8));
+        for (int i = 9; i < 16; ++i) {
+            assertEquals(i - 8, table.get(i));
+        }
+    }
+
+    public void testSet() {
+        final SparseTable table = new SparseTable(16, BLOCK_SIZE);
+        table.set(3, 6);
+        table.set(8, 16);
+        for (int i = 0; i < 16; ++i) {
+            if (i == 3 || i == 8) {
+                assertEquals(i * 2, table.get(i));
+            } else {
+                assertEquals(SparseTable.NOT_EXIST, table.get(i));
+            }
+        }
+    }
+
+    private void generateRandomIndex(final int size, final int prop) {
+        for (int i = 0; i < DEFAULT_SIZE; ++i) {
+            if (mRandom.nextInt(100) < prop) {
+                mRandomIndex.set(i, mRandom.nextInt());
+            } else {
+                mRandomIndex.set(i, SparseTable.NOT_EXIST);
+            }
+        }
+    }
+
+    private void runTestRandomSet() {
+        final SparseTable table = new SparseTable(DEFAULT_SIZE, BLOCK_SIZE);
+        int elementCount = 0;
+        for (int i = 0; i < DEFAULT_SIZE; ++i) {
+            if (mRandomIndex.get(i) != SparseTable.NOT_EXIST) {
+                table.set(i, mRandomIndex.get(i));
+                elementCount++;
+            }
+        }
+
+        Log.d(TAG, "table size = " + table.getLookupTableSize() + " + "
+              + table.getContentTableSize());
+        Log.d(TAG, "the table has " + elementCount + " elements");
+        for (int i = 0; i < DEFAULT_SIZE; ++i) {
+            assertEquals(table.get(i), (int)mRandomIndex.get(i));
+        }
+
+        // flush and reload
+        OutputStream lookupOutStream = null;
+        OutputStream contentOutStream = null;
+        InputStream lookupInStream = null;
+        InputStream contentInStream = null;
+        try {
+            final File lookupIndexFile = File.createTempFile("testRandomSet", ".small");
+            final File contentFile = File.createTempFile("testRandomSet", ".big");
+            lookupOutStream = new FileOutputStream(lookupIndexFile);
+            contentOutStream = new FileOutputStream(contentFile);
+            table.write(lookupOutStream, contentOutStream);
+            lookupInStream = new FileInputStream(lookupIndexFile);
+            contentInStream = new FileInputStream(contentFile);
+            final byte[] lookupArray = new byte[(int) lookupIndexFile.length()];
+            final byte[] contentArray = new byte[(int) contentFile.length()];
+            lookupInStream.read(lookupArray);
+            contentInStream.read(contentArray);
+            final SparseTable newTable = new SparseTable(lookupArray, contentArray, BLOCK_SIZE);
+            for (int i = 0; i < DEFAULT_SIZE; ++i) {
+                assertEquals(table.get(i), newTable.get(i));
+            }
+        } catch (IOException e) {
+            Log.d(TAG, "IOException while flushing and realoding", e);
+        } finally {
+            if (lookupOutStream != null) {
+                try {
+                    lookupOutStream.close();
+                } catch (IOException e) {
+                    Log.d(TAG, "IOException while closing the stream", e);
+                }
+            }
+            if (contentOutStream != null) {
+                try {
+                    contentOutStream.close();
+                } catch (IOException e) {
+                    Log.d(TAG, "IOException while closing contentStream.", e);
+                }
+            }
+        }
+    }
+
+    public void testRandomSet() {
+        for (int i = 0; i <= 100; i += 10) {
+            generateRandomIndex(DEFAULT_SIZE, i);
+            runTestRandomSet();
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index d605cdb..06c4271 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -84,29 +84,24 @@
     }
 
     /**
-     * @param checksContents if true, checks whether written words are actually in the dictionary
+     * @param checkContents if true, checks whether written words are actually in the dictionary
      * or not.
      */
     private void addAndWriteRandomWords(final String testFilenameSuffix, final int numberOfWords,
-            final Random random, final boolean checksContents) {
+            final Random random, final boolean checkContents) {
         final List<String> words = generateWords(numberOfWords, random);
         final UserHistoryPredictionDictionary dict =
                 PersonalizationHelper.getUserHistoryPredictionDictionary(getContext(),
                         testFilenameSuffix /* locale */, mPrefs);
         // Add random words to the user history dictionary.
         addToDict(dict, words);
-        if (checksContents) {
+        if (checkContents) {
             try {
                 Thread.sleep(TimeUnit.MILLISECONDS.convert(5L, TimeUnit.SECONDS));
             } catch (InterruptedException e) {
             }
-            // Limit word count to check when using a Java on memory dictionary.
-            final int wordCountToCheck =
-                    ExpandableBinaryDictionary.ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
-                            numberOfWords : 10;
-            for (int i = 0; i < wordCountToCheck; ++i) {
+            for (int i = 0; i < numberOfWords; ++i) {
                 final String word = words.get(i);
-                // This may fail as long as we use tryLock on inserting the bigram words
                 assertTrue(dict.isInDictionaryForTests(word));
             }
         }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
index fa80385..6c4cbcf 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
@@ -185,7 +185,7 @@
                     crash(filename, new RuntimeException(
                             filename + " does not seem to be a dictionary file"));
                 } else {
-                    final DictDecoder dictDecoder = FormatSpec.getDictDecoder(file,
+                    final DictDecoder dictDecoder = FormatSpec.getDictDecoder(decodedSpec.mFile,
                             DictDecoder.USE_BYTEARRAY);
                     if (report) {
                         System.out.println("Format : Binary dictionary format");