Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index c0fee73..2e6489c 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 591292c..0b14822 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index 10d91a2..4bf38fc 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index 6a8c62f..ea12c77 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index 3ce13cc..9f244f2 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 65507ee..c5b3fbb 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index 724e142..66824cf 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index 264b65b..527dfd0 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index 70270e2..98c085b 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 31f9e02..f0c1328 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index 97ef98d..a2b17ba 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index a6e52ff..99ff0af 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index 8147586..4ec5864 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 598bb68..6b5c0c0 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index 963f34a..6fd8eed 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index b40d6f4..9132288 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/values-v20/platform-theme.xml b/java/res/values-v20/platform-theme.xml
index 0606204..52e7f35 100644
--- a/java/res/values-v20/platform-theme.xml
+++ b/java/res/values-v20/platform-theme.xml
@@ -21,6 +21,6 @@
 <!-- TODO: This file is temporarily placed under values-v20. -->
 <!-- TODO: It might be moved under values-v21. -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <style name="platformActivityTheme" parent="@android:style/Theme.Quantum.Light" />
-    <style name="platformDialogTheme" parent="@android:style/Theme.Quantum.Light.Dialog" />
+    <style name="platformActivityTheme" parent="@android:style/Theme.Material.Light" />
+    <style name="platformDialogTheme" parent="@android:style/Theme.Material.Light.Dialog" />
 </resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 775a90c..a1f478b 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -437,7 +437,6 @@
         <attr name="navigatePrevious" format="boolean" />
         <attr name="passwordInput" format="boolean" />
         <attr name="clobberSettingsKey" format="boolean" />
-        <attr name="supportsSwitchingToShortcutIme" format="boolean" />
         <attr name="hasShortcutKey" format="boolean" />
         <attr name="languageSwitchKeyEnabled" format="boolean" />
         <attr name="isMultiLine" format="boolean" />
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 93a55fe..3c11675 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -70,7 +70,6 @@
     public final int mElementId;
     public final EditorInfo mEditorInfo;
     public final boolean mClobberSettingsKey;
-    public final boolean mSupportsSwitchingToShortcutIme;
     public final boolean mLanguageSwitchKeyEnabled;
     public final String mCustomActionLabel;
     public final boolean mHasShortcutKey;
@@ -86,11 +85,10 @@
         mElementId = elementId;
         mEditorInfo = params.mEditorInfo;
         mClobberSettingsKey = params.mNoSettingsKey;
-        mSupportsSwitchingToShortcutIme = params.mSupportsSwitchingToShortcutIme;
         mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled;
         mCustomActionLabel = (mEditorInfo.actionLabel != null)
                 ? mEditorInfo.actionLabel.toString() : null;
-        mHasShortcutKey = mSupportsSwitchingToShortcutIme && params.mShowsVoiceInputKey;
+        mHasShortcutKey = params.mVoiceInputKeyEnabled;
 
         mHashCode = computeHashCode(this);
     }
@@ -103,7 +101,6 @@
                 id.mHeight,
                 id.passwordInput(),
                 id.mClobberSettingsKey,
-                id.mSupportsSwitchingToShortcutIme,
                 id.mHasShortcutKey,
                 id.mLanguageSwitchKeyEnabled,
                 id.isMultiLine(),
@@ -124,7 +121,6 @@
                 && other.mHeight == mHeight
                 && other.passwordInput() == passwordInput()
                 && other.mClobberSettingsKey == mClobberSettingsKey
-                && other.mSupportsSwitchingToShortcutIme == mSupportsSwitchingToShortcutIme
                 && other.mHasShortcutKey == mHasShortcutKey
                 && other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled
                 && other.isMultiLine() == isMultiLine()
@@ -179,7 +175,7 @@
 
     @Override
     public String toString() {
-        return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]",
+        return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
                 mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
                 mWidth, mHeight,
@@ -189,7 +185,6 @@
                 (navigatePrevious() ? " navigatePrevious" : ""),
                 (mClobberSettingsKey ? " clobberSettingsKey" : ""),
                 (passwordInput() ? " passwordInput" : ""),
-                (mSupportsSwitchingToShortcutIme ? " supportsSwitchingToShortcutIme" : ""),
                 (mHasShortcutKey ? " hasShortcutKey" : ""),
                 (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
                 (isMultiLine() ? " isMultiLine" : "")
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index aa64699..3e5cfc1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -17,8 +17,6 @@
 package com.android.inputmethod.keyboard;
 
 import static com.android.inputmethod.latin.Constants.ImeOption.FORCE_ASCII;
-import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
-import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
 import static com.android.inputmethod.latin.Constants.ImeOption.NO_SETTINGS_KEY;
 
 import android.content.Context;
@@ -102,12 +100,11 @@
     public static final class Params {
         String mKeyboardLayoutSetName;
         int mMode;
-        EditorInfo mEditorInfo;
         boolean mDisableTouchPositionCorrectionDataForTest;
+        // TODO: Use {@link InputAttributes} instead of these variables.
+        EditorInfo mEditorInfo;
         boolean mIsPasswordField;
-        boolean mSupportsSwitchingToShortcutIme;
-        boolean mShowsVoiceInputKey;
-        boolean mNoMicrophoneKey;
+        boolean mVoiceInputKeyEnabled;
         boolean mNoSettingsKey;
         boolean mLanguageSwitchKeyEnabled;
         InputMethodSubtype mSubtype;
@@ -228,14 +225,9 @@
 
             final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO;
             params.mMode = getKeyboardMode(editorInfo);
+            // TODO: Consolidate those with {@link InputAttributes}.
             params.mEditorInfo = editorInfo;
             params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
-            @SuppressWarnings("deprecation")
-            final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
-                    null, NO_MICROPHONE_COMPAT, editorInfo);
-            params.mNoMicrophoneKey = InputAttributes.inPrivateImeOptions(
-                    mPackageName, NO_MICROPHONE, editorInfo)
-                    || deprecatedNoMicrophone;
             params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
                     mPackageName, NO_SETTINGS_KEY, editorInfo);
         }
@@ -248,6 +240,7 @@
 
         public Builder setSubtype(final InputMethodSubtype subtype) {
             final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype);
+            // TODO: Consolidate with {@link InputAttributes}.
             @SuppressWarnings("deprecation")
             final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
                     mPackageName, FORCE_ASCII, mParams.mEditorInfo);
@@ -268,12 +261,13 @@
             return this;
         }
 
-        public Builder setOptions(final boolean isShortcutImeEnabled,
-                final boolean showsVoiceInputKey, final boolean languageSwitchKeyEnabled) {
-            mParams.mSupportsSwitchingToShortcutIme =
-                    isShortcutImeEnabled && !mParams.mNoMicrophoneKey && !mParams.mIsPasswordField;
-            mParams.mShowsVoiceInputKey = showsVoiceInputKey;
-            mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
+        public Builder setVoiceInputKeyEnabled(final boolean enabled) {
+            mParams.mVoiceInputKeyEnabled = enabled;
+            return this;
+        }
+
+        public Builder setLanguageSwitchKeyEnabled(final boolean enabled) {
+            mParams.mLanguageSwitchKeyEnabled = enabled;
             return this;
         }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ef5fabb..6aeff18 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -115,10 +115,8 @@
         final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
         builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
         builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
-        builder.setOptions(
-                mSubtypeSwitcher.isShortcutImeEnabled(),
-                settingsValues.mShowsVoiceInputKey,
-                mLatinIME.shouldShowLanguageSwitchKey());
+        builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey);
+        builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey());
         mKeyboardLayoutSet = builder.build();
         mCurrentSettingsValues = settingsValues;
         try {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index 60d9a05..0ea9c74 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -21,46 +21,43 @@
 import android.os.Build.VERSION_CODES;
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.R;
 
 import java.util.Arrays;
-import java.util.Comparator;
 
-public final class KeyboardTheme {
+public final class KeyboardTheme implements Comparable<KeyboardTheme> {
     private static final String TAG = KeyboardTheme.class.getSimpleName();
 
     static final String KLP_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916";
     static final String LXX_KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
 
-    static final int THEME_ID_ICS = 0;
-    static final int THEME_ID_KLP = 2;
-    static final int THEME_ID_LXX_DARK = 3;
-    static final int DEFAULT_THEME_ID = THEME_ID_KLP;
+    public static final int THEME_ID_ICS = 0;
+    public static final int THEME_ID_KLP = 2;
+    public static final int THEME_ID_LXX_DARK = 3;
+    public static final int DEFAULT_THEME_ID = THEME_ID_KLP;
 
     private static final KeyboardTheme[] KEYBOARD_THEMES = {
         new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS,
+                // This has never been selected because we support ICS or later.
                 VERSION_CODES.BASE),
         new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP,
+                // Default theme for ICS, JB, and KLP.
                 VERSION_CODES.ICE_CREAM_SANDWICH),
         new KeyboardTheme(THEME_ID_LXX_DARK, R.style.KeyboardTheme_LXX_Dark,
+                // Default theme for LXX.
                 // TODO: Update this constant once the *next* version becomes available.
                 VERSION_CODES.CUR_DEVELOPMENT),
     };
+
     static {
         // Sort {@link #KEYBOARD_THEME} by descending order of {@link #mMinApiVersion}.
-        Arrays.sort(KEYBOARD_THEMES, new Comparator<KeyboardTheme>() {
-            @Override
-            public int compare(final KeyboardTheme lhs, final KeyboardTheme rhs) {
-                if (lhs.mMinApiVersion > rhs.mMinApiVersion) return -1;
-                if (lhs.mMinApiVersion < rhs.mMinApiVersion) return 1;
-                return 0;
-            }
-        });
+        Arrays.sort(KEYBOARD_THEMES);
     }
 
     public final int mThemeId;
     public final int mStyleId;
-    final int mMinApiVersion;
+    private final int mMinApiVersion;
 
     // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
     // in values/themes-<style>.xml.
@@ -71,6 +68,13 @@
     }
 
     @Override
+    public int compareTo(final KeyboardTheme rhs) {
+        if (mMinApiVersion > rhs.mMinApiVersion) return -1;
+        if (mMinApiVersion < rhs.mMinApiVersion) return 1;
+        return 0;
+    }
+
+    @Override
     public boolean equals(final Object o) {
         if (o == this) return true;
         return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId;
@@ -81,7 +85,8 @@
         return mThemeId;
     }
 
-    private static KeyboardTheme searchKeyboardThemeById(final int themeId) {
+    @UsedForTesting
+    static KeyboardTheme searchKeyboardThemeById(final int themeId) {
         // TODO: This search algorithm isn't optimal if there are many themes.
         for (final KeyboardTheme theme : KEYBOARD_THEMES) {
             if (theme.mThemeId == themeId) {
@@ -100,6 +105,7 @@
         return sdkVersion;
     }
 
+    @UsedForTesting
     static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
             final int sdkVersion) {
         final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null);
@@ -134,6 +140,7 @@
         saveKeyboardThemeId(themeIdString, prefs, getSdkVersion());
     }
 
+    @UsedForTesting
     static String getPreferenceKey(final int sdkVersion) {
         if (sdkVersion <= VERSION_CODES.KITKAT) {
             return KLP_KEYBOARD_THEME_KEY;
@@ -141,8 +148,9 @@
         return LXX_KEYBOARD_THEME_KEY;
     }
 
-    static void saveKeyboardThemeId(final String themeIdString, final SharedPreferences prefs,
-            final int sdkVersion) {
+    @UsedForTesting
+    static void saveKeyboardThemeId(final String themeIdString,
+            final SharedPreferences prefs, final int sdkVersion) {
         final String prefKey = getPreferenceKey(sdkVersion);
         prefs.edit().putString(prefKey, themeIdString).apply();
     }
@@ -151,6 +159,7 @@
         return getKeyboardTheme(prefs, getSdkVersion());
     }
 
+    @UsedForTesting
     static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) {
         final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null);
         if (lxxThemeIdString == null) {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index 81d8cda..abed520 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -115,8 +115,6 @@
         builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype());
         builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res),
                 mEmojiLayoutParams.mEmojiKeyboardHeight);
-        builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */,
-                false /* languageSwitchKeyEnabled */);
         final KeyboardLayoutSet layoutSet = builder.build();
         final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
                 R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 2aeeed8..e694998 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -652,9 +652,6 @@
                     R.styleable.Keyboard_Case_passwordInput, id.passwordInput());
             final boolean clobberSettingsKeyMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
-            final boolean supportsSwitchingToShortcutImeMatched = matchBoolean(caseAttr,
-                    R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
-                    id.mSupportsSwitchingToShortcutIme);
             final boolean hasShortcutKeyMatched = matchBoolean(caseAttr,
                     R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
             final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr,
@@ -674,14 +671,13 @@
                     R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
             final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched
                     && modeMatched && navigateNextMatched && navigatePreviousMatched
-                    && passwordInputMatched && clobberSettingsKeyMatched
-                    && supportsSwitchingToShortcutImeMatched && hasShortcutKeyMatched
+                    && passwordInputMatched && clobberSettingsKeyMatched && hasShortcutKeyMatched
                     && languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched
                     && isIconDefinedMatched && localeCodeMatched && languageCodeMatched
                     && countryCodeMatched;
 
             if (DEBUG) {
-                startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+                startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
                         textAttr(caseAttr.getString(
                                 R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
                         textAttr(caseAttr.getString(
@@ -698,9 +694,6 @@
                                 "clobberSettingsKey"),
                         booleanAttr(caseAttr, R.styleable.Keyboard_Case_passwordInput,
                                 "passwordInput"),
-                        booleanAttr(
-                                caseAttr, R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme,
-                                "supportsSwitchingToShortcutIme"),
                         booleanAttr(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey,
                                 "hasShortcutKey"),
                         booleanAttr(caseAttr, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 096b946..7247a1f 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -189,6 +189,7 @@
     private static native void closeNative(long dict);
     private static native int getFormatVersionNative(long dict);
     private static native int getProbabilityNative(long dict, int[] word);
+    private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word);
     private static native int getBigramProbabilityNative(long dict, int[] word0,
             boolean isBeginningOfSentence, int[] word1);
     private static native void getWordPropertyNative(long dict, int[] word,
@@ -350,11 +351,18 @@
 
     @Override
     public int getFrequency(final String word) {
-        if (word == null) return NOT_A_PROBABILITY;
+        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
         int[] codePoints = StringUtils.toCodePointArray(word);
         return getProbabilityNative(mNativeDict, codePoints);
     }
 
+    @Override
+    public int getMaxFrequencyOfExactMatches(final String word) {
+        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
+        int[] codePoints = StringUtils.toCodePointArray(word);
+        return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
+    }
+
     @UsedForTesting
     public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
         return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index aab1665..bc7276b 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -95,6 +95,10 @@
         return NOT_A_PROBABILITY;
     }
 
+    public int getMaxFrequencyOfExactMatches(final String word) {
+        return NOT_A_PROBABILITY;
+    }
+
     /**
      * Compares the contents of the character array with the typed word and returns true if they
      * are the same.
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 53be281..53c78fd 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -89,9 +89,17 @@
         int maxFreq = -1;
         for (int i = mDictionaries.size() - 1; i >= 0; --i) {
             final int tempFreq = mDictionaries.get(i).getFrequency(word);
-            if (tempFreq >= maxFreq) {
-                maxFreq = tempFreq;
-            }
+            maxFreq = Math.max(tempFreq, maxFreq);
+        }
+        return maxFreq;
+    }
+
+    @Override
+    public int getMaxFrequencyOfExactMatches(final String word) {
+        int maxFreq = -1;
+        for (int i = mDictionaries.size() - 1; i >= 0; --i) {
+            final int tempFreq = mDictionaries.get(i).getMaxFrequencyOfExactMatches(word);
+            maxFreq = Math.max(tempFreq, maxFreq);
         }
         return maxFreq;
     }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 7fa3d04..e8b0be0 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -63,7 +63,7 @@
     private final Object mLock = new Object();
     private final DistracterFilter mDistracterFilter;
 
-    private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTION =
+    private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
             new String[] {
                 Dictionary.TYPE_MAIN,
                 Dictionary.TYPE_USER_HISTORY,
@@ -89,8 +89,8 @@
             new Class[] { Context.class, Locale.class, File.class };
 
     private static final String[] SUB_DICT_TYPES =
-            Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTION, 1 /* start */,
-                    DICT_TYPES_ORDERED_TO_GET_SUGGESTION.length);
+            Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
+                    DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
 
     /**
      * Class contains dictionaries for a locale.
@@ -333,7 +333,7 @@
             dictionaries = mDictionaries;
             mDictionaries = new Dictionaries();
         }
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
             dictionaries.closeDict(dictType);
         }
         mDistracterFilter.close();
@@ -469,7 +469,7 @@
         final SuggestionResults suggestionResults =
                 new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS);
         final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
             final Dictionary dictionary = dictionaries.getDict(dictType);
             if (null == dictionary) continue;
             final ArrayList<SuggestedWordInfo> dictionarySuggestions =
@@ -502,7 +502,7 @@
             return false;
         }
         final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
             final Dictionary dictionary = dictionaries.getDict(dictType);
             // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
             // would be immutable once it's finished initializing, but concretely a null test is
@@ -516,16 +516,22 @@
         return false;
     }
 
-    public int getFrequency(final String word) {
+    private int getFrequencyInternal(final String word,
+            final boolean isGettingMaxFrequencyOfExactMatches) {
         if (TextUtils.isEmpty(word)) {
             return Dictionary.NOT_A_PROBABILITY;
         }
-        int maxFreq = -1;
+        int maxFreq = Dictionary.NOT_A_PROBABILITY;
         final Dictionaries dictionaries = mDictionaries;
-        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) {
+        for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
             final Dictionary dictionary = dictionaries.getDict(dictType);
             if (dictionary == null) continue;
-            final int tempFreq = dictionary.getFrequency(word);
+            final int tempFreq;
+            if (isGettingMaxFrequencyOfExactMatches) {
+                tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
+            } else {
+                tempFreq = dictionary.getFrequency(word);
+            }
             if (tempFreq >= maxFreq) {
                 maxFreq = tempFreq;
             }
@@ -533,6 +539,14 @@
         return maxFreq;
     }
 
+    public int getFrequency(final String word) {
+        return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
+    }
+
+    public int getMaxFrequencyOfExactMatches(final String word) {
+        return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
+    }
+
     public void clearUserHistoryDictionary() {
         final ExpandableBinaryDictionary userHistoryDict =
                 mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index b10bae0..8664c09 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -441,6 +441,30 @@
         return mBinaryDictionary.isValidWord(word);
     }
 
+    @Override
+    public int getMaxFrequencyOfExactMatches(final String word) {
+        reloadDictionaryIfRequired();
+        boolean lockAcquired = false;
+        try {
+            lockAcquired = mLock.readLock().tryLock(
+                    TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+            if (lockAcquired) {
+                if (mBinaryDictionary == null) {
+                    return NOT_A_PROBABILITY;
+                }
+                return mBinaryDictionary.getMaxFrequencyOfExactMatches(word);
+            }
+        } catch (final InterruptedException e) {
+            Log.e(TAG, "Interrupted tryLock() in getMaxFrequencyOfExactMatches().", e);
+        } finally {
+            if (lockAcquired) {
+                mLock.readLock().unlock();
+            }
+        }
+        return NOT_A_PROBABILITY;
+    }
+
+
     protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) {
         if (mBinaryDictionary == null) return false;
         return mBinaryDictionary.isValidNgram(prevWordsInfo, word);
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index e778a14..e1ae3df 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.latin;
 
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
+
 import android.text.InputType;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
@@ -35,12 +38,17 @@
     final public String mTargetApplicationPackageName;
     final public boolean mInputTypeNoAutoCorrect;
     final public boolean mIsPasswordField;
-    final public boolean mIsSettingsSuggestionStripOn;
+    final public boolean mShouldShowSuggestions;
     final public boolean mApplicationSpecifiedCompletionOn;
     final public boolean mShouldInsertSpacesAutomatically;
     final private int mInputType;
+    final private EditorInfo mEditorInfo;
+    final private String mPackageNameForPrivateImeOptions;
 
-    public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
+    public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode,
+            final String packageNameForPrivateImeOptions) {
+        mEditorInfo = editorInfo;
+        mPackageNameForPrivateImeOptions = packageNameForPrivateImeOptions;
         mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null;
         final int inputType = null != editorInfo ? editorInfo.inputType : 0;
         final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
@@ -62,7 +70,7 @@
                 Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
                         + " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
             }
-            mIsSettingsSuggestionStripOn = false;
+            mShouldShowSuggestions = false;
             mInputTypeNoAutoCorrect = false;
             mApplicationSpecifiedCompletionOn = false;
             mShouldInsertSpacesAutomatically = false;
@@ -81,13 +89,13 @@
 
         // TODO: Have a helper method in InputTypeUtils
         // Make sure that passwords are not displayed in {@link SuggestionStripView}.
-        final boolean noSuggestionStrip = mIsPasswordField
+        final boolean shouldSuppressSuggestions = mIsPasswordField
                 || InputTypeUtils.isEmailVariation(variation)
                 || InputType.TYPE_TEXT_VARIATION_URI == variation
                 || InputType.TYPE_TEXT_VARIATION_FILTER == variation
                 || flagNoSuggestions
                 || flagAutoComplete;
-        mIsSettingsSuggestionStripOn = !noSuggestionStrip;
+        mShouldShowSuggestions = !shouldSuppressSuggestions;
 
         mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType);
 
@@ -111,6 +119,15 @@
         return editorInfo.inputType == mInputType;
     }
 
+    public boolean hasNoMicrophoneKeyOption() {
+        @SuppressWarnings("deprecation")
+        final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
+                null, NO_MICROPHONE_COMPAT, mEditorInfo);
+        final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
+                mPackageNameForPrivateImeOptions, NO_MICROPHONE, mEditorInfo);
+        return noMicrophone || deprecatedNoMicrophone;
+    }
+
     @SuppressWarnings("unused")
     private void dumpFlags(final int inputType) {
         final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
@@ -241,7 +258,7 @@
                 mInputType,
                 (mInputTypeNoAutoCorrect ? " noAutoCorrect" : ""),
                 (mIsPasswordField ? " password" : ""),
-                (mIsSettingsSuggestionStripOn ? " suggestionStrip" : ""),
+                (mShouldShowSuggestions ? " shouldShowSuggestions" : ""),
                 (mApplicationSpecifiedCompletionOn ? " appSpecified" : ""),
                 (mShouldInsertSpacesAutomatically ? " insertSpaces" : ""),
                 mTargetApplicationPackageName);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d329d2c..709f133 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -166,6 +166,8 @@
         private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
         private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2;
         private static final int ARG2_UNUSED = 0;
+        private static final int ARG1_FALSE = 0;
+        private static final int ARG1_TRUE = 1;
 
         private int mDelayUpdateSuggestions;
         private int mDelayUpdateShiftState;
@@ -213,7 +215,7 @@
             case MSG_RESUME_SUGGESTIONS:
                 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
                         latinIme.mSettings.getCurrent(),
-                        false /* includeResumedWordInSuggestions */);
+                        msg.arg1 == ARG1_TRUE /* shouldIncludeResumedWordInSuggestions */);
                 break;
             case MSG_REOPEN_DICTIONARIES:
                 latinIme.resetSuggest();
@@ -250,7 +252,7 @@
             sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
         }
 
-        public void postResumeSuggestions() {
+        public void postResumeSuggestions(final boolean shouldIncludeResumedWordInSuggestions) {
             final LatinIME latinIme = getOwnerInstance();
             if (latinIme == null) {
                 return;
@@ -260,7 +262,10 @@
                 return;
             }
             removeMessages(MSG_RESUME_SUGGESTIONS);
-            sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
+            sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS,
+                    shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
+                            0 /* ignored */),
+                    mDelayUpdateSuggestions);
         }
 
         public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
@@ -522,7 +527,8 @@
     void loadSettings() {
         final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
         final EditorInfo editorInfo = getCurrentInputEditorInfo();
-        final InputAttributes inputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
+        final InputAttributes inputAttributes = new InputAttributes(
+                editorInfo, isFullscreenMode(), getPackageName());
         mSettings.loadSettings(this, locale, inputAttributes);
         final SettingsValues currentSettingsValues = mSettings.getCurrent();
         AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
@@ -602,7 +608,7 @@
         mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
                 settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
                 false /* forceReloadMainDictionary */, this);
-        if (settingsValues.mCorrectionEnabled) {
+        if (settingsValues.mAutoCorrectionEnabled) {
             mInputLogic.mSuggest.setAutoCorrectionThreshold(
                     settingsValues.mAutoCorrectionThreshold);
         }
@@ -640,14 +646,19 @@
 
     @Override
     public void onConfigurationChanged(final Configuration conf) {
-        // If orientation changed while predicting, commit the change
         final SettingsValues settingsValues = mSettings.getCurrent();
         if (settingsValues.mDisplayOrientation != conf.orientation) {
             mHandler.startOrientationChanging();
-            mInputLogic.mConnection.beginBatchEdit();
-            mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
-            mInputLogic.mConnection.finishComposingText();
-            mInputLogic.mConnection.endBatchEdit();
+            // If !isComposingWord, #commitTyped() is a no-op, but still, it's better to avoid
+            // the useless IPC of {begin,end}BatchEdit.
+            if (mInputLogic.mWordComposer.isComposingWord()) {
+                mInputLogic.mConnection.beginBatchEdit();
+                // If we had a composition in progress, we need to commit the word so that the
+                // suggestionsSpan will be added. This will allow resuming on the same suggestions
+                // after rotation is finished.
+                mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
+                mInputLogic.mConnection.endBatchEdit();
+            }
         }
         PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf,
                 mDictionaryFacilitator);
@@ -741,6 +752,7 @@
         }
         Log.i(TAG, "Starting input. Cursor position = "
                 + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
+        // TODO: Consolidate these checks with {@link InputAttributes}.
         if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
             Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
             Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
@@ -802,7 +814,7 @@
             // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best
             // effort to work around this bug.
             mInputLogic.mConnection.tryFixLyingCursorPosition();
-            mHandler.postResumeSuggestions();
+            mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */);
             canReachInputConnection = true;
         }
 
@@ -814,7 +826,7 @@
             mainKeyboardView.closing();
             currentSettingsValues = mSettings.getCurrent();
 
-            if (currentSettingsValues.mCorrectionEnabled) {
+            if (currentSettingsValues.mAutoCorrectionEnabled) {
                 suggest.setAutoCorrectionThreshold(
                         currentSettingsValues.mAutoCorrectionThreshold);
             }
@@ -991,7 +1003,7 @@
                 null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */,
                 false /* isObsoleteSuggestions */, false /* isPrediction */);
         // When in fullscreen mode, show completions generated by the application forcibly
-        setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */);
+        setSuggestedWords(suggestedWords);
     }
 
     private int getAdjustedBackingViewHeight() {
@@ -1294,30 +1306,6 @@
         // Nothing to do so far.
     }
 
-    private boolean isSuggestionStripVisible() {
-        if (!hasSuggestionStripView()) {
-            return false;
-        }
-        if (mSuggestionStripView.isShowingAddToDictionaryHint()) {
-            return true;
-        }
-        final SettingsValues currentSettings = mSettings.getCurrent();
-        if (null == currentSettings) {
-            return false;
-        }
-        if (ImportantNoticeUtils.shouldShowImportantNotice(this,
-                currentSettings.mInputAttributes)) {
-            return true;
-        }
-        if (!currentSettings.isCurrentOrientationAllowingSuggestionsPerUserSettings()) {
-            return false;
-        }
-        if (currentSettings.isApplicationSpecifiedCompletionsOn()) {
-            return true;
-        }
-        return currentSettings.isSuggestionsRequested();
-    }
-
     public boolean hasSuggestionStripView() {
         return null != mSuggestionStripView;
     }
@@ -1335,9 +1323,7 @@
         mSuggestionStripView.dismissAddToDictionaryHint();
     }
 
-    // TODO[IL]: Define a clear interface for this
-    public void setSuggestedWords(final SuggestedWords suggestedWords,
-            final boolean isSuggestionStripVisible) {
+    private void setSuggestedWords(final SuggestedWords suggestedWords) {
         mInputLogic.setSuggestedWords(suggestedWords);
         // TODO: Modify this when we support suggestions with hard keyboard
         if (!hasSuggestionStripView()) {
@@ -1346,28 +1332,35 @@
         if (!onEvaluateInputViewShown()) {
             return;
         }
-        if (!isSuggestionStripVisible) {
-            mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
+
+        final SettingsValues currentSettingsValues = mSettings.getCurrent();
+        final boolean shouldShowImportantNotice =
+                ImportantNoticeUtils.shouldShowImportantNotice(this);
+        final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice
+                || currentSettingsValues.mShowsVoiceInputKey
+                || currentSettingsValues.isSuggestionsRequested()
+                || currentSettingsValues.isApplicationSpecifiedCompletionsOn();
+        final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword
+                && !currentSettingsValues.mInputAttributes.mIsPasswordField;
+        mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode());
+        if (!shouldShowSuggestionsStrip) {
             return;
         }
-        mSuggestionStripView.setVisibility(View.VISIBLE);
 
-        final SettingsValues currentSettings = mSettings.getCurrent();
-        final boolean showSuggestions;
-        // May show the important notice when there are no suggestions to show,
-        if (SuggestedWords.EMPTY == suggestedWords
-                // or the suggestion strip is expected to show punctuation suggestions,
+        final boolean isEmptyApplicationSpecifiedCompletions =
+                currentSettingsValues.isApplicationSpecifiedCompletionsOn()
+                && suggestedWords.isEmpty();
+        final boolean noSuggestionsToShow = (SuggestedWords.EMPTY == suggestedWords)
                 || suggestedWords.isPunctuationSuggestions()
-                // or it's not requested to show suggestions by the input field,
-                || !currentSettings.isSuggestionsRequested()
-                // or the "show correction suggestions" settings is off by users preference.
-                || !currentSettings.isCurrentOrientationAllowingSuggestionsPerUserSettings()) {
-            showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle(
-                    currentSettings.mInputAttributes);
+                || isEmptyApplicationSpecifiedCompletions;
+        final boolean isShowingImportantNotice;
+        if (shouldShowImportantNotice && noSuggestionsToShow) {
+            isShowingImportantNotice = mSuggestionStripView.maybeShowImportantNoticeTitle();
         } else {
-            showSuggestions = true;
+            isShowingImportantNotice = false;
         }
-        if (showSuggestions) {
+
+        if (currentSettingsValues.isSuggestionsRequested() && !isShowingImportantNotice) {
             mSuggestionStripView.setSuggestions(suggestedWords,
                     SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype()));
         }
@@ -1410,7 +1403,7 @@
         mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer,
                 mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion(),
                 keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive,
-                currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId,
+                currentSettings.mAutoCorrectionEnabled, additionalFeaturesOptions, sessionId,
                 sequenceNumber, callback);
     }
 
@@ -1430,7 +1423,7 @@
             setNeutralSuggestionStrip();
         } else {
             mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
-            setSuggestedWords(suggestedWords, isSuggestionStripVisible());
+            setSuggestedWords(suggestedWords);
         }
         // Cache the auto-correction in accessibility code so we can speak it if the user
         // touches a key that will insert it.
@@ -1463,7 +1456,7 @@
         final SettingsValues currentSettings = mSettings.getCurrent();
         final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
                 ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
-        setSuggestedWords(neutralSuggestions, isSuggestionStripVisible());
+        setSuggestedWords(neutralSuggestions);
     }
 
     // TODO: Make this private
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 8f744be..7989346 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -102,6 +102,18 @@
     }
 
     @Override
+    public int getMaxFrequencyOfExactMatches(final String word) {
+        if (mLock.readLock().tryLock()) {
+            try {
+                return mBinaryDictionary.getMaxFrequencyOfExactMatches(word);
+            } finally {
+                mLock.readLock().unlock();
+            }
+        }
+        return NOT_A_PROBABILITY;
+    }
+
+    @Override
     public void close() {
         mLock.writeLock().lock();
         try {
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 9d72c64..96476b2 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -891,4 +891,8 @@
     public boolean hasSelection() {
         return mExpectedSelEnd != mExpectedSelStart;
     }
+
+    public boolean isCursorPositionKnown() {
+        return INVALID_CURSOR_POSITION != mExpectedSelStart;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index dbbe1a0..c90dc90 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -319,8 +319,16 @@
                 || !mWordComposer.isComposingWord(); // safe to reset
         final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd);
         final int moveAmount = newSelStart - oldSelStart;
-        if (selectionChangedOrSafeToReset && (hasOrHadSelection
-                || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
+        // As an added small gift from the framework, it happens upon rotation when there
+        // is a selection that we get a wrong cursor position delivered to startInput() that
+        // does not get reflected in the oldSel{Start,End} parameters to the next call to
+        // onUpdateSelection. In this case, we may have set a composition, and when we're here
+        // we realize we shouldn't have. In theory, in this case, selectionChangedOrSafeToReset
+        // should be true, but that is if the framework had taken that wrong cursor position
+        // into account, which means we have to reset the entire composing state whenever there
+        // is or was a selection regardless of whether it changed or not.
+        if (hasOrHadSelection || (selectionChangedOrSafeToReset
+                && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
             // If we are composing a word and moving the cursor, we would want to set a
             // suggestion span for recorrection to work correctly. Unfortunately, that
             // would involve the keyboard committing some new text, which would move the
@@ -348,7 +356,7 @@
         // The cursor has been moved : we now accept to perform recapitalization
         mRecapitalizeStatus.enable();
         // We moved the cursor. If we are touching a word, we need to resume suggestion.
-        mLatinIME.mHandler.postResumeSuggestions();
+        mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */);
         // Stop the last recapitalization, if started.
         mRecapitalizeStatus.stop();
         return true;
@@ -770,7 +778,7 @@
         }
         // isComposingWord() may have changed since we stored wasComposing
         if (mWordComposer.isComposingWord()) {
-            if (settingsValues.mCorrectionEnabled) {
+            if (settingsValues.mAutoCorrectionEnabled) {
                 final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
                         : StringUtils.newSingleCodePointString(codePoint);
                 commitCurrentAutoCorrection(settingsValues, separator, handler);
@@ -990,7 +998,7 @@
                     && !mConnection.isCursorFollowedByWordCharacter(
                             inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
                 restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
-                        true /* includeResumedWordInSuggestions */);
+                        true /* shouldIncludeResumedWordInSuggestions */);
             }
         }
     }
@@ -1173,7 +1181,7 @@
         // If correction is not enabled, we don't add words to the user history dictionary.
         // That's to avoid unintended additions in some sensitive fields, or fields that
         // expect to receive non-words.
-        if (!settingsValues.mCorrectionEnabled) return;
+        if (!settingsValues.mAutoCorrectionEnabled) return;
 
         if (TextUtils.isEmpty(suggestion)) return;
         final boolean wasAutoCapitalized =
@@ -1230,12 +1238,12 @@
      * do nothing.
      *
      * @param settingsValues the current values of the settings.
-     * @param includeResumedWordInSuggestions whether to include the word on which we resume
+     * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume
      *   suggestions in the suggestion list.
      */
     // TODO: make this private.
     public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
-            final boolean includeResumedWordInSuggestions) {
+            final boolean shouldIncludeResumedWordInSuggestions) {
         // HACK: We may want to special-case some apps that exhibit bad behavior in case of
         // recorrection. This is a temporary, stopgap measure that will be removed later.
         // TODO: remove this.
@@ -1281,7 +1289,7 @@
         if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
         final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
         final String typedWord = range.mWord.toString();
-        if (includeResumedWordInSuggestions) {
+        if (shouldIncludeResumedWordInSuggestions) {
             suggestions.add(new SuggestedWordInfo(typedWord,
                     SuggestedWords.MAX_SUGGESTIONS + 1,
                     SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
@@ -1319,9 +1327,10 @@
                 typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
         mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
                 expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
-        if (suggestions.isEmpty()) {
-            // We come here if there weren't any suggestion spans on this word. We will try to
-            // compute suggestions for it instead.
+        if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
+            // If there weren't any suggestion spans on this word, suggestions#size() will be 1
+            // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
+            // have no useful suggestions, so we will try to compute some for it instead.
             mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
                     SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
                         @Override
@@ -1329,7 +1338,7 @@
                                 final SuggestedWords suggestedWordsIncludingTypedWord) {
                             final SuggestedWords suggestedWords;
                             if (suggestedWordsIncludingTypedWord.size() > 1
-                                    && !includeResumedWordInSuggestions) {
+                                    && !shouldIncludeResumedWordInSuggestions) {
                                 // We were able to compute new suggestions for this word.
                                 // Remove the typed word, since we don't want to display it in this
                                 // case. The #getSuggestedWordsExcludingTypedWord() method sets
@@ -1922,9 +1931,11 @@
             final boolean tryResumeSuggestions, final int remainingTries,
             // TODO: remove these arguments
             final LatinIME.UIHandler handler) {
+        final boolean shouldFinishComposition = mConnection.hasSelection()
+                || !mConnection.isCursorPositionKnown();
         if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(
                 mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(),
-                false /* shouldFinishComposition */)) {
+                shouldFinishComposition)) {
             if (0 < remainingTries) {
                 handler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
                 return false;
@@ -1934,7 +1945,9 @@
         }
         mConnection.tryFixLyingCursorPosition();
         if (tryResumeSuggestions) {
-            handler.postResumeSuggestions();
+            // This is triggered when starting input anew, so we want to include the resumed
+            // word in suggestions.
+            handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */);
         }
         return true;
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 6c6e79e..389d9a8 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -28,6 +28,7 @@
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
@@ -84,7 +85,7 @@
     public final int mKeyPreviewPopupDismissDelay;
     private final boolean mAutoCorrectEnabled;
     public final float mAutoCorrectionThreshold;
-    public final boolean mCorrectionEnabled;
+    public final boolean mAutoCorrectionEnabled;
     public final int mSuggestionVisibility;
     public final int mDisplayOrientation;
     private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
@@ -109,7 +110,8 @@
 
         // Store the input attributes
         if (null == inputAttributes) {
-            mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
+            mInputAttributes = new InputAttributes(
+                    null, false /* isFullscreenMode */, context.getPackageName());
         } else {
             mInputAttributes = inputAttributes;
         }
@@ -121,7 +123,10 @@
         mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res);
         mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
                 DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
-        mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res);
+        mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
+                && !mInputAttributes.mIsPasswordField
+                && !mInputAttributes.hasNoMicrophoneKeyOption()
+                && SubtypeSwitcher.getInstance().isShortcutImeEnabled();
         final String autoCorrectionThresholdRawValue = prefs.getString(
                 Settings.PREF_AUTO_CORRECTION_THRESHOLD,
                 res.getString(R.string.auto_correction_threshold_mode_index_modest));
@@ -150,7 +155,7 @@
         mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
                 Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
         mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
-        mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
+        mAutoCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
         final String showSuggestionsSetting = prefs.getString(
                 Settings.PREF_SHOW_SUGGESTIONS_SETTING,
                 res.getString(R.string.prefs_suggestion_visibility_default_value));
@@ -189,8 +194,8 @@
     }
 
     public boolean isSuggestionsRequested() {
-        return mInputAttributes.mIsSettingsSuggestionStripOn
-                && (mCorrectionEnabled
+        return mInputAttributes.mShouldShowSuggestions
+                && (mAutoCorrectionEnabled
                         || isCurrentOrientationAllowingSuggestionsPerUserSettings());
     }
 
@@ -318,18 +323,18 @@
 
     private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs,
             final Resources res) {
-        if (!prefs.contains(Settings.PREF_VOICE_INPUT_KEY)) {
-            // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
-            // {@link Settings#PREF_VOICE_INPUT_KEY}.
+        // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
+        // {@link Settings#PREF_VOICE_INPUT_KEY}.
+        if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
             final String voiceModeMain = res.getString(R.string.voice_mode_main);
             final String voiceMode = prefs.getString(
                     Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
             final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode);
-            prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey).apply();
-        }
-        // Remove the obsolete preference if exists.
-        if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
-            prefs.edit().remove(Settings.PREF_VOICE_MODE_OBSOLETE).apply();
+            prefs.edit()
+                    .putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey)
+                    // Remove the obsolete preference if exists.
+                    .remove(Settings.PREF_VOICE_MODE_OBSOLETE)
+                    .apply();
         }
         return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
     }
@@ -390,8 +395,8 @@
         sb.append("" + mAutoCorrectEnabled);
         sb.append("\n   mAutoCorrectionThreshold = ");
         sb.append("" + mAutoCorrectionThreshold);
-        sb.append("\n   mCorrectionEnabled = ");
-        sb.append("" + mCorrectionEnabled);
+        sb.append("\n   mAutoCorrectionEnabled = ");
+        sb.append("" + mAutoCorrectionEnabled);
         sb.append("\n   mSuggestionVisibility = ");
         sb.append("" + mSuggestionVisibility);
         sb.append("\n   mDisplayOrientation = ");
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 4a5a7f0..9724149 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -42,12 +42,12 @@
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener;
 import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
 
@@ -89,19 +89,17 @@
     private static class StripVisibilityGroup {
         private final View mSuggestionStripView;
         private final View mSuggestionsStrip;
-        private final View mVoiceKey;
         private final View mAddToDictionaryStrip;
         private final View mImportantNoticeStrip;
 
         public StripVisibilityGroup(final View suggestionStripView,
-                final ViewGroup suggestionsStrip, final ImageButton voiceKey,
-                final ViewGroup addToDictionaryStrip, final View importantNoticeStrip) {
+                final ViewGroup suggestionsStrip, final ViewGroup addToDictionaryStrip,
+                final View importantNoticeStrip) {
             mSuggestionStripView = suggestionStripView;
             mSuggestionsStrip = suggestionsStrip;
-            mVoiceKey = voiceKey;
             mAddToDictionaryStrip = addToDictionaryStrip;
             mImportantNoticeStrip = importantNoticeStrip;
-            showSuggestionsStrip(false /* voiceKeyEnabled */);
+            showSuggestionsStrip();
         }
 
         public void setLayoutDirection(final boolean isRtlLanguage) {
@@ -113,23 +111,20 @@
             ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection);
         }
 
-        public void showSuggestionsStrip(final boolean enableVoiceKey) {
+        public void showSuggestionsStrip() {
             mSuggestionsStrip.setVisibility(VISIBLE);
-            mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
             mAddToDictionaryStrip.setVisibility(INVISIBLE);
             mImportantNoticeStrip.setVisibility(INVISIBLE);
         }
 
         public void showAddToDictionaryStrip() {
             mSuggestionsStrip.setVisibility(INVISIBLE);
-            mVoiceKey.setVisibility(INVISIBLE);
             mAddToDictionaryStrip.setVisibility(VISIBLE);
             mImportantNoticeStrip.setVisibility(INVISIBLE);
         }
 
-        public void showImportantNoticeStrip(final boolean enableVoiceKey) {
+        public void showImportantNoticeStrip() {
             mSuggestionsStrip.setVisibility(INVISIBLE);
-            mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
             mAddToDictionaryStrip.setVisibility(INVISIBLE);
             mImportantNoticeStrip.setVisibility(VISIBLE);
         }
@@ -159,7 +154,7 @@
         mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key);
         mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip);
         mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
-        mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip, mVoiceKey,
+        mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip,
                 mAddToDictionaryStrip, mImportantNoticeStrip);
 
         for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
@@ -207,15 +202,11 @@
         mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
     }
 
-    private boolean isVoiceKeyEnabled() {
-        if (mMainKeyboardView == null) {
-            return false;
-        }
-        final Keyboard keyboard = mMainKeyboardView.getKeyboard();
-        if (keyboard == null) {
-            return false;
-        }
-        return keyboard.mId.mHasShortcutKey;
+    public void updateVisibility(final boolean shouldBeVisible, final boolean isFullscreenMode) {
+        final int visibility = shouldBeVisible ? VISIBLE : (isFullscreenMode ? GONE : INVISIBLE);
+        setVisibility(visibility);
+        final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
+        mVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : INVISIBLE);
     }
 
     public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) {
@@ -224,7 +215,7 @@
         mSuggestedWords = suggestedWords;
         mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
                 mSuggestedWords, mSuggestionsStrip, this);
-        mStripVisibilityGroup.showSuggestionsStrip(isVoiceKeyEnabled());
+        mStripVisibilityGroup.showSuggestionsStrip();
     }
 
     public int setMoreSuggestionsHeight(final int remainingHeight) {
@@ -255,8 +246,8 @@
     // This method checks if we should show the important notice (checks on permanent storage if
     // it has been shown once already or not, and if in the setup wizard). If applicable, it shows
     // the notice. In all cases, it returns true if it was shown, false otherwise.
-    public boolean maybeShowImportantNoticeTitle(final InputAttributes inputAttributes) {
-        if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), inputAttributes)) {
+    public boolean maybeShowImportantNoticeTitle() {
+        if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext())) {
             return false;
         }
         if (getWidth() <= 0) {
@@ -271,7 +262,7 @@
             dismissMoreSuggestionsPanel();
         }
         mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle);
-        mStripVisibilityGroup.showImportantNoticeStrip(isVoiceKeyEnabled());
+        mStripVisibilityGroup.showImportantNoticeStrip();
         mImportantNoticeStrip.setOnClickListener(this);
         return true;
     }
@@ -279,7 +270,7 @@
     public void clear() {
         mSuggestionsStrip.removeAllViews();
         removeAllDebugInfoViews();
-        mStripVisibilityGroup.showSuggestionsStrip(false /* enableVoiceKey */);
+        mStripVisibilityGroup.showSuggestionsStrip();
         dismissMoreSuggestionsPanel();
     }
 
@@ -481,7 +472,7 @@
         // Called by the framework when the size is known. Show the important notice if applicable.
         // This may be overriden by showing suggestions later, if applicable.
         if (oldw <= 0 && w > 0) {
-            maybeShowImportantNoticeTitle(Settings.getInstance().getCurrent().mInputAttributes);
+            maybeShowImportantNoticeTitle();
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 80d4cc4..eda8194 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -167,7 +167,9 @@
         // should not insert, because either A. the word exists with no shortcut, in which
         // case the exact same thing we want to insert is already there, or B. the word
         // exists with at least one shortcut, in which case it has priority on our word.
-        if (hasWord(newWord, context)) return CODE_ALREADY_PRESENT;
+        if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
+            return CODE_ALREADY_PRESENT;
+        }
 
         // Disallow duplicates. If the same word with no shortcut is defined, remove it; if
         // the same word with the same shortcut is defined, remove it; but we don't mind if
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterUsingSuggestion.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterUsingSuggestion.java
index b9c7f56..8c3844e 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterUsingSuggestion.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterUsingSuggestion.java
@@ -16,33 +16,22 @@
 
 package com.android.inputmethod.latin.utils;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import android.content.Context;
-import android.content.res.Resources;
-import android.text.InputType;
 import android.util.Log;
-import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.DictionaryFacilitator;
 import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
 
 /**
  * This class is used to prevent distracters being added to personalization
  * or user history dictionaries
  */
+// TODO: Rename.
 public class DistracterFilterUsingSuggestion implements DistracterFilter {
     private static final String TAG = DistracterFilterUsingSuggestion.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -50,10 +39,7 @@
     private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
 
     private final Context mContext;
-    private final Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap;
-    private final Map<Locale, Keyboard> mLocaleToKeyboardMap;
     private final DictionaryFacilitator mDictionaryFacilitator;
-    private Keyboard mKeyboard;
     private final Object mLock = new Object();
 
     /**
@@ -63,10 +49,7 @@
      */
     public DistracterFilterUsingSuggestion(final Context context) {
         mContext = context;
-        mLocaleToSubtypeMap = new HashMap<>();
-        mLocaleToKeyboardMap = new HashMap<>();
         mDictionaryFacilitator = new DictionaryFacilitator();
-        mKeyboard = null;
     }
 
     @Override
@@ -76,94 +59,6 @@
 
     @Override
     public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
-        final Map<Locale, InputMethodSubtype> newLocaleToSubtypeMap = new HashMap<>();
-        if (enabledSubtypes != null) {
-            for (final InputMethodSubtype subtype : enabledSubtypes) {
-                final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
-                if (newLocaleToSubtypeMap.containsKey(locale)) {
-                    // Multiple subtypes are enabled for one locale.
-                    // TODO: Investigate what we should do for this case.
-                    continue;
-                }
-                newLocaleToSubtypeMap.put(locale, subtype);
-            }
-        }
-        if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
-            // Enabled subtypes have not been changed.
-            return;
-        }
-        synchronized (mLock) {
-            mLocaleToSubtypeMap.clear();
-            mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
-            mLocaleToKeyboardMap.clear();
-        }
-    }
-
-    private boolean isDistracter(
-            final SuggestionResults suggestionResults, final String consideredWord) {
-        int perfectMatchProbability = Dictionary.NOT_A_PROBABILITY;
-        for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) {
-            if (suggestedWordInfo.mWord.equals(consideredWord)) {
-                perfectMatchProbability = mDictionaryFacilitator.getFrequency(consideredWord);
-                continue;
-            }
-            // Exact match can include case errors, accent errors, digraph conversions.
-            final boolean isExactMatch = suggestedWordInfo.isExactMatch();
-            final boolean isExactMatchWithIntentionalOmission =
-                    suggestedWordInfo.isExactMatchWithIntentionalOmission();
-
-            if (DEBUG) {
-                final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
-                        consideredWord, suggestedWordInfo.mWord, suggestedWordInfo.mScore);
-                Log.d(TAG, "consideredWord: " +  consideredWord);
-                Log.d(TAG, "top suggestion: " +  suggestedWordInfo.mWord);
-                Log.d(TAG, "suggestionScore: " +  suggestedWordInfo.mScore);
-                Log.d(TAG, "normalizedScore: " +  normalizedScore);
-                Log.d(TAG, "isExactMatch: " + isExactMatch);
-                Log.d(TAG, "isExactMatchWithIntentionalOmission: "
-                            + isExactMatchWithIntentionalOmission);
-            }
-            if (perfectMatchProbability != Dictionary.NOT_A_PROBABILITY) {
-                final int topNonPerfectProbability = mDictionaryFacilitator.getFrequency(
-                        suggestedWordInfo.mWord);
-                if (DEBUG) {
-                    Log.d(TAG, "perfectMatchProbability: " + perfectMatchProbability);
-                    Log.d(TAG, "topNonPerfectProbability: " + topNonPerfectProbability);
-                }
-                if (perfectMatchProbability > topNonPerfectProbability) {
-                    return false;
-                }
-            }
-            return isExactMatch || isExactMatchWithIntentionalOmission;
-        }
-        return false;
-    }
-
-    private void loadKeyboardForLocale(final Locale newLocale) {
-        final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
-        if (cachedKeyboard != null) {
-            mKeyboard = cachedKeyboard;
-            return;
-        }
-        final InputMethodSubtype subtype;
-        synchronized (mLock) {
-            subtype = mLocaleToSubtypeMap.get(newLocale);
-        }
-        if (subtype == null) {
-            return;
-        }
-        final EditorInfo editorInfo = new EditorInfo();
-        editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
-        final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
-                mContext, editorInfo);
-        final Resources res = mContext.getResources();
-        final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
-        final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
-        builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
-        builder.setSubtype(subtype);
-        builder.setIsSpellChecker(false /* isSpellChecker */);
-        final KeyboardLayoutSet layoutSet = builder.build();
-        mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
     }
 
     private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
@@ -191,12 +86,6 @@
         }
         if (!locale.equals(mDictionaryFacilitator.getLocale())) {
             synchronized (mLock) {
-                if (!mLocaleToSubtypeMap.containsKey(locale)) {
-                    Log.e(TAG, "Locale " + locale + " is not enabled.");
-                    // TODO: Investigate what we should do for disabled locales.
-                    return false;
-                }
-                loadKeyboardForLocale(locale);
                 // Reset dictionaries for the locale.
                 try {
                     loadDictionariesForLocale(locale);
@@ -207,37 +96,17 @@
                 }
             }
         }
-        if (mKeyboard == null) {
-            return false;
+        // The tested word is a distracter when there is a word that is exact matched to the tested
+        // word and its probability is higher than the tested word's probability.
+        final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
+        final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
+        final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
+        if (DEBUG) {
+            Log.d(TAG, "testedWord: " + testedWord);
+            Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
+            Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
+            Log.d(TAG, "isDistracter: " + isDistracter);
         }
-        final WordComposer composer = new WordComposer();
-        final int[] codePoints = StringUtils.toCodePointArray(testedWord);
-        final int[] coordinates = mKeyboard.getCoordinates(codePoints);
-        composer.setComposingWord(codePoints, coordinates, PrevWordsInfo.EMPTY_PREV_WORDS_INFO);
-
-        final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
-        final String consideredWord = trailingSingleQuotesCount > 0 ?
-                testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) :
-                testedWord;
-        final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<>();
-        ExecutorUtils.getExecutor("check distracters").execute(new Runnable() {
-            @Override
-            public void run() {
-                final SuggestionResults suggestionResults =
-                        mDictionaryFacilitator.getSuggestionResults(
-                                composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO,
-                                mKeyboard.getProximityInfo(), true /* blockOffensiveWords */,
-                                null /* additionalFeaturesOptions */, 0 /* sessionId */,
-                                null /* rawSuggestions */);
-                if (suggestionResults.isEmpty()) {
-                    holder.set(false);
-                    return;
-                }
-                holder.set(isDistracter(suggestionResults, consideredWord));
-            }
-        });
-        // It's OK to block the distracter filtering, but the dictionary lookup should be done
-        // sequentially using ExecutorUtils.
-        return holder.get(false /* defaultValue */, Constants.GET_SUGGESTED_WORDS_TIMEOUT);
+        return isDistracter;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index 7d937a9..8b70778 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -23,7 +23,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.R;
 
 public final class ImportantNoticeUtils {
@@ -78,14 +77,7 @@
         return getCurrentImportantNoticeVersion(context) > lastVersion;
     }
 
-    public static boolean shouldShowImportantNotice(final Context context,
-            final InputAttributes inputAttributes) {
-        if (inputAttributes == null || inputAttributes.mIsPasswordField) {
-            return false;
-        }
-        if (isInSystemSetupWizard(context)) {
-            return false;
-        }
+    public static boolean shouldShowImportantNotice(final Context context) {
         if (!hasNewImportantNotice(context)) {
             return false;
         }
@@ -93,6 +85,9 @@
         if (TextUtils.isEmpty(importantNoticeTitle)) {
             return false;
         }
+        if (isInSystemSetupWizard(context)) {
+            return false;
+        }
         return true;
     }
 
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index cb337e6..07a82a9 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -28,6 +28,7 @@
     $(addprefix suggest/core/dictionary/, \
         bigram_dictionary.cpp \
         dictionary.cpp \
+        dictionary_utils.cpp \
         digraph_utils.cpp \
         error_type_utils.cpp \
         multi_bigram_map.cpp \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index d6a6196..476338e 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -273,6 +273,16 @@
     return dictionary->getProbability(codePoints, wordLength);
 }
 
+static jint latinime_BinaryDictionary_getMaxProbabilityOfExactMatches(
+        JNIEnv *env, jclass clazz, jlong dict, jintArray word) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) return NOT_A_PROBABILITY;
+    const jsize wordLength = env->GetArrayLength(word);
+    int codePoints[wordLength];
+    env->GetIntArrayRegion(word, 0, wordLength, codePoints);
+    return dictionary->getMaxProbabilityOfExactMatches(codePoints, wordLength);
+}
+
 static jint latinime_BinaryDictionary_getBigramProbability(JNIEnv *env, jclass clazz,
         jlong dict, jintArray word0, jboolean isBeginningOfSentence, jintArray word1) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
@@ -634,6 +644,11 @@
         reinterpret_cast<void *>(latinime_BinaryDictionary_getProbability)
     },
     {
+        const_cast<char *>("getMaxProbabilityOfExactMatchesNative"),
+        const_cast<char *>("(J[I)I"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_getMaxProbabilityOfExactMatches)
+    },
+    {
         const_cast<char *>("getBigramProbabilityNative"),
         const_cast<char *>("(J[IZ[I)I"),
         reinterpret_cast<void *>(latinime_BinaryDictionary_getBigramProbability)
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index ef03d2b..92f39ea 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -125,7 +125,7 @@
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
 
-    void initAsPassingChild(DicNode *parentDicNode) {
+    void initAsPassingChild(const DicNode *parentDicNode) {
         mIsCachedForNextSuggestion = parentDicNode->mIsCachedForNextSuggestion;
         const int codePoint =
                 parentDicNode->mDicNodeState.mDicNodeStateOutput.getCurrentWordCodePointAt(
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index bf2a000..4445f4a 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -48,7 +48,7 @@
 ///////////////////////////////////
 // Traverse node expansion utils //
 ///////////////////////////////////
-/* static */ void DicNodeUtils::getAllChildDicNodes(DicNode *dicNode,
+/* static */ void DicNodeUtils::getAllChildDicNodes(const DicNode *dicNode,
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
         DicNodeVector *const childDicNodes) {
     if (dicNode->isTotalInputSizeExceedingLimit()) {
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.h b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
index 0d60e57..00e80c6 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
@@ -35,7 +35,7 @@
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
             const DicNode *const prevWordLastDicNode, DicNode *const newRootDicNode);
     static void initByCopy(const DicNode *const srcDicNode, DicNode *const destDicNode);
-    static void getAllChildDicNodes(DicNode *dicNode,
+    static void getAllChildDicNodes(const DicNode *dicNode,
             const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
             DicNodeVector *childDicNodes);
     static float getBigramNodeImprobability(
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_vector.h b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
index cb28e57..54cde19 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_vector.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -52,7 +52,7 @@
         return static_cast<int>(mDicNodes.size());
     }
 
-    void pushPassingChild(DicNode *dicNode) {
+    void pushPassingChild(const DicNode *dicNode) {
         ASSERT(!mLock);
         mDicNodes.emplace_back();
         mDicNodes.back().initAsPassingChild(dicNode);
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 898b44f..f88388c 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -19,6 +19,7 @@
 #include "suggest/core/dictionary/dictionary.h"
 
 #include "defines.h"
+#include "suggest/core/dictionary/dictionary_utils.h"
 #include "suggest/core/policy/dictionary_header_structure_policy.h"
 #include "suggest/core/result/suggestion_results.h"
 #include "suggest/core/session/dic_traverse_session.h"
@@ -74,6 +75,12 @@
     return getDictionaryStructurePolicy()->getUnigramProbabilityOfPtNode(pos);
 }
 
+int Dictionary::getMaxProbabilityOfExactMatches(const int *word, int length) const {
+    TimeKeeper::setCurrentTime();
+    return DictionaryUtils::getMaxProbabilityOfExactMatches(
+            mDictionaryStructureWithBufferPolicy.get(), word, length);
+}
+
 int Dictionary::getBigramProbability(const PrevWordsInfo *const prevWordsInfo, const int *word,
         int length) const {
     TimeKeeper::setCurrentTime();
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index f6d406f..10010b2 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -73,6 +73,8 @@
 
     int getProbability(const int *word, int length) const;
 
+    int getMaxProbabilityOfExactMatches(const int *word, int length) const;
+
     int getBigramProbability(const PrevWordsInfo *const prevWordsInfo,
             const int *word, int length) const;
 
diff --git a/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp b/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
new file mode 100644
index 0000000..b94966c
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/dictionary_utils.h"
+
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+#include "suggest/core/session/prev_words_info.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+
+namespace latinime {
+
+/* static */ int DictionaryUtils::getMaxProbabilityOfExactMatches(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+        const int *const codePoints, const int codePointCount) {
+    std::vector<DicNode> current;
+    std::vector<DicNode> next;
+
+    // No prev words information.
+    PrevWordsInfo emptyPrevWordsInfo;
+    int prevWordsPtNodePos[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+    emptyPrevWordsInfo.getPrevWordsTerminalPtNodePos(dictionaryStructurePolicy,
+            prevWordsPtNodePos, false /* tryLowerCaseSearch */);
+    current.emplace_back();
+    DicNodeUtils::initAsRoot(dictionaryStructurePolicy, prevWordsPtNodePos, &current.front());
+    for (int i = 0; i < codePointCount; ++i) {
+        // The base-lower input is used to ignore case errors and accent errors.
+        const int codePoint = CharUtils::toBaseLowerCase(codePoints[i]);
+        for (const DicNode &dicNode : current) {
+            if (dicNode.isInDigraph() && dicNode.getNodeCodePoint() == codePoint) {
+                next.emplace_back(dicNode);
+                next.back().advanceDigraphIndex();
+                continue;
+            }
+            processChildDicNodes(dictionaryStructurePolicy, codePoint, &dicNode, &next);
+        }
+        current.clear();
+        current.swap(next);
+    }
+
+    int maxProbability = NOT_A_PROBABILITY;
+    for (const DicNode &dicNode : current) {
+        if (!dicNode.isTerminalDicNode()) {
+            continue;
+        }
+        // dicNode can contain case errors, accent errors, intentional omissions or digraphs.
+        maxProbability = std::max(maxProbability, dicNode.getProbability());
+    }
+    return maxProbability;
+}
+
+/* static */ void DictionaryUtils::processChildDicNodes(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+        const int inputCodePoint, const DicNode *const parentDicNode,
+        std::vector<DicNode> *const outDicNodes) {
+    DicNodeVector childDicNodes;
+    DicNodeUtils::getAllChildDicNodes(parentDicNode, dictionaryStructurePolicy, &childDicNodes);
+    for (int childIndex = 0; childIndex < childDicNodes.getSizeAndLock(); ++childIndex) {
+        DicNode *const childDicNode = childDicNodes[childIndex];
+        const int codePoint = CharUtils::toBaseLowerCase(childDicNode->getNodeCodePoint());
+        if (inputCodePoint == codePoint) {
+            outDicNodes->emplace_back(*childDicNode);
+        }
+        if (childDicNode->canBeIntentionalOmission()) {
+            processChildDicNodes(dictionaryStructurePolicy, inputCodePoint, childDicNode,
+                    outDicNodes);
+        }
+        if (DigraphUtils::hasDigraphForCodePoint(
+                dictionaryStructurePolicy->getHeaderStructurePolicy(),
+                childDicNode->getNodeCodePoint())) {
+            childDicNode->advanceDigraphIndex();
+            if (childDicNode->getNodeCodePoint() == codePoint) {
+                childDicNode->advanceDigraphIndex();
+                outDicNodes->emplace_back(*childDicNode);
+            }
+        }
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/dictionary_utils.h b/native/jni/src/suggest/core/dictionary/dictionary_utils.h
new file mode 100644
index 0000000..358ebf6
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/dictionary_utils.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DICTIONARY_UTILS_H
+#define LATINIME_DICTIONARY_UTILS_H
+
+#include <vector>
+
+#include "defines.h"
+
+namespace latinime {
+
+class DictionaryStructureWithBufferPolicy;
+class DicNode;
+
+class DictionaryUtils {
+ public:
+    static int getMaxProbabilityOfExactMatches(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            const int *const codePoints, const int codePointCount);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryUtils);
+
+    static void processChildDicNodes(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            const int inputCodePoint, const DicNode *const parentDicNode,
+            std::vector<DicNode> *const outDicNodes);
+};
+} // namespace latinime
+#endif // LATINIME_DICTIONARY_UTILS_H
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java
similarity index 84%
rename from tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java
rename to tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java
index 06139b8..96f9255 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelKlpTests.java
@@ -29,9 +29,14 @@
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 @MediumTest
-public final class KeyboardLayoutSetActionLabelTests extends KeyboardLayoutSetTestsBase {
+public class KeyboardLayoutSetActionLabelKlpTests extends KeyboardLayoutSetTestsBase {
+    @Override
+    protected int getKeyboardThemeForTests() {
+        return KeyboardTheme.THEME_ID_KLP;
+    }
+
     private static void doTestActionKey(final String tag, final KeyboardLayoutSet layoutSet,
-            final int elementId, final String label, final int iconId) {
+            final int elementId, final CharSequence label, final int iconId) {
         final Keyboard keyboard = layoutSet.getKeyboard(elementId);
         final Key enterKey = keyboard.getKey(Constants.CODE_ENTER);
         assertNotNull(tag + " enter key on " + keyboard.mId, enterKey);
@@ -39,7 +44,7 @@
         assertEquals(tag + " enter icon " + enterKey, iconId, enterKey.getIconId());
     }
 
-    private void doTestActionLabel(final String tag, final InputMethodSubtype subtype,
+    protected void doTestActionLabel(final String tag, final InputMethodSubtype subtype,
             final int actionId, final int labelResId) {
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.imeOptions = actionId;
@@ -57,6 +62,11 @@
         } else {
             label = job.runInLocale(res, SubtypeLocaleUtils.getSubtypeLocale(subtype));
         }
+        doTestActionLabel(tag, subtype, editorInfo, label);
+    }
+
+    protected void doTestActionLabel(final String tag, final InputMethodSubtype subtype,
+            final EditorInfo editorInfo, final CharSequence label) {
         // Test text layouts.
         editorInfo.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
         final KeyboardLayoutSet layoutSet = createKeyboardLayoutSet(subtype, editorInfo);
@@ -82,7 +92,7 @@
                 label, KeyboardIconsSet.ICON_UNDEFINED);
     }
 
-    private void doTestActionKeyIcon(final String tag, final InputMethodSubtype subtype,
+    protected void doTestActionKeyIcon(final String tag, final InputMethodSubtype subtype,
             final int actionId, final String iconName) {
         final int iconId = KeyboardIconsSet.getIconId(iconName);
         final EditorInfo editorInfo = new EditorInfo();
@@ -111,14 +121,16 @@
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "unspecifiled "
                     + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_UNSPECIFIED, "enter_key");
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_UNSPECIFIED,
+                    KeyboardIconsSet.NAME_ENTER_KEY);
         }
     }
 
     public void testActionNone() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "none " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE, "enter_key");
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NONE,
+                    KeyboardIconsSet.NAME_ENTER_KEY);
         }
     }
 
@@ -132,7 +144,8 @@
     public void testActionSearch() {
         for (final InputMethodSubtype subtype : getAllSubtypesList()) {
             final String tag = "search " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
-            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEARCH, "search_key");
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEARCH,
+                    KeyboardIconsSet.NAME_SEARCH_KEY);
         }
     }
 
@@ -164,4 +177,15 @@
                     tag, subtype, EditorInfo.IME_ACTION_PREVIOUS, R.string.label_previous_key);
         }
     }
+
+    public void testActionCustom() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "custom " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            final CharSequence customLabel = "customLabel";
+            final EditorInfo editorInfo = new EditorInfo();
+            editorInfo.imeOptions = EditorInfo.IME_ACTION_UNSPECIFIED;
+            editorInfo.actionLabel = customLabel;
+            doTestActionLabel(tag, subtype, editorInfo, customLabel);
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java
new file mode 100644
index 0000000..7747ac5
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetActionLabelLxxTests.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+@MediumTest
+public class KeyboardLayoutSetActionLabelLxxTests extends KeyboardLayoutSetActionLabelKlpTests {
+    @Override
+    protected int getKeyboardThemeForTests() {
+        return KeyboardTheme.THEME_ID_LXX_DARK;
+    }
+
+    @Override
+    public void testActionUnspecified() {
+        super.testActionUnspecified();
+    }
+
+    @Override
+    public void testActionNone() {
+        super.testActionNone();
+    }
+
+    @Override
+    public void testActionGo() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "go " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_GO,
+                    KeyboardIconsSet.NAME_GO_KEY);
+        }
+    }
+
+    @Override
+    public void testActionSearch() {
+        super.testActionSearch();
+    }
+
+    @Override
+    public void testActionSend() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "send " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_SEND,
+                    KeyboardIconsSet.NAME_SEND_KEY);
+        }
+    }
+
+    @Override
+    public void testActionNext() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "next " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_NEXT,
+                    KeyboardIconsSet.NAME_NEXT_KEY);
+        }
+    }
+
+    @Override
+    public void testActionDone() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "done " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_DONE,
+                    KeyboardIconsSet.NAME_DONE_KEY);
+        }
+    }
+
+    @Override
+    public void testActionPrevious() {
+        for (final InputMethodSubtype subtype : getAllSubtypesList()) {
+            final String tag = "previous " + SubtypeLocaleUtils.getSubtypeNameForLogging(subtype);
+            doTestActionKeyIcon(tag, subtype, EditorInfo.IME_ACTION_PREVIOUS,
+                    KeyboardIconsSet.NAME_PREVIOUS_KEY);
+        }
+    }
+
+    @Override
+    public void testActionCustom() {
+        super.testActionCustom();
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
index 26402f8..bf19a8b 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
@@ -29,6 +29,11 @@
     private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 45;
     private static final int NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES = 2;
 
+    @Override
+    protected int getKeyboardThemeForTests() {
+        return KeyboardTheme.THEME_ID_KLP;
+    }
+
     private static String toString(final ArrayList<InputMethodSubtype> subtypeList) {
         final StringBuilder sb = new StringBuilder();
         for (int index = 0; index < subtypeList.size(); index++) {
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index 1ccb58c..ab7d1b2 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -18,9 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.ContextThemeWrapper;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
@@ -38,8 +36,7 @@
 import java.util.ArrayList;
 import java.util.Locale;
 
-@SmallTest
-public class KeyboardLayoutSetTestsBase extends AndroidTestCase {
+public abstract class KeyboardLayoutSetTestsBase extends AndroidTestCase {
     // All input method subtypes of LatinIME.
     private final ArrayList<InputMethodSubtype> mAllSubtypesList = new ArrayList<>();
     private final ArrayList<InputMethodSubtype> mAsciiCapableSubtypesList = new ArrayList<>();
@@ -48,13 +45,15 @@
     private Context mThemeContext;
     private int mScreenMetrics;
 
+    protected abstract int getKeyboardThemeForTests();
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mScreenMetrics = mContext.getResources().getInteger(R.integer.config_screen_metrics);
 
-        final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(
-                PreferenceManager.getDefaultSharedPreferences(mContext));
+        final KeyboardTheme keyboardTheme = KeyboardTheme.searchKeyboardThemeById(
+                getKeyboardThemeForTests());
         mThemeContext = new ContextThemeWrapper(mContext, keyboardTheme.mStyleId);
         RichInputMethodManager.init(mThemeContext);
         final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
@@ -115,13 +114,13 @@
 
     protected final KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
             final EditorInfo editorInfo) {
-        return createKeyboardLayoutSet(subtype, editorInfo, false /* isShortcutImeEnabled */,
-                false /* showsVoiceInputKey */, false /* isLanguageSwitchKeyEnabled */);
+        return createKeyboardLayoutSet(subtype, editorInfo, false /* voiceInputKeyEnabled */,
+                false /* languageSwitchKeyEnabled */);
     }
 
     protected final KeyboardLayoutSet createKeyboardLayoutSet(final InputMethodSubtype subtype,
-            final EditorInfo editorInfo, final boolean isShortcutImeEnabled,
-            final boolean showsVoiceInputKey, final boolean isLanguageSwitchKeyEnabled) {
+            final EditorInfo editorInfo, final boolean voiceInputKeyEnabled,
+            final boolean languageSwitchKeyEnabled) {
         final Context context = mThemeContext;
         final Resources res = context.getResources();
         final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
@@ -129,7 +128,8 @@
         final Builder builder = new Builder(context, editorInfo);
         builder.setKeyboardGeometry(keyboardWidth, keyboardHeight)
                 .setSubtype(subtype)
-                .setOptions(isShortcutImeEnabled, showsVoiceInputKey, isLanguageSwitchKeyEnabled);
+                .setVoiceInputKeyEnabled(voiceInputKeyEnabled)
+                .setLanguageSwitchKeyEnabled(languageSwitchKeyEnabled);
         return builder.build();
     }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
index d4e8fb6..a22ed60 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/LayoutTestsBase.java
@@ -24,6 +24,7 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet;
 import com.android.inputmethod.keyboard.KeyboardLayoutSetTestsBase;
+import com.android.inputmethod.keyboard.KeyboardTheme;
 import com.android.inputmethod.keyboard.layout.LayoutBase;
 import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase;
 import com.android.inputmethod.keyboard.layout.expected.ActualKeyboardBuilder;
@@ -53,8 +54,12 @@
                 + (isPhone() ? "phone" : "tablet");
         // TODO: Test with language switch key enabled and disabled.
         mKeyboardLayoutSet = createKeyboardLayoutSet(mSubtype, null /* editorInfo */,
-                true /* isShortcutImeEnabled */, true /* showsVoiceInputKey */,
-                true /* isLanguageSwitchKeyEnabled */);
+                true /* voiceInputKeyEnabled */, true /* languageSwitchKeyEnabled */);
+    }
+
+    @Override
+    protected int getKeyboardThemeForTests() {
+        return KeyboardTheme.THEME_ID_KLP;
     }
 
     // Those helper methods have a lower case name to be readable when defining expected keyboard
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index ccede0e..55b794c 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -1472,4 +1472,33 @@
         assertEquals(bigramProbability,
                 binaryDictionary.getNgramProbability(prevWordsInfoStartOfSentence, "bbb"));
     }
+
+    public void testGetMaxFrequencyOfExactMatches() {
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testGetMaxFrequencyOfExactMatches(formatVersion);
+        }
+    }
+
+    private void testGetMaxFrequencyOfExactMatches(final int formatVersion) {
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+        addUnigramWord(binaryDictionary, "abc", 10);
+        addUnigramWord(binaryDictionary, "aBc", 15);
+        assertEquals(15, binaryDictionary.getMaxFrequencyOfExactMatches("abc"));
+        addUnigramWord(binaryDictionary, "ab'c", 20);
+        assertEquals(20, binaryDictionary.getMaxFrequencyOfExactMatches("abc"));
+        addUnigramWord(binaryDictionary, "a-b-c", 25);
+        assertEquals(25, binaryDictionary.getMaxFrequencyOfExactMatches("abc"));
+        addUnigramWord(binaryDictionary, "ab-'-'-'-c", 30);
+        assertEquals(30, binaryDictionary.getMaxFrequencyOfExactMatches("abc"));
+        addUnigramWord(binaryDictionary, "ab c", 255);
+        assertEquals(30, binaryDictionary.getMaxFrequencyOfExactMatches("abc"));
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java
index 406e9a9..b7f2271 100644
--- a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java
+++ b/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java
@@ -42,11 +42,6 @@
         final Locale localeEnUs = new Locale("en", "US");
         String typedWord;
 
-        typedWord = "google";
-        // For this test case, we consider "google" is a distracter to "Google".
-        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
-                EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs));
-
         typedWord = "Bill";
         // For this test case, we consider "Bill" is a distracter to "bill".
         assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
@@ -83,13 +78,18 @@
                 EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs));
 
         typedWord = "cafe";
-        // For this test case, we consider "café" is not a distracter to any word in dictionaries.
+        // For this test case, we consider "cafe" is not a distracter to any word in dictionaries.
+        assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs));
+
+        typedWord = "I'll";
+        // For this test case, we consider "I'll" is not a distracter to any word in dictionaries.
         assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries(
                 EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs));
 
         typedWord = "ill";
-        // For this test case, we consider "ill" is not a distracter to any word in dictionaries.
-        assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries(
+        // For this test case, we consider "ill" is a distracter to "I'll"
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
                 EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs));
 
         typedWord = "asdfd";
@@ -101,8 +101,51 @@
         typedWord = "thank";
         // For this test case, we consider "thank" is not a distracter to any other word
         // in dictionaries.
-        assertFalse(
-                mDistracterFilter.isDistracterToWordsInDictionaries(
-                        EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs));
+        assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeEnUs));
+
+        final Locale localeDeDe = new Locale("de", "DE");
+
+        typedWord = "fuer";
+        // For this test case, we consider "fuer" is a distracter to "für".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeDeDe));
+
+        typedWord = "fUEr";
+        // For this test case, we consider "fUEr" is a distracter to "für".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeDeDe));
+
+        typedWord = "fur";
+        // For this test case, we consider "fur" is a distracter to "für".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeDeDe));
+
+        final Locale localeFrFr = new Locale("fr", "FR");
+
+        typedWord = "a";
+        // For this test case, we consider "a" is a distracter to "à".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr));
+
+        typedWord = "à";
+        // For this test case, we consider "à" is not a distracter to any word in dictionaries.
+        assertFalse(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr));
+
+        typedWord = "etre";
+        // For this test case, we consider "etre" is a distracter to "être".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr));
+
+        typedWord = "États-unis";
+        // For this test case, we consider "États-unis" is a distracter to "États-Unis".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr));
+
+        typedWord = "ÉtatsUnis";
+        // For this test case, we consider "ÉtatsUnis" is a distracter to "États-Unis".
+        assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
+                EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr));
     }
 }