Merge "Simplify DictionaryFacilitatorForSuggest."
diff --git a/java/res/values-fr/donottranslate-config-spacing-and-punctuations.xml b/java/res/values-fr/donottranslate-config-spacing-and-punctuations.xml
index d72f72b..5a49142 100644
--- a/java/res/values-fr/donottranslate-config-spacing-and-punctuations.xml
+++ b/java/res/values-fr/donottranslate-config-spacing-and-punctuations.xml
@@ -22,6 +22,8 @@
     <string name="symbols_preceded_by_space">([{&amp;;:!?</string>
     <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
     <string name="symbols_followed_by_space">.,;:!?)]}&amp;</string>
+    <!-- Symbols that behave like a single punctuation when typed next to each other -->
+    <string name="symbols_clustering_together">!?</string>
     <!-- Symbols that separate words -->
     <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
     <string name="symbols_word_separators">"&#x0009;&#x0020;&#x000A;&#x00A0;"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
diff --git a/java/res/values-hi/strings-talkback-descriptions.xml b/java/res/values-hi/strings-talkback-descriptions.xml
index a1468da..df95119 100644
--- a/java/res/values-hi/strings-talkback-descriptions.xml
+++ b/java/res/values-hi/strings-talkback-descriptions.xml
@@ -20,25 +20,25 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="spoken_use_headphones" msgid="4313642710742229868">"ज़ोर से बोली जाने वाली पासवर्ड कुंजियां सुनने के लिए हैडसेट प्‍लग करें."</string>
+    <string name="spoken_use_headphones" msgid="4313642710742229868">"जोर से बोली जाने वाली पासवर्ड कुंजियां सुनने के लिए हैडसेट प्‍लग करें."</string>
     <string name="spoken_current_text_is" msgid="4240549866156675799">"वर्तमान पाठ %s है"</string>
     <string name="spoken_no_text_entered" msgid="1711276837961785646">"कोई पाठ नहीं डाला गया"</string>
     <string name="spoken_auto_correct" msgid="8989324692167993804">"<xliff:g id="KEY_NAME">%1$s</xliff:g> <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> को सुधार कर <xliff:g id="CORRECTED_WORD">%3$s</xliff:g> करता है"</string>
     <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"<xliff:g id="KEY_NAME">%1$s</xliff:g> स्वत: सुधार करता है"</string>
     <string name="spoken_description_unknown" msgid="2382510329910793539">"कुंजी कोड %d"</string>
-    <string name="spoken_description_shift" msgid="7209798151676638728">"Shift"</string>
+    <string name="spoken_description_shift" msgid="7209798151676638728">"शिफ़्ट"</string>
     <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"शिफ़्ट चालू (अक्षम करने के लिए टैप करें)"</string>
     <string name="spoken_description_caps_lock" msgid="5020582161133170892">"कैप्स लॉक चालू (अक्षम करने के लिए टैप करें)"</string>
-    <string name="spoken_description_delete" msgid="3878902286264983302">"Delete"</string>
+    <string name="spoken_description_delete" msgid="3878902286264983302">"डिलीट"</string>
     <string name="spoken_description_to_symbol" msgid="8244903740201126590">"प्रतीक"</string>
     <string name="spoken_description_to_alpha" msgid="4081215210530031950">"अक्षर"</string>
     <string name="spoken_description_to_numeric" msgid="4560261331530795682">"संख्‍याएं"</string>
     <string name="spoken_description_settings" msgid="7281251004003143204">"सेटिंग"</string>
-    <string name="spoken_description_tab" msgid="8210782459446866716">"Tab"</string>
+    <string name="spoken_description_tab" msgid="8210782459446866716">"टैब"</string>
     <string name="spoken_description_space" msgid="5908716896642059145">"स्पेस"</string>
     <string name="spoken_description_mic" msgid="6153138783813452464">"ध्‍वनि इनपुट"</string>
     <string name="spoken_description_emoji" msgid="7990051553008088470">"ईमोजी"</string>
-    <string name="spoken_description_return" msgid="3183692287397645708">"Return"</string>
+    <string name="spoken_description_return" msgid="3183692287397645708">"रिटर्न"</string>
     <string name="spoken_description_search" msgid="5099937658231911288">"खोजें"</string>
     <string name="spoken_description_dot" msgid="5644176501632325560">"डॉट"</string>
     <string name="spoken_description_language_switch" msgid="6818666779313544553">"भाषा स्विच करें"</string>
diff --git a/java/res/values-hy-rAM/strings-talkback-descriptions.xml b/java/res/values-hy-rAM/strings-talkback-descriptions.xml
index d8f7d27..e5b1ce6 100644
--- a/java/res/values-hy-rAM/strings-talkback-descriptions.xml
+++ b/java/res/values-hy-rAM/strings-talkback-descriptions.xml
@@ -23,7 +23,7 @@
     <string name="spoken_use_headphones" msgid="4313642710742229868">"Միացրեք ականջակալը՝ բարձրաձայն արտասանվող գաղտնաբառը լսելու համար:"</string>
     <string name="spoken_current_text_is" msgid="4240549866156675799">"Տվյալ տեքստը %s է"</string>
     <string name="spoken_no_text_entered" msgid="1711276837961785646">"Տեքստ չի մուտքագրվել"</string>
-    <string name="spoken_auto_correct" msgid="8989324692167993804">"<xliff:g id="KEY_NAME">%1$s</xliff:g>-ը շտկում է <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>-ը և դարձնում <xliff:g id="CORRECTED_WORD">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct" msgid="8989324692167993804">"<xliff:g id="KEY_NAME">%1$s</xliff:g>-ը շտկում է <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g>-ը՝ դարձնելով <xliff:g id="CORRECTED_WORD">%3$s</xliff:g>"</string>
     <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"<xliff:g id="KEY_NAME">%1$s</xliff:g> ստեղնը ինքնաշտկում է կատարում"</string>
     <string name="spoken_description_unknown" msgid="2382510329910793539">"Բանալու կոդը՝ %d"</string>
     <string name="spoken_description_shift" msgid="7209798151676638728">"Shift"</string>
@@ -34,8 +34,8 @@
     <string name="spoken_description_to_alpha" msgid="4081215210530031950">"Տառեր"</string>
     <string name="spoken_description_to_numeric" msgid="4560261331530795682">"Թվեր"</string>
     <string name="spoken_description_settings" msgid="7281251004003143204">"Կարգավորումներ"</string>
-    <string name="spoken_description_tab" msgid="8210782459446866716">"Թաբ"</string>
-    <string name="spoken_description_space" msgid="5908716896642059145">"Բացակ"</string>
+    <string name="spoken_description_tab" msgid="8210782459446866716">"Tab"</string>
+    <string name="spoken_description_space" msgid="5908716896642059145">"Բացատ"</string>
     <string name="spoken_description_mic" msgid="6153138783813452464">"Ձայնային մուտքագրում"</string>
     <string name="spoken_description_emoji" msgid="7990051553008088470">"Զմայլիկներ"</string>
     <string name="spoken_description_return" msgid="3183692287397645708">"Վերադառնալ"</string>
@@ -59,7 +59,7 @@
     <string name="keyboard_mode_im" msgid="3812086215529493501">"նամակագրություն"</string>
     <string name="keyboard_mode_number" msgid="5395042245837996809">"թվեր"</string>
     <string name="keyboard_mode_phone" msgid="2486230278064523665">"հեռախոսահամար"</string>
-    <string name="keyboard_mode_text" msgid="9138789594969187494">"տեքստային հաղորդագրություն"</string>
+    <string name="keyboard_mode_text" msgid="9138789594969187494">"տեքստ"</string>
     <string name="keyboard_mode_time" msgid="8558297845514402675">"ժամանակ"</string>
     <string name="keyboard_mode_url" msgid="8072011652949962550">"URL"</string>
     <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"Վերջինները"</string>
diff --git a/java/res/values-zu/strings-talkback-descriptions.xml b/java/res/values-zu/strings-talkback-descriptions.xml
index d30ec8b..e01f734 100644
--- a/java/res/values-zu/strings-talkback-descriptions.xml
+++ b/java/res/values-zu/strings-talkback-descriptions.xml
@@ -26,12 +26,12 @@
     <string name="spoken_auto_correct" msgid="8989324692167993804">"I-<xliff:g id="KEY_NAME">%1$s</xliff:g> ilungisa i-<xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> kube yi-<xliff:g id="CORRECTED_WORD">%3$s</xliff:g>"</string>
     <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"I-<xliff:g id="KEY_NAME">%1$s</xliff:g> yenza ukulungisa okuzenzakalelayo"</string>
     <string name="spoken_description_unknown" msgid="2382510329910793539">"Ikhodi yokhiye %d"</string>
-    <string name="spoken_description_shift" msgid="7209798151676638728">"I-Shift"</string>
+    <string name="spoken_description_shift" msgid="7209798151676638728">"U-Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"U-Shift uvuliwe (thepha ukuwuvimbela)"</string>
     <string name="spoken_description_caps_lock" msgid="5020582161133170892">"Ofeleba bavuliwe (thepha ukubavimbela)"</string>
     <string name="spoken_description_delete" msgid="3878902286264983302">"Susa"</string>
     <string name="spoken_description_to_symbol" msgid="8244903740201126590">"Amasimbuli"</string>
-    <string name="spoken_description_to_alpha" msgid="4081215210530031950">"Imbhalo"</string>
+    <string name="spoken_description_to_alpha" msgid="4081215210530031950">"Izinhlamvu"</string>
     <string name="spoken_description_to_numeric" msgid="4560261331530795682">"Izinombolo"</string>
     <string name="spoken_description_settings" msgid="7281251004003143204">"Izilungiselelo"</string>
     <string name="spoken_description_tab" msgid="8210782459446866716">"Ithebhu"</string>
@@ -65,7 +65,7 @@
     <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"Okwakamuva"</string>
     <string name="spoken_descrption_emoji_category_people" msgid="8414196269847492817">"Abantu"</string>
     <string name="spoken_descrption_emoji_category_objects" msgid="6116297906606195278">"Izinto"</string>
-    <string name="spoken_descrption_emoji_category_nature" msgid="5018340512472354640">"Awendalo"</string>
+    <string name="spoken_descrption_emoji_category_nature" msgid="5018340512472354640">"Indalo"</string>
     <string name="spoken_descrption_emoji_category_places" msgid="1163315840948545317">"Izindawo"</string>
     <string name="spoken_descrption_emoji_category_symbols" msgid="474680659024880601">"Amasimbuli"</string>
     <string name="spoken_descrption_emoji_category_emoticons" msgid="456737544787823539">"Izithombe-mzwelo"</string>
diff --git a/java/res/values/donottranslate-config-spacing-and-punctuations.xml b/java/res/values/donottranslate-config-spacing-and-punctuations.xml
index 1be5cf8..2faf578 100644
--- a/java/res/values/donottranslate-config-spacing-and-punctuations.xml
+++ b/java/res/values/donottranslate-config-spacing-and-punctuations.xml
@@ -26,6 +26,8 @@
     <string name="symbols_preceded_by_space">([{&amp;</string>
     <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
     <string name="symbols_followed_by_space">.,;:!?)]}&amp;</string>
+    <!-- Symbols that behave like a single punctuation when typed next to each other -->
+    <string name="symbols_clustering_together"></string>
     <!-- Symbols that separate words -->
     <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
     <string name="symbols_word_separators">"&#x0009;&#x0020;&#x000A;&#x00A0;"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
diff --git a/java/res/values/strings-talkback-descriptions.xml b/java/res/values/strings-talkback-descriptions.xml
index 9c1e652..4ffca10 100644
--- a/java/res/values/strings-talkback-descriptions.xml
+++ b/java/res/values/strings-talkback-descriptions.xml
@@ -74,11 +74,10 @@
     <string name="spoken_description_shiftmode_on">Shift enabled</string>
     <!-- Spoken feedback after turning "Caps lock" mode on. -->
     <string name="spoken_description_shiftmode_locked">Caps lock enabled</string>
-    <!-- Spoken feedback after turning "Shift" mode off. -->
-    <string name="spoken_description_shiftmode_off">Shift disabled</string>
-
     <!-- Spoken feedback after changing to the symbols keyboard. -->
     <string name="spoken_description_mode_symbol">Symbols mode</string>
+    <!-- Spoken feedback after changing to the symbols shift keyboard. -->
+    <string name="spoken_description_mode_symbol_shift">Symbols shift mode</string>
     <!-- Spoken feedback after changing to the alphanumeric keyboard. -->
     <string name="spoken_description_mode_alpha">Letters mode</string>
     <!-- Spoken feedback after changing to the phone dialer keyboard. -->
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 3e3bedf..7d86dbd 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -157,13 +157,6 @@
                 android:summary="@string/include_other_imes_in_language_switch_list_summary"
                 android:persistent="true"
                 android:defaultValue="false" />
-            <!-- Values for popup dismiss delay are added programmatically -->
-            <CheckBoxPreference
-                android:key="pref_sliding_key_input_preview"
-                android:title="@string/sliding_key_input_preview"
-                android:summary="@string/sliding_key_input_preview_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
             <ListPreference
                 android:key="pref_keyboard_layout_20110916"
                 android:title="@string/keyboard_color_scheme"
@@ -180,12 +173,6 @@
                 android:key="pref_key_preview_popup_dismiss_delay"
                 android:title="@string/key_preview_popup_dismiss_delay" />
             <com.android.inputmethod.latin.settings.SeekBarDialogPreference
-                android:key="pref_key_longpress_timeout"
-                android:title="@string/prefs_key_longpress_timeout_settings"
-                latin:minValue="@integer/config_min_longpress_timeout"
-                latin:maxValue="@integer/config_max_longpress_timeout"
-                latin:stepValue="@integer/config_longpress_timeout_step" />
-            <com.android.inputmethod.latin.settings.SeekBarDialogPreference
                 android:key="pref_vibration_duration_settings"
                 android:title="@string/prefs_keypress_vibration_duration_settings"
                 latin:maxValue="@integer/config_max_vibration_duration" />
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index bb6a641..c333b07 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -41,6 +41,18 @@
         android:title="@string/prefs_usability_study_mode"
         android:persistent="true"
         android:defaultValue="false" />
+    <CheckBoxPreference
+        android:key="pref_sliding_key_input_preview"
+        android:title="@string/sliding_key_input_preview"
+        android:summary="@string/sliding_key_input_preview_summary"
+        android:persistent="true"
+        android:defaultValue="true" />
+    <com.android.inputmethod.latin.settings.SeekBarDialogPreference
+        android:key="pref_key_longpress_timeout"
+        android:title="@string/prefs_key_longpress_timeout_settings"
+        latin:minValue="@integer/config_min_longpress_timeout"
+        latin:maxValue="@integer/config_max_longpress_timeout"
+        latin:stepValue="@integer/config_longpress_timeout_step" />
     <com.android.inputmethod.latin.settings.SeekBarDialogPreference
         android:key="pref_key_preview_show_up_start_scale"
         android:title="@string/prefs_key_popup_show_up_start_scale_settings"
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index bc094b1..d50dd3e 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -68,7 +68,6 @@
         // These only need to be initialized if the kill switch is off.
         sInstance.initInternal(context);
         KeyCodeDescriptionMapper.init();
-        AccessibleKeyboardViewProxy.init(context);
     }
 
     public static AccessibilityUtils getInstance() {
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
similarity index 77%
rename from java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
rename to java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
index 322127a..1092942 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
@@ -36,9 +36,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
-public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
-    private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
-
+public final class MainKeyboardAccessibilityDelegate extends AccessibilityDelegateCompat {
     /** Map of keyboard modes to resource IDs. */
     private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
 
@@ -54,9 +52,9 @@
         KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url);
     }
 
-    private MainKeyboardView mView;
+    private final MainKeyboardView mView;
     private Keyboard mKeyboard;
-    private AccessibilityEntityProvider mAccessibilityNodeProvider;
+    private MainKeyboardAccessibilityNodeProvider mAccessibilityNodeProvider;
 
     private Key mLastHoverKey = null;
 
@@ -69,46 +67,14 @@
     private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     private static final int KEYBOARD_IS_HIDDEN = -1;
 
-    public static void init(final Context context) {
-        sInstance.initInternal(context);
-    }
-
-    public static AccessibleKeyboardViewProxy getInstance() {
-        return sInstance;
-    }
-
-    private AccessibleKeyboardViewProxy() {
-        // Not publicly instantiable.
-    }
-
-    private void initInternal(final Context context) {
+    public MainKeyboardAccessibilityDelegate(final MainKeyboardView view) {
+        final Context context = view.getContext();
         mEdgeSlop = context.getResources().getDimensionPixelSize(
                 R.dimen.config_accessibility_edge_slop);
-    }
-
-    /**
-     * Sets the view wrapped by this proxy.
-     *
-     * @param view The view to wrap.
-     */
-    public void setView(final MainKeyboardView view) {
-        if (view == null) {
-            // Ignore null views.
-            return;
-        }
         mView = view;
 
         // Ensure that the view has an accessibility delegate.
         ViewCompat.setAccessibilityDelegate(view, this);
-
-        if (mAccessibilityNodeProvider == null) {
-            return;
-        }
-        mAccessibilityNodeProvider.setView(view);
-
-        // Since this class is constructed lazily, we might not get a subsequent
-        // call to setKeyboard() and therefore need to call it now.
-        setKeyboard(view.getKeyboard());
     }
 
     /**
@@ -136,12 +102,19 @@
             return;
         }
         // Announce the language name only when the language is changed.
-        if (lastKeyboard == null || !lastKeyboard.mId.mSubtype.equals(keyboard.mId.mSubtype)) {
+        if (lastKeyboard == null || !keyboard.mId.mSubtype.equals(lastKeyboard.mId.mSubtype)) {
             announceKeyboardLanguage(keyboard);
+            return;
         }
         // Announce the mode only when the mode is changed.
-        if (lastKeyboardMode != keyboard.mId.mMode) {
+        if (keyboard.mId.mMode != lastKeyboardMode) {
             announceKeyboardMode(keyboard);
+            return;
+        }
+        // Announce the keyboard type only when the type is changed.
+        if (keyboard.mId.mElementId != lastKeyboard.mId.mElementId) {
+            announceKeyboardType(keyboard, lastKeyboard);
+            return;
         }
     }
 
@@ -149,9 +122,6 @@
      * Called when the keyboard is hidden and accessibility is enabled.
      */
     public void onHideWindow() {
-        if (mView == null) {
-            return;
-        }
         announceKeyboardHidden();
         mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     }
@@ -174,9 +144,8 @@
      * @param keyboard The new keyboard.
      */
     private void announceKeyboardMode(final Keyboard keyboard) {
-        final int mode = keyboard.mId.mMode;
         final Context context = mView.getContext();
-        final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(mode);
+        final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode);
         if (modeTextResId == 0) {
             return;
         }
@@ -186,6 +155,50 @@
     }
 
     /**
+     * Announces which type of keyboard is being displayed.
+     *
+     * @param keyboard The new keyboard.
+     * @param lastKeyboard The last keyboard.
+     */
+    private void announceKeyboardType(final Keyboard keyboard, final Keyboard lastKeyboard) {
+        final int lastElementId = lastKeyboard.mId.mElementId;
+        final int resId;
+        switch (keyboard.mId.mElementId) {
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET:
+            if (lastElementId == KeyboardId.ELEMENT_ALPHABET
+                    || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+                return;
+            }
+            resId = R.string.spoken_description_mode_alpha;
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+            resId = R.string.spoken_description_shiftmode_on;
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            resId = R.string.spoken_description_shiftmode_locked;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS:
+            resId = R.string.spoken_description_mode_symbol;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_mode_symbol_shift;
+            break;
+        case KeyboardId.ELEMENT_PHONE:
+            resId = R.string.spoken_description_mode_phone;
+            break;
+        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
+            resId = R.string.spoken_description_mode_phone_shift;
+            break;
+        default:
+            return;
+        }
+        final String text = mView.getContext().getString(resId);
+        sendWindowStateChanged(text);
+    }
+
+    /**
      * Announces that the keyboard has been hidden.
      */
     private void announceKeyboardHidden() {
@@ -214,7 +227,7 @@
     }
 
     /**
-     * Proxy method for View.getAccessibilityNodeProvider(). This method is called in SDK
+     * Delegate method for View.getAccessibilityNodeProvider(). This method is called in SDK
      * version 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) and higher to obtain the virtual
      * node hierarchy provider.
      *
@@ -222,10 +235,7 @@
      * @return The accessibility node provider for the current keyboard.
      */
     @Override
-    public AccessibilityEntityProvider getAccessibilityNodeProvider(final View host) {
-        if (mView == null) {
-            return null;
-        }
+    public MainKeyboardAccessibilityNodeProvider getAccessibilityNodeProvider(final View host) {
         return getAccessibilityNodeProvider();
     }
 
@@ -238,10 +248,6 @@
      * @return {@code true} if the event is handled
      */
     public boolean dispatchHoverEvent(final MotionEvent event, final KeyDetector keyDetector) {
-        if (mView == null) {
-            return false;
-        }
-
         final int x = (int) event.getX();
         final int y = (int) event.getY();
         final Key previousKey = mLastHoverKey;
@@ -275,14 +281,14 @@
     }
 
     /**
-     * @return A lazily-instantiated node provider for this view proxy.
+     * @return A lazily-instantiated node provider for this view delegate.
      */
-    private AccessibilityEntityProvider getAccessibilityNodeProvider() {
+    private MainKeyboardAccessibilityNodeProvider getAccessibilityNodeProvider() {
         // Instantiate the provide only when requested. Since the system
         // will call this method multiple times it is a good practice to
         // cache the provider instance.
         if (mAccessibilityNodeProvider == null) {
-            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView);
+            mAccessibilityNodeProvider = new MainKeyboardAccessibilityNodeProvider(mView);
         }
         return mAccessibilityNodeProvider;
     }
@@ -301,7 +307,7 @@
     }
 
     /**
-     * Simulates a key press by injecting touch event into the keyboard view.
+     * Simulates a key press by injecting touch an event into the keyboard view.
      * This avoids the complexity of trackers and listeners within the keyboard.
      *
      * @param key The key to press.
@@ -318,7 +324,7 @@
     }
 
     /**
-     * Simulates a key release by injecting touch event into the keyboard view.
+     * Simulates a key release by injecting touch an event into the keyboard view.
      * This avoids the complexity of trackers and listeners within the keyboard.
      *
      * @param key The key to release.
@@ -367,7 +373,7 @@
         if (key == null) {
             return false;
         }
-        final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
+        final MainKeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
 
         switch (event.getAction()) {
         case MotionEvent.ACTION_HOVER_ENTER:
@@ -383,72 +389,4 @@
         }
         return true;
     }
-
-    /**
-     * Notifies the user of changes in the keyboard shift state.
-     */
-    public void notifyShiftState() {
-        if (mView == null || mKeyboard == null) {
-            return;
-        }
-
-        final KeyboardId keyboardId = mKeyboard.mId;
-        final int elementId = keyboardId.mElementId;
-        final Context context = mView.getContext();
-        final CharSequence text;
-
-        switch (elementId) {
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
-            text = context.getText(R.string.spoken_description_shiftmode_locked);
-            break;
-        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
-        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
-            text = context.getText(R.string.spoken_description_shiftmode_on);
-            break;
-        default:
-            text = context.getText(R.string.spoken_description_shiftmode_off);
-        }
-        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
-    }
-
-    /**
-     * Notifies the user of changes in the keyboard symbols state.
-     */
-    public void notifySymbolsState() {
-        if (mView == null || mKeyboard == null) {
-            return;
-        }
-
-        final KeyboardId keyboardId = mKeyboard.mId;
-        final int elementId = keyboardId.mElementId;
-        final Context context = mView.getContext();
-        final int resId;
-
-        switch (elementId) {
-        case KeyboardId.ELEMENT_ALPHABET:
-        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
-        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
-            resId = R.string.spoken_description_mode_alpha;
-            break;
-        case KeyboardId.ELEMENT_SYMBOLS:
-        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
-            resId = R.string.spoken_description_mode_symbol;
-            break;
-        case KeyboardId.ELEMENT_PHONE:
-            resId = R.string.spoken_description_mode_phone;
-            break;
-        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
-            resId = R.string.spoken_description_mode_phone_shift;
-            break;
-        default:
-            return;
-        }
-
-        final String text = context.getString(resId);
-        AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
-    }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
similarity index 95%
rename from java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
rename to java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
index ec1ab35..f69d316 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityNodeProvider.java
@@ -47,8 +47,8 @@
  * virtual views, thus conveying their logical structure.
  * </p>
  */
-public final class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
-    private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
+public final class MainKeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat {
+    private static final String TAG = MainKeyboardAccessibilityNodeProvider.class.getSimpleName();
     private static final int UNDEFINED = Integer.MIN_VALUE;
 
     private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
@@ -64,23 +64,14 @@
     private int mAccessibilityFocusedView = UNDEFINED;
 
     /** The current keyboard view. */
-    private KeyboardView mKeyboardView;
+    private final KeyboardView mKeyboardView;
 
     /** The current keyboard. */
     private Keyboard mKeyboard;
 
-    public AccessibilityEntityProvider(final KeyboardView keyboardView) {
+    public MainKeyboardAccessibilityNodeProvider(final KeyboardView keyboardView) {
         mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance();
         mAccessibilityUtils = AccessibilityUtils.getInstance();
-        setView(keyboardView);
-    }
-
-    /**
-     * Sets the keyboard view represented by this node provider.
-     *
-     * @param keyboardView The keyboard view to represent.
-     */
-    public void setView(final KeyboardView keyboardView) {
         mKeyboardView = keyboardView;
         updateParentLocation();
 
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index a80c3fe..18b3a60 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -28,6 +28,12 @@
     private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod(
             InputMethodManager.class, "switchToNextInputMethod", IBinder.class, Boolean.TYPE);
 
+    // Note that InputMethodManager.shouldOfferSwitchingToNextInputMethod() has been introduced
+    // in API level 19 (Build.VERSION_CODES.KITKAT).
+    private static final Method METHOD_shouldOfferSwitchingToNextInputMethod =
+            CompatUtils.getMethod(InputMethodManager.class,
+                    "shouldOfferSwitchingToNextInputMethod", IBinder.class);
+
     public final InputMethodManager mImm;
 
     public InputMethodManagerCompatWrapper(final Context context) {
@@ -38,4 +44,9 @@
         return (Boolean)CompatUtils.invoke(mImm, false /* defaultValue */,
                 METHOD_switchToNextInputMethod, token, onlyCurrentIme);
     }
+
+    public boolean shouldOfferSwitchingToNextInputMethod(final IBinder token) {
+        return (Boolean)CompatUtils.invoke(mImm, false /* defaultValue */,
+                METHOD_shouldOfferSwitchingToNextInputMethod, token);
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index dcf7f74..589e99e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -26,7 +26,6 @@
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
 import com.android.inputmethod.keyboard.internal.KeyboardState;
@@ -123,7 +122,7 @@
         builder.setOptions(
                 mSubtypeSwitcher.isShortcutImeEnabled(),
                 settingsValues.mShowsVoiceInputKey,
-                settingsValues.isLanguageSwitchKeyEnabled());
+                mLatinIME.shouldShowLanguageSwitchKey());
         mKeyboardLayoutSet = builder.build();
         mCurrentSettingsValues = settingsValues;
         try {
@@ -148,6 +147,7 @@
 
     public void onHideWindow() {
         mIsAutoCorrectionActive = false;
+        mKeyboardView.onHideWindow();
     }
 
     private void setKeyboard(final Keyboard keyboard) {
@@ -353,11 +353,6 @@
         mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled(
                 isHardwareAcceleratedDrawingEnabled);
         mEmojiPalettesView.setKeyboardActionListener(mLatinIME);
-
-        // This always needs to be set since the accessibility state can
-        // potentially change without the input view being re-created.
-        AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
-
         return mCurrentInputView;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index ecef8cc..e24d4e9 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -39,7 +39,7 @@
 import android.widget.TextView;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
+import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.keyboard.internal.DrawingHandler;
 import com.android.inputmethod.keyboard.internal.DrawingPreviewPlacerView;
@@ -179,6 +179,8 @@
     private final DrawingHandler mDrawingHandler =
             new DrawingHandler(this);
 
+    private final MainKeyboardAccessibilityDelegate mAccessibilityDelegate;
+
     public MainKeyboardView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.mainKeyboardViewStyle);
     }
@@ -278,6 +280,8 @@
 
         mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension(
                 R.dimen.config_language_on_spacebar_horizontal_margin);
+
+        mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this);
     }
 
     @Override
@@ -404,9 +408,7 @@
             ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
         }
 
-        // This always needs to be set since the accessibility state can
-        // potentially change without the keyboard being set again.
-        AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
+        mAccessibilityDelegate.setKeyboard(keyboard);
     }
 
     /**
@@ -769,6 +771,10 @@
         mMoreKeysKeyboardCache.clear();
     }
 
+    public void onHideWindow() {
+        mAccessibilityDelegate.onHideWindow();
+    }
+
     /**
      * Receives hover events from the input framework.
      *
@@ -779,8 +785,7 @@
     @Override
     public boolean dispatchHoverEvent(final MotionEvent event) {
         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            return AccessibleKeyboardViewProxy.getInstance().dispatchHoverEvent(
-                    event, mKeyDetector);
+            return mAccessibilityDelegate.dispatchHoverEvent(event, mKeyDetector);
         }
 
         // Reflection doesn't support calling superclass methods.
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index b79b99e..cda429e 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -516,12 +516,12 @@
      */
     private final void asyncReloadDictionary() {
         if (mIsReloading.compareAndSet(false, true)) {
-            mNeedsToReload = false;
             asyncExecuteTaskWithWriteLock(new Runnable() {
                 @Override
                 public void run() {
                     try {
-                        if (!mDictFile.exists() || haveContentsChanged()) {
+                        // TODO: Quit checking contents in ExpandableBinaryDictionary.
+                        if (!mDictFile.exists() || (mNeedsToReload && haveContentsChanged())) {
                             // If the dictionary file does not exist or contents have been updated,
                             // generate a new one.
                             createNewDictionaryLocked();
@@ -529,6 +529,7 @@
                             // Otherwise, load the existing dictionary.
                             loadBinaryDictionaryLocked();
                         }
+                        mNeedsToReload = false;
                         if (mBinaryDictionary != null && !(isValidDictionaryLocked()
                                 // TODO: remove the check below
                                 && matchesExpectedBinaryDictFormatVersionForThisType(
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 27790d6..d64a1a6 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -55,7 +55,6 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
@@ -1002,10 +1001,6 @@
         LatinImeLogger.commit();
         mKeyboardSwitcher.onHideWindow();
 
-        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
-            AccessibleKeyboardViewProxy.getInstance().onHideWindow();
-        }
-
         if (TRACE) Debug.stopMethodTracing();
         if (isShowingOptionDialog()) {
             mOptionsDialog.dismiss();
@@ -1230,7 +1225,7 @@
     // TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
     public void switchToNextSubtype() {
         final IBinder token = getWindow().getWindow().getAttributes().token;
-        if (mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList) {
+        if (shouldSwitchToOtherInputMethods()) {
             mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
             return;
         }
@@ -1597,18 +1592,6 @@
     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(),
                 getCurrentRecapitalizeState());
-
-        // If accessibility is on, ensure the user receives keyboard state updates.
-        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
-            switch (primaryCode) {
-            case Constants.CODE_SHIFT:
-                AccessibleKeyboardViewProxy.getInstance().notifyShiftState();
-                break;
-            case Constants.CODE_SWITCH_ALPHA_SYMBOL:
-                AccessibleKeyboardViewProxy.getInstance().notifySymbolsState();
-                break;
-            }
-        }
     }
 
     private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
@@ -1800,4 +1783,28 @@
         p.println(settingsValues.dump());
         // TODO: Dump all settings values
     }
+
+    public boolean shouldSwitchToOtherInputMethods() {
+        // TODO: Revisit here to reorganize the settings. Probably we can/should use different
+        // strategy once the implementation of
+        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well.
+        final boolean fallbackValue = mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList;
+        final IBinder token = getWindow().getWindow().getAttributes().token;
+        if (token == null) {
+            return fallbackValue;
+        }
+        return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue);
+    }
+
+    public boolean shouldShowLanguageSwitchKey() {
+        // TODO: Revisit here to reorganize the settings. Probably we can/should use different
+        // strategy once the implementation of
+        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is defined well.
+        final boolean fallbackValue = mSettings.getCurrent().isLanguageSwitchKeyEnabled();
+        final IBinder token = getWindow().getWindow().getAttributes().token;
+        if (token == null) {
+            return fallbackValue;
+        }
+        return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 630a036..64cc562 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.os.Build;
 import android.os.IBinder;
 import android.preference.PreferenceManager;
 import android.util.Log;
@@ -406,4 +407,24 @@
         mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
         mInputMethodInfoCache.clear();
     }
+
+    public boolean shouldOfferSwitchingToNextInputMethod(final IBinder binder,
+            boolean defaultValue) {
+        // Use the default value instead on Jelly Bean MR2 and previous, where
+        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} isn't yet available.
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+            return defaultValue;
+        }
+        // Use the default value instead on KitKat as well, where
+        // {@link InputMethodManager#shouldOfferSwitchingToNextInputMethod} is still just a stub to
+        // return true always.
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
+            // Make sure this is actually KitKat.
+            // TODO: Consider to remove this check once the *next* version becomes available.
+            if (Build.VERSION.CODENAME.equals("REL")) {
+                return defaultValue;
+            }
+        }
+        return mImmWrapper.shouldOfferSwitchingToNextInputMethod(binder);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index b21f300..2006a5c 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -50,23 +50,15 @@
     // to auto-correct, so we set this to the highest frequency that won't, i.e. 14.
     private static final int USER_DICT_SHORTCUT_FREQUENCY = 14;
 
-    // TODO: use Words.SHORTCUT when we target JellyBean or above
-    final static String SHORTCUT = "shortcut";
-    private static final String[] PROJECTION_QUERY;
-    static {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            PROJECTION_QUERY = new String[] {
-                Words.WORD,
-                SHORTCUT,
-                Words.FREQUENCY,
-            };
-        } else {
-            PROJECTION_QUERY = new String[] {
-                Words.WORD,
-                Words.FREQUENCY,
-            };
-        }
-    }
+    private static final String[] PROJECTION_QUERY_WITH_SHORTCUT = new String[] {
+        Words.WORD,
+        Words.SHORTCUT,
+        Words.FREQUENCY,
+    };
+    private static final String[] PROJECTION_QUERY_WITHOUT_SHORTCUT = new String[] {
+        Words.WORD,
+        Words.FREQUENCY,
+    };
 
     private static final String NAME = "userunigram";
 
@@ -179,10 +171,29 @@
         } else {
             requestArguments = localeElements;
         }
+        final String requestString = request.toString();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            try {
+                addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString,
+                        requestArguments);
+            } catch (IllegalArgumentException e) {
+                // This may happen on some non-compliant devices where the declared API is JB+ but
+                // the SHORTCUT column is not present for some reason.
+                addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
+                        requestArguments);
+            }
+        } else {
+            addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
+                    requestArguments);
+        }
+    }
+
+    private void addWordsFromProjectionLocked(final String[] query, String request,
+            final String[] requestArguments) throws IllegalArgumentException {
         Cursor cursor = null;
         try {
             cursor = mContext.getContentResolver().query(
-                Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
+                    Words.CONTENT_URI, query, request, requestArguments, null);
             addWordsLocked(cursor);
         } catch (final SQLiteException e) {
             Log.e(TAG, "SQLiteException in the remote User dictionary process.", e);
@@ -239,7 +250,7 @@
         if (cursor == null) return;
         if (cursor.moveToFirst()) {
             final int indexWord = cursor.getColumnIndex(Words.WORD);
-            final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(SHORTCUT) : 0;
+            final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(Words.SHORTCUT) : 0;
             final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
             while (!cursor.isAfterLast()) {
                 final String word = cursor.getString(indexWord);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index d2100d4..75432fb 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -784,11 +784,11 @@
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
         final int codePoint = inputTransaction.mEvent.mCodePoint;
+        final SettingsValues settingsValues = inputTransaction.mSettingsValues;
         boolean didAutoCorrect = false;
         // We avoid sending spaces in languages without spaces if we were composing.
         final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
-                && !inputTransaction.mSettingsValues.mSpacingAndPunctuations
-                        .mCurrentLanguageHasSpaces
+                && !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
                 && mWordComposer.isComposingWord();
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
@@ -798,13 +798,13 @@
         }
         // isComposingWord() may have changed since we stored wasComposing
         if (mWordComposer.isComposingWord()) {
-            if (inputTransaction.mSettingsValues.mCorrectionEnabled) {
+            if (settingsValues.mCorrectionEnabled) {
                 final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
                         : StringUtils.newSingleCodePointString(codePoint);
-                commitCurrentAutoCorrection(inputTransaction.mSettingsValues, separator, handler);
+                commitCurrentAutoCorrection(settingsValues, separator, handler);
                 didAutoCorrect = true;
             } else {
-                commitTyped(inputTransaction.mSettingsValues,
+                commitTyped(settingsValues,
                         StringUtils.newSingleCodePointString(codePoint));
             }
         }
@@ -821,20 +821,23 @@
             // Double quotes behave like they are usually preceded by space iff we are
             // not inside a double quote or after a digit.
             needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit;
+        } else if (settingsValues.mSpacingAndPunctuations.isClusteringSymbol(codePoint)
+                && settingsValues.mSpacingAndPunctuations.isClusteringSymbol(
+                        mConnection.getCodePointBeforeCursor())) {
+            needsPrecedingSpace = false;
         } else {
-            needsPrecedingSpace = inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(
-                    codePoint);
+            needsPrecedingSpace = settingsValues.isUsuallyPrecededBySpace(codePoint);
         }
 
         if (needsPrecedingSpace) {
-            promotePhantomSpace(inputTransaction.mSettingsValues);
+            promotePhantomSpace(settingsValues);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
         }
 
         if (!shouldAvoidSendingCode) {
-            sendKeyCodePoint(inputTransaction.mSettingsValues, codePoint);
+            sendKeyCodePoint(settingsValues, codePoint);
         }
 
         if (Constants.CODE_SPACE == codePoint) {
@@ -852,7 +855,7 @@
                 swapSwapperAndSpace(inputTransaction);
                 mSpaceState = SpaceState.SWAP_PUNCTUATION;
             } else if ((SpaceState.PHANTOM == inputTransaction.mSpaceState
-                    && inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint))
+                    && settingsValues.isUsuallyFollowedBySpace(codePoint))
                     || (Constants.CODE_DOUBLE_QUOTE == codePoint
                             && isInsideDoubleQuoteOrAfterDigit)) {
                 // If we are in phantom space state, and the user presses a separator, we want to
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 11d3692..c4c1234 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -55,6 +55,8 @@
     private static final String PREF_DUMP_USER_DICT = "dump_user_dict";
     private static final String PREF_DUMP_USER_HISTORY_DICT = "dump_user_history_dict";
     private static final String PREF_DUMP_PERSONALIZATION_DICT = "dump_personalization_dict";
+    public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview";
+    public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
 
     private static final boolean SHOW_STATISTICS_LOGGING = false;
 
@@ -110,6 +112,7 @@
         findPreference(PREF_DUMP_PERSONALIZATION_DICT).setOnPreferenceClickListener(
                 dictDumpPrefClickListener);
         final Resources res = getResources();
+        setupKeyLongpressTimeoutSettings(prefs, res);
         setupKeyPreviewAnimationDuration(prefs, res, PREF_KEY_PREVIEW_SHOW_UP_DURATION,
                 res.getInteger(R.integer.config_key_preview_show_up_duration));
         setupKeyPreviewAnimationDuration(prefs, res, PREF_KEY_PREVIEW_DISMISS_DURATION,
@@ -200,6 +203,44 @@
         }
     }
 
+    private void setupKeyLongpressTimeoutSettings(final SharedPreferences sp,
+            final Resources res) {
+        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
+                PREF_KEY_LONGPRESS_TIMEOUT);
+        if (pref == null) {
+            return;
+        }
+        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
+            @Override
+            public void writeValue(final int value, final String key) {
+                sp.edit().putInt(key, value).apply();
+            }
+
+            @Override
+            public void writeDefaultValue(final String key) {
+                sp.edit().remove(key).apply();
+            }
+
+            @Override
+            public int readValue(final String key) {
+                return Settings.readKeyLongpressTimeout(sp, res);
+            }
+
+            @Override
+            public int readDefaultValue(final String key) {
+                return Settings.readDefaultKeyLongpressTimeout(res);
+            }
+
+            @Override
+            public String getValueText(final int value) {
+                return res.getString(R.string.abbreviation_unit_milliseconds, value);
+            }
+
+            @Override
+            public void feedbackValue(final int value) {}
+        });
+    }
+
     private void setupKeyPreviewAnimationScale(final SharedPreferences sp, final Resources res,
             final String prefKey, final float defaultValue) {
         final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(prefKey);
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 353b746..a3aae8c 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -72,8 +72,6 @@
     public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction";
     public static final String PREF_GESTURE_SETTINGS = "gesture_typing_settings";
     public static final String PREF_GESTURE_INPUT = "gesture_input";
-    public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview";
-    public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
     public static final String PREF_VIBRATION_DURATION_SETTINGS =
             "pref_vibration_duration_settings";
     public static final String PREF_KEYPRESS_SOUND_VOLUME =
@@ -196,7 +194,7 @@
     // Accessed from the settings interface, hence public
     public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
             final Resources res) {
-        return prefs.getBoolean(Settings.PREF_SOUND_ON,
+        return prefs.getBoolean(PREF_SOUND_ON,
                 res.getBoolean(R.bool.config_default_sound_enabled));
     }
 
@@ -216,7 +214,7 @@
 
     public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs,
             final Resources res) {
-        return prefs.getBoolean(Settings.PREF_BLOCK_POTENTIALLY_OFFENSIVE,
+        return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE,
                 res.getBoolean(R.bool.config_block_potentially_offensive));
     }
 
@@ -227,12 +225,12 @@
     public static boolean readGestureInputEnabled(final SharedPreferences prefs,
             final Resources res) {
         return readFromBuildConfigIfGestureInputEnabled(res)
-                && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
+                && prefs.getBoolean(PREF_GESTURE_INPUT, true);
     }
 
     public static boolean readPhraseGestureEnabled(final SharedPreferences prefs,
             final Resources res) {
-        return prefs.getBoolean(Settings.PREF_PHRASE_GESTURE_ENABLED,
+        return prefs.getBoolean(PREF_PHRASE_GESTURE_ENABLED,
                 res.getBoolean(R.bool.config_default_phrase_gesture_enabled));
     }
 
@@ -278,7 +276,7 @@
 
     public static void writePrefAdditionalSubtypes(final SharedPreferences prefs,
             final String prefSubtypes) {
-        prefs.edit().putString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply();
+        prefs.edit().putString(PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply();
     }
 
     public static float readKeypressSoundVolume(final SharedPreferences prefs,
@@ -301,7 +299,7 @@
     public static int readKeyLongpressTimeout(final SharedPreferences prefs,
             final Resources res) {
         final int milliseconds = prefs.getInt(
-                PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
+                DebugSettings.PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
         return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
                 : readDefaultKeyLongpressTimeout(res);
     }
@@ -354,18 +352,18 @@
         if (!enableSetupWizardByConfig) {
             return false;
         }
-        if (!prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
+        if (!prefs.contains(PREF_SHOW_SETUP_WIZARD_ICON)) {
             final ApplicationInfo appInfo = context.getApplicationInfo();
             final boolean isApplicationInSystemImage =
                     (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
             // Default value
             return !isApplicationInSystemImage;
         }
-        return prefs.getBoolean(Settings.PREF_SHOW_SETUP_WIZARD_ICON, false);
+        return prefs.getBoolean(PREF_SHOW_SETUP_WIZARD_ICON, false);
     }
 
     public static boolean isInternal(final SharedPreferences prefs) {
-        return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
+        return prefs.getBoolean(PREF_KEY_IS_INTERNAL, false);
     }
 
     public void writeLastUsedPersonalizationToken(byte[] token) {
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index bb5547f..22cbd20 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -228,7 +228,6 @@
 
         AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
 
-        setupKeyLongpressTimeoutSettings(prefs, res);
         setupKeypressVibrationDurationSettings(prefs, res);
         setupKeypressSoundVolumeSettings(prefs, res);
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
@@ -368,44 +367,6 @@
         });
     }
 
-    private void setupKeyLongpressTimeoutSettings(final SharedPreferences sp,
-            final Resources res) {
-        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
-                Settings.PREF_KEY_LONGPRESS_TIMEOUT);
-        if (pref == null) {
-            return;
-        }
-        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
-            @Override
-            public void writeValue(final int value, final String key) {
-                sp.edit().putInt(key, value).apply();
-            }
-
-            @Override
-            public void writeDefaultValue(final String key) {
-                sp.edit().remove(key).apply();
-            }
-
-            @Override
-            public int readValue(final String key) {
-                return Settings.readKeyLongpressTimeout(sp, res);
-            }
-
-            @Override
-            public int readDefaultValue(final String key) {
-                return Settings.readDefaultKeyLongpressTimeout(res);
-            }
-
-            @Override
-            public String getValueText(final int value) {
-                return res.getString(R.string.abbreviation_unit_milliseconds, value);
-            }
-
-            @Override
-            public void feedbackValue(final int value) {}
-        });
-    }
-
     private void setupKeypressSoundVolumeSettings(final SharedPreferences sp, final Resources res) {
         final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
                 Settings.PREF_KEYPRESS_SOUND_VOLUME);
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index d47a61e..dde50cc 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -119,7 +119,7 @@
         mSoundOn = Settings.readKeypressSoundEnabled(prefs, res);
         mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res);
         mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
-                Settings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
+                DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
         mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res);
         final String autoCorrectionThresholdRawValue = prefs.getString(
                 Settings.PREF_AUTO_CORRECTION_THRESHOLD,
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
index 796921f..b8d2a22 100644
--- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -30,6 +30,7 @@
 public final class SpacingAndPunctuations {
     private final int[] mSortedSymbolsPrecededBySpace;
     private final int[] mSortedSymbolsFollowedBySpace;
+    private final int[] mSortedSymbolsClusteringTogether;
     private final int[] mSortedWordConnectors;
     public final int[] mSortedWordSeparators;
     public final PunctuationSuggestions mSuggestPuncList;
@@ -46,6 +47,8 @@
         // To be able to binary search the code point. See {@link #isUsuallyFollowedBySpace(int)}.
         mSortedSymbolsFollowedBySpace = StringUtils.toSortedCodePointArray(
                 res.getString(R.string.symbols_followed_by_space));
+        mSortedSymbolsClusteringTogether = StringUtils.toSortedCodePointArray(
+                res.getString(R.string.symbols_clustering_together));
         // To be able to binary search the code point. See {@link #isWordConnector(int)}.
         mSortedWordConnectors = StringUtils.toSortedCodePointArray(
                 res.getString(R.string.symbols_word_connectors));
@@ -85,6 +88,10 @@
         return Arrays.binarySearch(mSortedSymbolsFollowedBySpace, code) >= 0;
     }
 
+    public boolean isClusteringSymbol(final int code) {
+        return Arrays.binarySearch(mSortedSymbolsClusteringTogether, code) >= 0;
+    }
+
     public boolean isSentenceSeparator(final int code) {
         return code == mSentenceSeparator;
     }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index a104baa..5a325ea 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -74,7 +74,13 @@
             int rowStartIndex = fromIndex;
             final int size = Math.min(suggestedWords.size(), SuggestedWords.MAX_SUGGESTIONS);
             while (index < size) {
-                final String word = suggestedWords.getLabel(index);
+                final String word;
+                if (isIndexSubjectToAutoCorrection(suggestedWords, index)) {
+                    // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped.
+                    word = suggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD);
+                } else {
+                    word = suggestedWords.getLabel(index);
+                }
                 // TODO: Should take care of text x-scaling.
                 mWidths[index] = (int)(TypefaceUtils.getStringWidth(word, paint) + padding);
                 final int numColumn = index - rowStartIndex + 1;
@@ -172,6 +178,11 @@
         }
     }
 
+    private static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords,
+            final int index) {
+        return suggestedWords.mWillAutoCorrect && index == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+    }
+
     public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
         private final MoreSuggestionsView mPaneView;
         private SuggestedWords mSuggestedWords;
@@ -189,7 +200,6 @@
             final int xmlId = R.xml.kbd_suggestions_pane_template;
             load(xmlId, parentKeyboard.mId);
             mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
-
             mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
             final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow,
                     mPaneView.newLabelPaint(null /* key */), mResources);
@@ -206,8 +216,16 @@
                 final int x = params.getX(index);
                 final int y = params.getY(index);
                 final int width = params.getWidth(index);
-                final String word = mSuggestedWords.getLabel(index);
-                final String info = mSuggestedWords.getDebugString(index);
+                final String word;
+                final String info;
+                if (isIndexSubjectToAutoCorrection(mSuggestedWords, index)) {
+                    // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped.
+                    word = mSuggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD);
+                    info = mSuggestedWords.getDebugString(SuggestedWords.INDEX_OF_TYPED_WORD);
+                } else {
+                    word = mSuggestedWords.getLabel(index);
+                    info = mSuggestedWords.getDebugString(index);
+                }
                 final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
                 final Key key = new Key(word, KeyboardIconsSet.ICON_UNDEFINED,
                         indexInMoreSuggestions, null /* outputText */, info, 0 /* labelFlags */,
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
index a23b3ac..bf38abc 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.utils;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
 import java.util.Queue;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -74,6 +76,7 @@
      * Enqueues the given task into the prioritized task queue.
      * @param r the enqueued task
      */
+    @UsedForTesting
     public void executePrioritized(final Runnable r) {
         synchronized(mLock) {
             if (!mIsShutdown) {
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index 70a6638..34c1907 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -56,10 +56,10 @@
         dynamic_pt_reading_helper.cpp \
         dynamic_pt_reading_utils.cpp \
         dynamic_pt_updating_helper.cpp \
-        dynamic_pt_writing_utils.cpp) \
+        dynamic_pt_writing_utils.cpp \
+        patricia_trie_reading_utils.cpp) \
     $(addprefix suggest/policyimpl/dictionary/structure/v2/, \
         patricia_trie_policy.cpp \
-        patricia_trie_reading_utils.cpp \
         ver2_patricia_trie_node_reader.cpp \
         ver2_pt_node_array_reader.cpp) \
     $(addprefix suggest/policyimpl/dictionary/structure/v4/, \
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 3becc79..a80c975 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -293,13 +293,6 @@
 #define M_PI_F 3.14159265f
 #define MAX_PERCENTILE 100
 
-// Number of base-10 digits in the largest integer + 1 to leave room for a zero terminator.
-// As such, this is the maximum number of characters will be needed to represent an int as a
-// string, including the terminator; this is used as the size of a string buffer large enough to
-// hold any value that is intended to fit in an integer, e.g. in the code that reads the header
-// of the binary dictionary where a {key,value} string pair scheme is used.
-#define LARGEST_INT_DIGIT_COUNT 11
-
 #define NOT_A_CODE_POINT (-1)
 #define NOT_A_DISTANCE (-1)
 #define NOT_A_COORDINATE (-1)
@@ -325,14 +318,13 @@
 #define KEYCODE_SPACE ' '
 #define KEYCODE_SINGLE_QUOTE '\''
 #define KEYCODE_HYPHEN_MINUS '-'
+// Code point to indicate beginning-of-sentence. This is not in the code point space of unicode.
+#define CODE_POINT_BEGINNING_OF_SENTENCE 0x110000
 
 #define SUGGEST_INTERFACE_OUTPUT_SCALE 1000000.0f
 #define MAX_PROBABILITY 255
 #define MAX_BIGRAM_ENCODED_PROBABILITY 15
 
-// Assuming locale strings such as en_US, sr-Latn etc.
-#define MAX_LOCALE_STRING_LENGTH 10
-
 // Max value for length, distance and probability which are used in weighting
 // TODO: Remove
 #define MAX_VALUE_FOR_WEIGHTING 10000000
diff --git a/native/jni/src/suggest/core/layout/proximity_info.h b/native/jni/src/suggest/core/layout/proximity_info.h
index 56711d1..d4e4537 100644
--- a/native/jni/src/suggest/core/layout/proximity_info.h
+++ b/native/jni/src/suggest/core/layout/proximity_info.h
@@ -103,6 +103,8 @@
     const int KEYBOARD_HEIGHT;
     const float KEYBOARD_HYPOTENUSE;
     const bool HAS_TOUCH_POSITION_CORRECTION_DATA;
+    // Assuming locale strings such as en_US, sr-Latn etc.
+    static const int MAX_LOCALE_STRING_LENGTH = 10;
     char mLocaleStr[MAX_LOCALE_STRING_LENGTH];
     int *mProximityCharsArray;
     int mKeyXCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
index d20accf..5608e27 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp
@@ -26,6 +26,13 @@
 
 namespace latinime {
 
+// Number of base-10 digits in the largest integer + 1 to leave room for a zero terminator.
+// As such, this is the maximum number of characters will be needed to represent an int as a
+// string, including the terminator; this is used as the size of a string buffer large enough to
+// hold any value that is intended to fit in an integer, e.g. in the code that reads the header
+// of the binary dictionary where a {key,value} string pair scheme is used.
+const int HeaderReadWriteUtils::LARGEST_INT_DIGIT_COUNT = 11;
+
 const int HeaderReadWriteUtils::MAX_ATTRIBUTE_KEY_LENGTH = 256;
 const int HeaderReadWriteUtils::MAX_ATTRIBUTE_VALUE_LENGTH = 256;
 
@@ -154,8 +161,8 @@
 /* static */ void HeaderReadWriteUtils::setIntAttributeInner(AttributeMap *const headerAttributes,
         const AttributeMap::key_type *const key, const int value) {
     AttributeMap::mapped_type valueVector;
-    char charBuf[LARGEST_INT_DIGIT_COUNT + 1];
-    snprintf(charBuf, LARGEST_INT_DIGIT_COUNT + 1, "%d", value);
+    char charBuf[LARGEST_INT_DIGIT_COUNT];
+    snprintf(charBuf, sizeof(charBuf), "%d", value);
     insertCharactersIntoVector(charBuf, &valueVector);
     (*headerAttributes)[*key] = valueVector;
 }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
index a6b4c4e..9b90488 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/header/header_read_write_utils.h
@@ -92,6 +92,7 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(HeaderReadWriteUtils);
 
+    static const int LARGEST_INT_DIGIT_COUNT;
     static const int MAX_ATTRIBUTE_KEY_LENGTH;
     static const int MAX_ATTRIBUTE_VALUE_LENGTH;
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
index 2457b49..e02dd55 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
@@ -18,9 +18,9 @@
 
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_writing_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/pt_node_writer.h"
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 
 namespace latinime {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.cpp
similarity index 98%
rename from native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.cpp
rename to native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.cpp
index b4eee55..e64a13c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 
 #include "defines.h"
 #include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h
similarity index 99%
rename from native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h
rename to native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h
index a6090a5..c3f09c3 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h
@@ -26,7 +26,6 @@
 class DictionaryShortcutsStructurePolicy;
 class DictionaryBigramsStructurePolicy;
 
-// TODO: Move to pt_common
 class PatriciaTrieReadingUtils {
  public:
     typedef uint8_t NodeFlags;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
index e4847fc..91192fc 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_params.h
@@ -21,7 +21,7 @@
 
 #include "defines.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_utils.h"
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_constants.h"
 
 namespace latinime {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index b426dbf..b3af1f4 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -22,7 +22,7 @@
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h"
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/utils/probability_utils.h"
 
 namespace latinime {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
index 778d7a4..0c8de0d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
@@ -16,7 +16,7 @@
 
 #include "suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h"
 
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp
index 125ea31..b46617d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.cpp
@@ -16,7 +16,7 @@
 
 #include "suggest/policyimpl/dictionary/structure/v2/ver2_pt_node_array_reader.h"
 
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
index f149781..67420a2 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.cpp
@@ -17,7 +17,7 @@
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
 
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_utils.h"
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/probability_dict_content.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_reading_utils.h"
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
index f24c2e1..38ff42f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
@@ -21,7 +21,7 @@
 #include "suggest/policyimpl/dictionary/shortcut/ver4_shortcut_list_policy.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_writing_utils.h"
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/structure/v4/content/probability_entry.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h"
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_dict_buffers.h"
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_pt_node_array_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_pt_node_array_reader.cpp
index bbdf40c..b014c52 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_pt_node_array_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_pt_node_array_reader.cpp
@@ -17,7 +17,7 @@
 #include "suggest/policyimpl/dictionary/structure/v4/ver4_pt_node_array_reader.h"
 
 #include "suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_utils.h"
-#include "suggest/policyimpl/dictionary/structure/v2/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/structure/pt_common/patricia_trie_reading_utils.h"
 #include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
 
 namespace latinime {
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index d2dd292..29423e8 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -334,6 +334,18 @@
         assertEquals("manual pick then separator", EXPECTED_RESULT, mEditText.getText().toString());
     }
 
+    // This test matches the one in InputLogicTestsNonEnglish. In some non-English languages,
+    // ! and ? are clustering punctuation signs.
+    public void testClusteringPunctuation() {
+        final String WORD1_TO_TYPE = "test";
+        final String WORD2_TO_TYPE = "!!?!:!";
+        final String EXPECTED_RESULT = "test!!?!:!";
+        type(WORD1_TO_TYPE);
+        pickSuggestionManually(0, WORD1_TO_TYPE);
+        type(WORD2_TO_TYPE);
+        assertEquals("clustering punctuation", EXPECTED_RESULT, mEditText.getText().toString());
+    }
+
     public void testManualPickThenStripperThenPick() {
         final String WORD_TO_TYPE = "this";
         final String STRIPPER = "\n";
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
index 1257ae2..68b6ee6 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
@@ -45,6 +45,19 @@
                 mEditText.getText().toString());
     }
 
+    public void testClusteringPunctuationForFrench() {
+        final String WORD1_TO_TYPE = "test";
+        final String WORD2_TO_TYPE = "!!?!:!";
+        // In English, the expected result would be "test!!?!:!"
+        final String EXPECTED_RESULT = "test !!?! : !";
+        changeLanguage("fr");
+        type(WORD1_TO_TYPE);
+        pickSuggestionManually(0, WORD1_TO_TYPE);
+        type(WORD2_TO_TYPE);
+        assertEquals("clustering punctuation for French", EXPECTED_RESULT,
+                mEditText.getText().toString());
+    }
+
     public void testWordThenSpaceThenPunctuationFromStripTwiceForFrench() {
         final String WORD_TO_TYPE = "test ";
         final String PUNCTUATION_FROM_STRIP = "!";