Merge "Have ver4 support decaying dictionary"
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index fd29698..51ae636 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -57,6 +57,8 @@
     @UsedForTesting
     public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
 
+    private static final int NOT_A_VALID_TIME_STAMP = -1;
+
     private long mNativeDict;
     private final Locale mLocale;
     private final long mDictSize;
@@ -133,10 +135,14 @@
             int[] outputAutoCommitFirstWordConfidence);
     private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
     private static native int editDistanceNative(int[] before, int[] after);
-    private static native void addUnigramWordNative(long dict, int[] word, int probability);
+    private static native void addUnigramWordNative(long dict, int[] word, int probability,
+            int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
+            boolean isBlacklisted, int timeStamp);
     private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
-            int probability);
+            int probability, int timeStamp);
     private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
+    private static native int addMultipleDictionaryEntriesNative(long dict,
+            LanguageModelParam[] languageModelParams, int startIndex);
     private static native int calculateProbabilityNative(long dict, int unigramProbability,
             int bigramProbability);
     private static native String getPropertyNative(long dict, String query);
@@ -280,7 +286,10 @@
             return;
         }
         final int[] codePoints = StringUtils.toCodePointArray(word);
-        addUnigramWordNative(mNativeDict, codePoints, probability);
+        final int[] shortcutTarget = new int[0];
+        addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTarget,
+                NOT_A_PROBABILITY, false /* isNotAWord */, false /* isBlacklisted */,
+                NOT_A_VALID_TIME_STAMP);
     }
 
     // Add a bigram entry to binary dictionary in native code.
@@ -290,7 +299,8 @@
         }
         final int[] codePoints0 = StringUtils.toCodePointArray(word0);
         final int[] codePoints1 = StringUtils.toCodePointArray(word1);
-        addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
+        addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability,
+                NOT_A_VALID_TIME_STAMP);
     }
 
     // Remove a bigram entry form binary dictionary in native code.
@@ -303,6 +313,61 @@
         removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
     }
 
+    public static class LanguageModelParam {
+        public final int[] mWord0;
+        public final int[] mWord1;
+        public final int[] mShortcutTarget;
+        public final int mUnigramProbability;
+        public final int mBigramProbability;
+        public final int mShortcutProbability;
+        public final boolean mIsNotAWord;
+        public final boolean mIsBlacklisted;
+        public final int mTimeStamp;
+
+        // Constructor for unigram.
+        public LanguageModelParam(final String word, final int unigramProbability) {
+            mWord0 = null;
+            mWord1 = StringUtils.toCodePointArray(word);
+            mShortcutTarget = null;
+            mUnigramProbability = unigramProbability;
+            mBigramProbability = NOT_A_PROBABILITY;
+            mShortcutProbability = NOT_A_PROBABILITY;
+            mIsNotAWord = false;
+            mIsBlacklisted = false;
+            mTimeStamp = NOT_A_VALID_TIME_STAMP;
+        }
+
+        // Constructor for unigram and bigram.
+        public LanguageModelParam(final String word0, final String word1,
+                final int unigramProbability, final int bigramProbability) {
+            mWord0 = StringUtils.toCodePointArray(word0);
+            mWord1 = StringUtils.toCodePointArray(word1);
+            mShortcutTarget = null;
+            mUnigramProbability = unigramProbability;
+            mBigramProbability = bigramProbability;
+            mShortcutProbability = NOT_A_PROBABILITY;
+            mIsNotAWord = false;
+            mIsBlacklisted = false;
+            mTimeStamp = NOT_A_VALID_TIME_STAMP;
+        }
+    }
+
+    public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
+        if (!isValidDictionary()) return;
+        int processedParamCount = 0;
+        while (processedParamCount < languageModelParams.length) {
+            if (needsToRunGC(true /* mindsBlockByGC */)) {
+                flushWithGC();
+            }
+            processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
+                    languageModelParams, processedParamCount);
+            if (processedParamCount <= 0) {
+                return;
+            }
+        }
+
+    }
+
     private void reopen() {
         close();
         final File dictFile = new File(mDictFilePath);
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index a0e4e54..25aa5e5 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -22,6 +22,7 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
@@ -326,7 +327,7 @@
      * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
      */
     protected void addBigramDynamically(final String word0, final String word1,
-            final int frequency, final boolean isValid) {
+            final int frequency) {
         if (!mIsUpdatable) {
             Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: "
                     + mFilename);
@@ -363,22 +364,6 @@
         public void onFinished();
     }
 
-    public static class LanguageModelParam {
-        public final String mWord0;
-        public final String mWord1;
-        public final boolean mIsValid;
-        public final int mFrequency;
-        public final int mBigramFrequency;
-        public LanguageModelParam(final String word0, final String word1, final boolean isValid,
-                final int frequency, final int bigramFrequency) {
-            mWord0 = word0;
-            mWord1 = word1;
-            mIsValid = isValid;
-            mFrequency = frequency;
-            mBigramFrequency = bigramFrequency;
-        }
-    }
-
     /**
      * Dynamically add multiple entries to the dictionary.
      */
@@ -395,21 +380,9 @@
             public void run() {
                 final boolean locked = setProcessingLargeTaskIfNot();
                 try {
-                    for (final LanguageModelParam languageModelParam : languageModelParams) {
-                        if (languageModelParam.mWord1 == null) {
-                            continue;
-                        }
-                        if (mBinaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
-                            mBinaryDictionary.flushWithGC();
-                        }
-                        mBinaryDictionary.addUnigramWord(languageModelParam.mWord1,
-                                languageModelParam.mFrequency);
-                        if (languageModelParam.mWord0 != null
-                                && !languageModelParam.mWord0.equals(languageModelParam.mWord1)) {
-                            mBinaryDictionary.addBigramWords(languageModelParam.mWord0,
-                                    languageModelParam.mWord1, languageModelParam.mBigramFrequency);
-                        }
-                    }
+                    mBinaryDictionary.addMultipleDictionaryEntries(
+                            languageModelParams.toArray(
+                                    new LanguageModelParam[languageModelParams.size()]));
                 } finally {
                     if (callback != null) {
                         callback.onFinished();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 3dd0a4f..59d0e9f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1803,6 +1803,7 @@
             mSpaceState = SPACE_STATE_PHANTOM;
         }
         mConnection.endBatchEdit();
+        mKeyboardSwitcher.updateShiftState();
         mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode());
     }
 
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 85b74cc..379eaaa 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -32,6 +32,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
@@ -57,23 +58,34 @@
     private InputMethodSubtype mEmojiSubtype;
     private boolean mIsNetworkConnected;
 
+    private static final String KEYBOARD_MODE = "keyboard";
     // Dummy no language QWERTY subtype. See {@link R.xml.method}.
-    private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
-            R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
-            SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
-                    + SubtypeLocaleUtils.QWERTY
-                    + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
-                    + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
-                    + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
-            false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+    private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
+    private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
+            "KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
+            + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+            + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+            + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+    private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
+            InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+                    R.string.subtype_no_language_qwerty, R.drawable.ic_ime_switcher_dark,
+                    SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+                    EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE,
+                    false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+                    SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE);
     // Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
     // Dummy Emoji subtype. See {@link R.xml.method}.
-    private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE = new InputMethodSubtype(
-            R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
-            SubtypeLocaleUtils.NO_LANGUAGE, "keyboard", "KeyboardLayoutSet="
-                    + SubtypeLocaleUtils.EMOJI + ","
-                    + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
-            false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+    private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
+    private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
+            "KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+            + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+    private static final InputMethodSubtype DUMMY_EMOJI_SUBTYPE =
+            InputMethodSubtypeCompatUtils.newInputMethodSubtype(
+                    R.string.subtype_emoji, R.drawable.ic_ime_switcher_dark,
+                    SubtypeLocaleUtils.NO_LANGUAGE, KEYBOARD_MODE,
+                    EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE,
+                    false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */,
+                    SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE);
 
     static final class NeedsToDisplayLanguage {
         private int mEnabledSubtypeCount;
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 42bd760..bc11601 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
@@ -147,7 +148,7 @@
             return;
         }
         if (null != word0) {
-            addBigramDynamically(word0, word1, frequency, isValid);
+            addBigramDynamically(word0, word1, frequency);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
index 1f46f5b..2ab366b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 
 import java.lang.ref.WeakReference;
@@ -28,24 +29,6 @@
  * dictionary.
  */
 public abstract class PersonalizationDictionaryUpdateSession {
-    /**
-     * This class is a parameter for a new unigram or bigram word which will be added
-     * to the personalization dictionary.
-     */
-    public static class PersonalizationLanguageModelParam {
-        public final String mWord0;
-        public final String mWord1;
-        public final boolean mIsValid;
-        public final int mFrequency;
-        public PersonalizationLanguageModelParam(String word0, String word1, boolean isValid,
-                int frequency) {
-            mWord0 = word0;
-            mWord1 = word1;
-            mIsValid = isValid;
-            mFrequency = frequency;
-        }
-    }
-
     // TODO: Use a dynamic binary dictionary instead
     public WeakReference<PersonalizationDictionary> mDictionary;
     public WeakReference<DecayingExpandableBinaryDictionaryBase> mPredictionDictionary;
@@ -117,7 +100,7 @@
 
     // TODO: Support multi locale.
     public void addMultipleDictionaryEntriesToPersonalizationDictionary(
-            final ArrayList<ExpandableBinaryDictionary.LanguageModelParam> languageModelParams,
+            final ArrayList<LanguageModelParam> languageModelParams,
             final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
         final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
         if (dictionary == null) {
@@ -128,17 +111,4 @@
         }
         dictionary.addMultipleDictionaryEntriesToDictionary(languageModelParams, callback);
     }
-
-    // Bulk import
-    // TODO: Support multi locale to add bigram
-    public void addBigramsToPersonalizationDictionary(
-            final ArrayList<PersonalizationLanguageModelParam> lmParams) {
-        final DecayingExpandableBinaryDictionaryBase dictionary = getPredictionDictionary();
-        if (dictionary == null) {
-            return;
-        }
-        for (final PersonalizationLanguageModelParam lmParam : lmParams) {
-            dictionary.addToDictionary(lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index f841f20..9666dd6 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -60,13 +60,6 @@
             DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
                     || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
 
-    private CheckBoxPreference mVoiceInputKeyPreference;
-    private ListPreference mShowCorrectionSuggestionsPreference;
-    private ListPreference mAutoCorrectionThresholdPreference;
-    private ListPreference mKeyPreviewPopupDismissDelay;
-    // Use bigrams to predict the next word when there is no input for it yet
-    private CheckBoxPreference mBigramPrediction;
-
     private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
         final Preference preference = findPreference(preferenceKey);
         if (preference != null) {
@@ -74,6 +67,15 @@
         }
     }
 
+    private void updateListPreferenceSummaryToCurrentValue(final String prefKey) {
+        // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
+        // KitKat, we need to update the summary programmatically.
+        final ListPreference listPreference = (ListPreference)findPreference(prefKey);
+        final CharSequence entries[] = listPreference.getEntries();
+        final int entryIndex = listPreference.findIndexOfValue(listPreference.getValue());
+        listPreference.setSummary(entries[entryIndex]);
+    }
+
     private static void removePreference(final String preferenceKey, final PreferenceGroup parent) {
         if (parent == null) {
             return;
@@ -106,16 +108,9 @@
         SubtypeLocaleUtils.init(context);
         AudioAndHapticFeedbackManager.init(context);
 
-        mVoiceInputKeyPreference =
-                (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY);
-        mShowCorrectionSuggestionsPreference =
-                (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
         final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
         prefs.registerOnSharedPreferenceChangeListener(this);
 
-        mAutoCorrectionThresholdPreference =
-                (ListPreference) findPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD);
-        mBigramPrediction = (CheckBoxPreference) findPreference(Settings.PREF_BIGRAM_PREDICTIONS);
         ensureConsistencyOfAutoCorrectionSettings();
 
         final PreferenceGroup generalSettings =
@@ -161,7 +156,7 @@
         final boolean showVoiceKeyOption = res.getBoolean(
                 R.bool.config_enable_show_voice_key_option);
         if (!showVoiceKeyOption) {
-            generalSettings.removePreference(mVoiceInputKeyPreference);
+            removePreference(Settings.PREF_VOICE_INPUT_KEY, generalSettings);
         }
 
         final PreferenceGroup advancedSettings =
@@ -171,26 +166,27 @@
             removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
         }
 
-        mKeyPreviewPopupDismissDelay =
-                (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
         if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) {
             removePreference(Settings.PREF_POPUP_ON, generalSettings);
             removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings);
         } else {
+            // TODO: Cleanup this setup.
+            final ListPreference keyPreviewPopupDismissDelay =
+                    (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
             final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
                     R.integer.config_key_preview_linger_timeout));
-            mKeyPreviewPopupDismissDelay.setEntries(new String[] {
+            keyPreviewPopupDismissDelay.setEntries(new String[] {
                     res.getString(R.string.key_preview_popup_dismiss_no_delay),
                     res.getString(R.string.key_preview_popup_dismiss_default_delay),
             });
-            mKeyPreviewPopupDismissDelay.setEntryValues(new String[] {
+            keyPreviewPopupDismissDelay.setEntryValues(new String[] {
                     "0",
                     popupDismissDelayDefaultValue
             });
-            if (null == mKeyPreviewPopupDismissDelay.getValue()) {
-                mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+            if (null == keyPreviewPopupDismissDelay.getValue()) {
+                keyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
             }
-            mKeyPreviewPopupDismissDelay.setEnabled(
+            keyPreviewPopupDismissDelay.setEnabled(
                     Settings.readKeyPreviewPopupEnabled(prefs, res));
         }
 
@@ -237,20 +233,19 @@
     @Override
     public void onResume() {
         super.onResume();
-        final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
-        if (!isShortcutImeEnabled) {
-            getPreferenceScreen().removePreference(mVoiceInputKeyPreference);
-        }
         final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+        final Resources res = getResources();
+        final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+        setPreferenceEnabled(Settings.PREF_VOICE_INPUT_KEY, isShortcutImeEnabled);
         final CheckBoxPreference showSetupWizardIcon =
                 (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
         if (showSetupWizardIcon != null) {
             showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
         }
-        updateShowCorrectionSuggestionsSummary();
-        updateKeyPreviewPopupDelaySummary();
-        updateColorSchemeSummary(prefs, getResources());
-        updateCustomInputStylesSummary();
+        updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+        updateCustomInputStylesSummary(prefs, res);
     }
 
     @Override
@@ -281,50 +276,26 @@
             LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
         }
         ensureConsistencyOfAutoCorrectionSettings();
-        updateShowCorrectionSuggestionsSummary();
-        updateKeyPreviewPopupDelaySummary();
-        updateColorSchemeSummary(prefs, res);
+        updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
+        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
     }
 
     private void ensureConsistencyOfAutoCorrectionSettings() {
         final String autoCorrectionOff = getResources().getString(
                 R.string.auto_correction_threshold_mode_index_off);
-        final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
-        mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
+        final ListPreference autoCorrectionThresholdPref = (ListPreference)findPreference(
+                Settings.PREF_AUTO_CORRECTION_THRESHOLD);
+        final String currentSetting = autoCorrectionThresholdPref.getValue();
+        setPreferenceEnabled(
+                Settings.PREF_BIGRAM_PREDICTIONS, !currentSetting.equals(autoCorrectionOff));
     }
 
-    private void updateShowCorrectionSuggestionsSummary() {
-        mShowCorrectionSuggestionsPreference.setSummary(
-                getResources().getStringArray(R.array.prefs_suggestion_visibilities)
-                [mShowCorrectionSuggestionsPreference.findIndexOfValue(
-                        mShowCorrectionSuggestionsPreference.getValue())]);
-    }
-
-    private void updateColorSchemeSummary(final SharedPreferences prefs, final Resources res) {
-        // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
-        // KitKat, we need to update the summary by code.
-        final Preference preference = findPreference(Settings.PREF_KEYBOARD_LAYOUT);
-        if (!(preference instanceof ListPreference)) {
-            Log.w(TAG, "Can't find Keyboard Color Scheme preference");
-            return;
-        }
-        final ListPreference colorSchemePreference = (ListPreference)preference;
-        final int themeIndex = Settings.readKeyboardThemeIndex(prefs, res);
-        int entryIndex = colorSchemePreference.findIndexOfValue(Integer.toString(themeIndex));
-        if (entryIndex < 0) {
-            final int defaultThemeIndex = Settings.resetAndGetDefaultKeyboardThemeIndex(prefs, res);
-            entryIndex = colorSchemePreference.findIndexOfValue(
-                    Integer.toString(defaultThemeIndex));
-        }
-        colorSchemePreference.setSummary(colorSchemePreference.getEntries()[entryIndex]);
-    }
-
-    private void updateCustomInputStylesSummary() {
+    private void updateCustomInputStylesSummary(final SharedPreferences prefs,
+            final Resources res) {
         final PreferenceScreen customInputStyles =
                 (PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES);
-        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
-        final Resources res = getResources();
         final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
         final InputMethodSubtype[] subtypes =
                 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
@@ -336,13 +307,6 @@
         customInputStyles.setSummary(styles);
     }
 
-    private void updateKeyPreviewPopupDelaySummary() {
-        final ListPreference lp = mKeyPreviewPopupDismissDelay;
-        final CharSequence[] entries = lp.getEntries();
-        if (entries == null || entries.length <= 0) return;
-        lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
-    }
-
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
             final SharedPreferences sp, final Resources res) {
         setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index db0c189..5fc20ea 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -277,7 +277,8 @@
 }
 
 static void latinime_BinaryDictionary_addUnigramWord(JNIEnv *env, jclass clazz, jlong dict,
-        jintArray word, jint probability) {
+        jintArray word, jint probability, jintArray shortcutTarget, jint shortuctProbability,
+        jboolean isNotAWord, jboolean isBlacklisted, jint timeStamp) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) {
         return;
@@ -285,11 +286,16 @@
     jsize wordLength = env->GetArrayLength(word);
     int codePoints[wordLength];
     env->GetIntArrayRegion(word, 0, wordLength, codePoints);
+    jsize shortcutLength = shortcutTarget ? env->GetArrayLength(shortcutTarget) : 0;
+    int shortcutTargetCodePoints[wordLength];
+    if (shortcutTarget) {
+        env->GetIntArrayRegion(shortcutTarget, 0, shortcutLength, shortcutTargetCodePoints);
+    }
     dictionary->addUnigramWord(codePoints, wordLength, probability);
 }
 
 static void latinime_BinaryDictionary_addBigramWords(JNIEnv *env, jclass clazz, jlong dict,
-        jintArray word0, jintArray word1, jint probability) {
+        jintArray word0, jintArray word1, jint probability, jint timeStamp) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) {
         return;
@@ -320,6 +326,65 @@
             word1Length);
 }
 
+
+// Returns how many language model params are processed.
+static int latinime_BinaryDictionary_addMultipleDictionaryEntries(JNIEnv *env, jclass clazz,
+        jlong dict, jobjectArray languageModelParams, jint startIndex) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) {
+        return 0;
+    }
+    jsize languageModelParamCount = env->GetArrayLength(languageModelParams);
+    if (languageModelParamCount == 0 || startIndex >= languageModelParamCount) {
+        return 0;
+    }
+    jobject languageModelParam = env->GetObjectArrayElement(languageModelParams, 0);
+    jclass languageModelParamClass = env->GetObjectClass(languageModelParam);
+    env->DeleteLocalRef(languageModelParam);
+
+    // TODO: Support shortcut, timestamp and flags.
+    jfieldID word0FieldId = env->GetFieldID(languageModelParamClass, "mWord0", "[I");
+    jfieldID word1FieldId = env->GetFieldID(languageModelParamClass, "mWord1", "[I");
+    jfieldID unigramProbabilityFieldId =
+            env->GetFieldID(languageModelParamClass, "mUnigramProbability", "I");
+    jfieldID bigramProbabilityFieldId =
+            env->GetFieldID(languageModelParamClass, "mBigramProbability", "I");
+    env->DeleteLocalRef(languageModelParamClass);
+
+    for (int i = startIndex; i < languageModelParamCount; ++i) {
+        jobject languageModelParam = env->GetObjectArrayElement(languageModelParams, i);
+        // languageModelParam is a set of params for word1; thus, word1 cannot be null. On the
+        // other hand, word0 can be null and then it means the set of params doesn't contain bigram
+        // information.
+        jintArray word0 = static_cast<jintArray>(
+                env->GetObjectField(languageModelParam, word0FieldId));
+        jsize word0Length = word0 ? env->GetArrayLength(word0) : 0;
+        int word0CodePoints[word0Length];
+        if (word0) {
+            env->GetIntArrayRegion(word0, 0, word0Length, word0CodePoints);
+        }
+        jintArray word1 = static_cast<jintArray>(
+                env->GetObjectField(languageModelParam, word1FieldId));
+        jsize word1Length = env->GetArrayLength(word1);
+        int word1CodePoints[word1Length];
+        env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
+        jint unigramProbability = env->GetIntField(languageModelParam, unigramProbabilityFieldId);
+        dictionary->addUnigramWord(word1CodePoints, word1Length, unigramProbability);
+        if (word0) {
+            jint bigramProbability = env->GetIntField(languageModelParam, bigramProbabilityFieldId);
+            dictionary->addBigramWords(word0CodePoints, word0Length, word1CodePoints, word1Length,
+                    bigramProbability);
+        }
+        if (dictionary->needsToRunGC(true /* mindsBlockByGC */)) {
+            return i + 1;
+        }
+        env->DeleteLocalRef(word0);
+        env->DeleteLocalRef(word1);
+        env->DeleteLocalRef(languageModelParam);
+    }
+    return languageModelParamCount;
+}
+
 static int latinime_BinaryDictionary_calculateProbabilityNative(JNIEnv *env, jclass clazz,
         jlong dict, jint unigramProbability, jint bigramProbability) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
@@ -405,12 +470,12 @@
     },
     {
         const_cast<char *>("addUnigramWordNative"),
-        const_cast<char *>("(J[II)V"),
+        const_cast<char *>("(J[II[IIZZI)V"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_addUnigramWord)
     },
     {
         const_cast<char *>("addBigramWordsNative"),
-        const_cast<char *>("(J[I[II)V"),
+        const_cast<char *>("(J[I[III)V"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_addBigramWords)
     },
     {
@@ -419,6 +484,12 @@
         reinterpret_cast<void *>(latinime_BinaryDictionary_removeBigramWords)
     },
     {
+        const_cast<char *>("addMultipleDictionaryEntriesNative"),
+        const_cast<char *>(
+                "(J[Lcom/android/inputmethod/latin/BinaryDictionary$LanguageModelParam;I)I"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_addMultipleDictionaryEntries)
+    },
+    {
         const_cast<char *>("calculateProbabilityNative"),
         const_cast<char *>("(JII)I"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_calculateProbabilityNative)
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index 747bb29..6affe23 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -21,6 +21,7 @@
 import android.text.TextUtils;
 import android.util.Pair;
 
+import com.android.inputmethod.latin.BinaryDictionary.LanguageModelParam;
 import com.android.inputmethod.latin.makedict.CodePointUtils;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 
@@ -33,6 +34,7 @@
 import java.util.Map;
 import java.util.Random;
 
+// TODO Use the seed passed as an argument for makedict test.
 @LargeTest
 public class BinaryDictionaryTests extends AndroidTestCase {
     private static final String TEST_DICT_FILE_EXTENSION = ".testDict";
@@ -776,4 +778,66 @@
 
         dictFile.delete();
     }
+
+    public void testAddMultipleDictionaryEntries() {
+        testAddMultipleDictionaryEntries(3 /* formatVersion */);
+        testAddMultipleDictionaryEntries(4 /* formatVersion */);
+    }
+
+    private void testAddMultipleDictionaryEntries(final int formatVersion) {
+        final int codePointSetSize = 20;
+        final int lmParamCount = 1000;
+        final double bigramContinueRate = 0.9;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final HashMap<String, Integer> unigramProbabilities = new HashMap<String, Integer>();
+        final HashMap<Pair<String, String>, Integer> bigramProbabilities =
+                new HashMap<Pair<String, String>, Integer>();
+
+        final LanguageModelParam[] languageModelParams = new LanguageModelParam[lmParamCount];
+        String prevWord = null;
+        for (int i = 0; i < languageModelParams.length; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            final int probability = random.nextInt(0xFF);
+            final int bigramProbability = random.nextInt(0xF);
+            unigramProbabilities.put(word, probability);
+            if (prevWord == null) {
+                languageModelParams[i] = new LanguageModelParam(word, probability);
+            } else {
+                languageModelParams[i] = new LanguageModelParam(prevWord, word, probability,
+                        bigramProbability);
+                bigramProbabilities.put(new Pair<String, String>(prevWord, word),
+                        bigramProbability);
+            }
+            prevWord = (random.nextDouble() < bigramContinueRate) ? word : null;
+        }
+
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        binaryDictionary.addMultipleDictionaryEntries(languageModelParams);
+
+        for (Map.Entry<String, Integer> entry : unigramProbabilities.entrySet()) {
+            assertEquals((int)entry.getValue(), binaryDictionary.getFrequency(entry.getKey()));
+        }
+
+        for (Map.Entry<Pair<String, String>, Integer> entry : bigramProbabilities.entrySet()) {
+            final String word0 = entry.getKey().first;
+            final String word1 = entry.getKey().second;
+            final int unigramProbability = unigramProbabilities.get(word1);
+            final int bigramProbability = entry.getValue();
+            final int probability = binaryDictionary.calculateProbability(
+                    unigramProbability, bigramProbability);
+            assertEquals(probability, binaryDictionary.getBigramProbability(word0, word1));
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 6306571..5e7a9e6 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -45,6 +45,10 @@
 public class InputTestsBase extends ServiceTestCase<LatinIMEForTests> {
 
     private static final String PREF_DEBUG_MODE = "debug_mode";
+    private static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
+    // Default value for auto-correction threshold. This is the string representation of the
+    // index in the resources array of auto-correction threshold settings.
+    private static final String DEFAULT_AUTO_CORRECTION_THRESHOLD = "1";
 
     // The message that sets the underline is posted with a 500 ms delay
     protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 500;
@@ -56,6 +60,8 @@
     protected MyEditText mEditText;
     protected View mInputView;
     protected InputConnection mInputConnection;
+    private boolean mPreviousDebugSetting;
+    private String mPreviousAutoCorrectSetting;
 
     // A helper class to ease span tests
     public static class SpanGetter {
@@ -137,7 +143,17 @@
         final boolean previousSetting = prefs.getBoolean(key, defaultValue);
         final SharedPreferences.Editor editor = prefs.edit();
         editor.putBoolean(key, value);
-        editor.commit();
+        editor.apply();
+        return previousSetting;
+    }
+
+    protected String setStringPreference(final String key, final String value,
+            final String defaultValue) {
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
+        final String previousSetting = prefs.getString(key, defaultValue);
+        final SharedPreferences.Editor editor = prefs.edit();
+        editor.putString(key, value);
+        editor.apply();
         return previousSetting;
     }
 
@@ -156,9 +172,10 @@
         mEditText.setEnabled(true);
         setupService();
         mLatinIME = getService();
-        final boolean previousDebugSetting = setDebugMode(true);
+        mPreviousDebugSetting = setDebugMode(true);
+        mPreviousAutoCorrectSetting = setStringPreference(PREF_AUTO_CORRECTION_THRESHOLD,
+                DEFAULT_AUTO_CORRECTION_THRESHOLD, DEFAULT_AUTO_CORRECTION_THRESHOLD);
         mLatinIME.onCreate();
-        setDebugMode(previousDebugSetting);
         final EditorInfo ei = new EditorInfo();
         final InputConnection ic = mEditText.onCreateInputConnection(ei);
         final LayoutInflater inflater =
@@ -174,6 +191,13 @@
         changeLanguage("en_US");
     }
 
+    @Override
+    protected void tearDown() {
+        setStringPreference(PREF_AUTO_CORRECTION_THRESHOLD, mPreviousAutoCorrectSetting,
+                DEFAULT_AUTO_CORRECTION_THRESHOLD);
+        setDebugMode(mPreviousDebugSetting);
+    }
+
     // We need to run the messages added to the handler from LatinIME. The only way to do
     // that is to call Looper#loop() on the right looper, so we're going to get the looper
     // object and call #loop() here. The messages in the handler actually run on the UI
@@ -255,7 +279,7 @@
                     + SubtypeLocaleUtils.QWERTY
                     + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
                     + "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
-                    + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
+                    + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE,
                     false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
         SubtypeSwitcher.getInstance().forceSubtype(subtype);
         mLatinIME.loadKeyboard();