Merge "Add preliminary graphics of sliding key input preview"
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 36412b4..edf615a 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -120,18 +120,14 @@
     </string-array>
 
     <!-- Subtype locale display name exceptions.
-         For each exception, there should be related string resource for display name that has
-         explicit keyboard layout. The string resource name must be "subtype_with_layout_<locale>. -->
+         For each exception, there should be related string resources for display name that may have
+         explicit keyboard layout. The string resource name must be "subtype_<locale>" or
+         "subtype_with_layout_<locale>. Please refer to strings.xml for these resources. -->
     <string-array name="subtype_locale_exception_keys">
         <item>en_US</item>
         <item>en_GB</item>
         <item>es_US</item>
     </string-array>
-    <string-array name="subtype_locale_exception_values">
-        <item>English (US)</item>
-        <item>English (UK)</item>
-        <item>Español (EE.UU.)</item>
-    </string-array>
 
     <!-- Generic subtype label -->
     <string name="subtype_generic">%s</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index d8c2e84..1b518a1 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -322,12 +322,17 @@
     <string name="subtype_en_GB">English (UK)</string>
     <!-- Description for English (United States) keyboard subtype [CHAR LIMIT=25] -->
     <string name="subtype_en_US">English (US)</string>
+    <!-- Description for Spanish (United States) keyboard subtype [CHAR LIMIT=25] -->
+    <string name="subtype_es_US">Spanish (US)</string>
     <!-- Description for English (United Kingdom) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
          This should be identical to subtype_en_GB aside from the trailing (%s). -->
     <string name="subtype_with_layout_en_GB">English (UK) (<xliff:g id="layout">%s</xliff:g>)</string>
     <!-- Description for English (United States) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
          This should be identical to subtype_en_US aside from the trailing (%s). -->
     <string name="subtype_with_layout_en_US">English (US) (<xliff:g id="layout">%s</xliff:g>)</string>
+    <!-- Description for Spanish (United States) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
+         This should be identical to subtype_es_US aside from the trailing (%s). -->
+    <string name="subtype_with_layout_es_US">Spanish (US) (<xliff:g id="layout">%s</xliff:g>)</string>
     <!-- TODO: Uncomment once we can handle IETF language tag with script name specified.
          Description for Serbian Cyrillic keyboard subtype [CHAR LIMIT=25]
     <string name="subtype_serbian_cyrillic">Serbian (Cyrillic)</string>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index aa59c57..f30ef23 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -186,7 +186,7 @@
             android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
-            android:label="@string/subtype_generic"
+            android:label="@string/subtype_es_US"
             android:subtypeId="0x84d2efc6"
             android:imeSubtypeLocale="es_US"
             android:imeSubtypeMode="keyboard"
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index a56c78b..f787722 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -72,7 +72,8 @@
         }
 
         public SubtypeLocaleItem(final String localeString) {
-            this(localeString, SubtypeLocale.getSubtypeLocaleDisplayName(localeString));
+            this(localeString,
+                    SubtypeLocale.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
         }
 
         @Override
@@ -103,7 +104,7 @@
                 if (DEBUG_SUBTYPE_ID) {
                     android.util.Log.d(TAG, String.format("%-6s 0x%08x %11d %s",
                             subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
-                            SubtypeLocale.getSubtypeDisplayName(subtype)));
+                            SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype)));
                 }
                 if (subtype.containsExtraValueKey(ASCII_CAPABLE)) {
                     items.add(createItem(context, subtype.getLocale()));
@@ -205,7 +206,8 @@
                 setDialogTitle(R.string.add_style);
                 setKey(KEY_NEW_SUBTYPE);
             } else {
-                final String displayName = SubtypeLocale.getSubtypeDisplayName(subtype);
+                final String displayName =
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype);
                 setTitle(displayName);
                 setDialogTitle(displayName);
                 setKey(KEY_PREFIX + subtype.getLocale() + "_"
@@ -497,7 +499,7 @@
         final Context context = getActivity();
         final Resources res = context.getResources();
         final String message = res.getString(R.string.custom_input_style_already_exists,
-                SubtypeLocale.getSubtypeDisplayName(subtype));
+                SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
         Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 3ba24fb..f0c51e1 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -41,7 +41,6 @@
     private ListPreference mKeyPreviewPopupDismissDelay;
     // Use bigrams to predict the next word when there is no input for it yet
     private CheckBoxPreference mBigramPrediction;
-    private Preference mDebugSettingsPreference;
 
     private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
         final Preference preference = findPreference(preferenceKey);
@@ -50,11 +49,14 @@
         }
     }
 
-    private void ensureConsistencyOfAutoCorrectionSettings() {
-        final String autoCorrectionOff = getResources().getString(
-                R.string.auto_correction_threshold_mode_index_off);
-        final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
-        mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
+    private static void removePreference(final String preferenceKey, final PreferenceGroup parent) {
+        if (parent == null) {
+            return;
+        }
+        final Preference preference = parent.findPreference(preferenceKey);
+        if (preference != null) {
+            parent.removePreference(preference);
+        }
     }
 
     @Override
@@ -84,22 +86,18 @@
 
         final PreferenceGroup generalSettings =
                 (PreferenceGroup) findPreference(Settings.PREF_GENERAL_SETTINGS);
-        final PreferenceGroup textCorrectionGroup =
-                (PreferenceGroup) findPreference(Settings.PREF_CORRECTION_SETTINGS);
-        final PreferenceGroup gestureTypingSettings =
-                (PreferenceGroup) findPreference(Settings.PREF_GESTURE_SETTINGS);
         final PreferenceGroup miscSettings =
                 (PreferenceGroup) findPreference(Settings.PREF_MISC_SETTINGS);
 
-        mDebugSettingsPreference = findPreference(Settings.PREF_DEBUG_SETTINGS);
-        if (mDebugSettingsPreference != null) {
+        final Preference debugSettings = findPreference(Settings.PREF_DEBUG_SETTINGS);
+        if (debugSettings != null) {
             if (ProductionFlag.IS_INTERNAL) {
                 final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
                 debugSettingsIntent.setClassName(
                         context.getPackageName(), DebugSettingsActivity.class.getName());
-                mDebugSettingsPreference.setIntent(debugSettingsIntent);
+                debugSettings.setIntent(debugSettingsIntent);
             } else {
-                miscSettings.removePreference(mDebugSettingsPreference);
+                miscSettings.removePreference(debugSettings);
             }
         }
 
@@ -112,11 +110,8 @@
         final PreferenceGroup advancedSettings =
                 (PreferenceGroup) findPreference(Settings.PREF_ADVANCED_SETTINGS);
         if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) {
-            generalSettings.removePreference(findPreference(Settings.PREF_VIBRATE_ON));
-            if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
-                advancedSettings.removePreference(
-                        findPreference(Settings.PREF_VIBRATION_DURATION_SETTINGS));
-            }
+            removePreference(Settings.PREF_VIBRATE_ON, generalSettings);
+            removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
         }
 
         final boolean showKeyPreviewPopupOption = res.getBoolean(
@@ -124,10 +119,8 @@
         mKeyPreviewPopupDismissDelay =
                 (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
         if (!showKeyPreviewPopupOption) {
-            generalSettings.removePreference(findPreference(Settings.PREF_POPUP_ON));
-            if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
-                advancedSettings.removePreference(mKeyPreviewPopupDismissDelay);
-            }
+            removePreference(Settings.PREF_POPUP_ON, generalSettings);
+            removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings);
         } else {
             final String[] entries = new String[] {
                     res.getString(R.string.key_preview_popup_dismiss_no_delay),
@@ -148,10 +141,11 @@
         setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
                 Settings.readShowsLanguageSwitchKey(prefs));
 
+        final PreferenceGroup textCorrectionGroup =
+                (PreferenceGroup) findPreference(Settings.PREF_CORRECTION_SETTINGS);
         final PreferenceScreen dictionaryLink =
                 (PreferenceScreen) findPreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY);
         final Intent intent = dictionaryLink.getIntent();
-
         final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
         // TODO: The experimental version is not supported by the Dictionary Pack Service yet
         if (ProductionFlag.IS_EXPERIMENTAL || 0 >= number) {
@@ -161,7 +155,7 @@
         final boolean gestureInputEnabledByBuildConfig = res.getBoolean(
                 R.bool.config_gesture_input_enabled_by_build_config);
         if (!gestureInputEnabledByBuildConfig) {
-            getPreferenceScreen().removePreference(gestureTypingSettings);
+            removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
         }
 
         setupKeyLongpressTimeoutSettings(prefs, res);
@@ -219,6 +213,13 @@
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
     }
 
+    private void ensureConsistencyOfAutoCorrectionSettings() {
+        final String autoCorrectionOff = getResources().getString(
+                R.string.auto_correction_threshold_mode_index_off);
+        final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
+        mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
+    }
+
     private void updateShowCorrectionSuggestionsSummary() {
         mShowCorrectionSuggestionsPreference.setSummary(
                 getResources().getStringArray(R.array.prefs_suggestion_visibilities)
@@ -237,7 +238,7 @@
         final StringBuilder styles = new StringBuilder();
         for (final InputMethodSubtype subtype : subtypes) {
             if (styles.length() > 0) styles.append(", ");
-            styles.append(SubtypeLocale.getSubtypeDisplayName(subtype));
+            styles.append(SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
         }
         customInputStyles.setSummary(styles);
     }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 068c34e..2f26f92 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -51,20 +51,22 @@
     private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
             CollectionUtils.newHashMap();
     // Exceptional locale to subtype name resource id map.
+    private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap =
+            CollectionUtils.newHashMap();
+    // Exceptional locale to subtype name with layout resource id map.
     private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
             CollectionUtils.newHashMap();
+    private static final String SUBTYPE_NAME_RESOURCE_PREFIX =
+            "string/subtype_";
     private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
             "string/subtype_generic_";
     private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
             "string/subtype_with_layout_";
     private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX =
             "string/subtype_no_language_";
-    // Exceptional locales to display name map.
-    private static final HashMap<String, String> sExceptionalDisplayNamesMap =
-            CollectionUtils.newHashMap();
     // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
     // This is for compatibility to keep the same subtype ids as pre-JellyBean.
-    private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
+    private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
             CollectionUtils.newHashMap();
 
     private SubtypeLocale() {
@@ -98,14 +100,16 @@
 
         final String[] exceptionalLocales = res.getStringArray(
                 R.array.subtype_locale_exception_keys);
-        final String[] exceptionalDisplayNames = res.getStringArray(
-                R.array.subtype_locale_exception_values);
         for (int i = 0; i < exceptionalLocales.length; i++) {
             final String localeString = exceptionalLocales[i];
-            sExceptionalDisplayNamesMap.put(localeString, exceptionalDisplayNames[i]);
-            final String resourceName = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString;
+            final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString;
             final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
-            sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resId);
+            sExceptionalLocaleToNameIdsMap.put(localeString, resId);
+            final String resourceNameWithLayout =
+                    SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString;
+            final int resIdWithLayout = res.getIdentifier(
+                    resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME);
+            sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resIdWithLayout);
         }
 
         final String[] keyboardLayoutSetMap = res.getStringArray(
@@ -124,7 +128,7 @@
     }
 
     public static boolean isExceptionalLocale(final String localeString) {
-        return sExceptionalLocaleToWithLayoutNameIdsMap.containsKey(localeString);
+        return sExceptionalLocaleToNameIdsMap.containsKey(localeString);
     }
 
     private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) {
@@ -143,13 +147,33 @@
         return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
     }
 
+    public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) {
+        final Locale displayLocale = sResources.getConfiguration().locale;
+        return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
+    }
+
     public static String getSubtypeLocaleDisplayName(final String localeString) {
-        final String exceptionalValue = sExceptionalDisplayNamesMap.get(localeString);
-        if (exceptionalValue != null) {
-            return exceptionalValue;
-        }
+        final Locale displayLocale = LocaleUtils.constructLocaleFromString(localeString);
+        return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
+    }
+
+    private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
+            final Locale displayLocale) {
         final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
-        return StringUtils.toTitleCase(locale.getDisplayName(locale), locale);
+        final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+        final String displayName;
+        if (exceptionalNameResId != null) {
+            final RunInLocale<String> getExceptionalName = new RunInLocale<String>() {
+                @Override
+                protected String job(final Resources res) {
+                    return res.getString(exceptionalNameResId);
+                }
+            };
+            displayName = getExceptionalName.runInLocale(sResources, displayLocale);
+        } else {
+            displayName = locale.getDisplayName(displayLocale);
+        }
+        return StringUtils.toTitleCase(displayName, displayLocale);
     }
 
     // InputMethodSubtype's display name in its locale.
@@ -165,24 +189,36 @@
     //  zz    qwerty  F  No language (QWERTY)    in system locale
     //  fr    qwertz  T  Français (QWERTZ)
     //  de    qwerty  T  Deutsch (QWERTY)
-    //  en_US azerty  T  English (US) (AZERTY)
+    //  en_US azerty  T  English (US) (AZERTY)   exception
     //  zz    azerty  T  No language (AZERTY)    in system locale
 
-    private static String getReplacementString(final InputMethodSubtype subtype) {
+    private static String getReplacementString(final InputMethodSubtype subtype,
+            final Locale displayLocale) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
                 && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
             return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
         } else {
-            return getSubtypeLocaleDisplayName(subtype.getLocale());
+            return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
         }
     }
 
+    public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) {
+        final Locale subtypeLocale = sResources.getConfiguration().locale;
+        return getSubtypeDisplayNameInternal(subtype, subtypeLocale);
+    }
+
     public static String getSubtypeDisplayName(final InputMethodSubtype subtype) {
-        final String replacementString = getReplacementString(subtype);
+        final Locale subtypeLocale = LocaleUtils.constructLocaleFromString(subtype.getLocale());
+        return getSubtypeDisplayNameInternal(subtype, subtypeLocale);
+    }
+
+    private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
+            final Locale displayLocale) {
+        final String replacementString = getReplacementString(subtype, displayLocale);
         final int nameResId = subtype.getNameResId();
         final RunInLocale<String> getSubtypeName = new RunInLocale<String>() {
             @Override
-            protected String job(Resources res) {
+            protected String job(final Resources res) {
                 try {
                     return res.getString(nameResId, replacementString);
                 } catch (Resources.NotFoundException e) {
@@ -197,8 +233,9 @@
             }
         };
         final Locale locale = isNoLanguage(subtype)
-                ? sResources.getConfiguration().locale : getSubtypeLocale(subtype);
-        return getSubtypeName.runInLocale(sResources, locale);
+                ? sResources.getConfiguration().locale : displayLocale;
+        return StringUtils.toTitleCase(
+                getSubtypeName.runInLocale(sResources, locale), locale);
     }
 
     public static boolean isNoLanguage(final InputMethodSubtype subtype) {
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
index 60da203..7a4384d 100644
--- a/native/jni/src/char_utils.h
+++ b/native/jni/src/char_utils.h
@@ -72,5 +72,16 @@
     // TODO: Do not hardcode here
     return codePoint == KEYCODE_SINGLE_QUOTE || codePoint == KEYCODE_HYPHEN_MINUS;
 }
+
+inline static int getCodePointCount(const int arraySize, const int *const codePoints) {
+    int size = 0;
+    for (; size < arraySize; ++size) {
+        if (codePoints[size] == '\0') {
+            break;
+        }
+    }
+    return size;
+}
+
 } // namespace latinime
 #endif // LATINIME_CHAR_UTILS_H
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 8c6a525..f78b84e 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -33,8 +33,10 @@
         const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize,
         const int *const xCoordinates, const int *const yCoordinates, const int *const times,
         const int *const pointerIds, const bool isGeometric) {
-    mIsContinuationPossible = checkAndReturnIsContinuationPossible(
-            inputSize, xCoordinates, yCoordinates, times, isGeometric);
+    ASSERT(isGeometric || (inputSize < MAX_WORD_LENGTH));
+    mIsContinuationPossible = ProximityInfoStateUtils::checkAndReturnIsContinuationPossible(
+            inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, &mSampledInputXs,
+            &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
 
     mProximityInfo = proximityInfo;
     mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
@@ -149,39 +151,6 @@
     }
 }
 
-bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSize,
-        const int *const xCoordinates, const int *const yCoordinates, const int *const times,
-        const bool isGeometric) const {
-    if (isGeometric) {
-        for (int i = 0; i < mSampledInputSize; ++i) {
-            const int index = mSampledInputIndice[i];
-            if (index > inputSize || xCoordinates[index] != mSampledInputXs[i] ||
-                    yCoordinates[index] != mSampledInputYs[i] || times[index] != mSampledTimes[i]) {
-                return false;
-            }
-        }
-    } else {
-        if (inputSize < mSampledInputSize) {
-            // Assuming the cache is invalid if the previous input size is larger than the new one.
-            return false;
-        }
-        for (int i = 0; i < mSampledInputSize && i < MAX_WORD_LENGTH; ++i) {
-            if (xCoordinates[i] != mSampledInputXs[i]
-                    || yCoordinates[i] != mSampledInputYs[i]) {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-int ProximityInfoState::getDuration(const int index) const {
-    if (index >= 0 && index < mSampledInputSize - 1) {
-        return mSampledTimes[index + 1] - mSampledTimes[index];
-    }
-    return 0;
-}
-
 // TODO: Remove the "scale" parameter
 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
 // wrong to compare with mMaxPointToKeyLength.
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 642925c..9c4f557 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -122,8 +122,6 @@
         return true;
     }
 
-    int getDuration(const int index) const;
-
     bool isUsed() const {
         return mSampledInputSize > 0;
     }
@@ -217,8 +215,6 @@
     inline const int *getProximityCodePointsAt(const int index) const {
         return ProximityInfoStateUtils::getProximityCodePointsAt(mInputProximities, index);
     }
-    bool checkAndReturnIsContinuationPossible(const int inputSize, const int *const xCoordinates,
-            const int *const yCoordinates, const int *const times, const bool isGeometric) const;
     void popInputData();
 
     // const
diff --git a/native/jni/src/proximity_info_state_utils.cpp b/native/jni/src/proximity_info_state_utils.cpp
index cbc191e..dd8f1bc 100644
--- a/native/jni/src/proximity_info_state_utils.cpp
+++ b/native/jni/src/proximity_info_state_utils.cpp
@@ -983,6 +983,34 @@
     return true;
 }
 
+/* static */ bool ProximityInfoStateUtils::checkAndReturnIsContinuationPossible(const int inputSize,
+        const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+        const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs,
+        const std::vector<int> *const sampledTimes,
+        const std::vector<int> *const sampledInputIndices) {
+    if (inputSize < sampledInputSize) {
+        return false;
+    }
+    for (int i = 0; i < sampledInputSize; ++i) {
+        const int index = (*sampledInputIndices)[i];
+        if (index >= inputSize) {
+            return false;
+        }
+        if (xCoordinates[index] != (*sampledInputXs)[i]
+                || yCoordinates[index] != (*sampledInputYs)[i]) {
+            return false;
+        }
+        if (!times) {
+            continue;
+        }
+        if (times[index] != (*sampledTimes)[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
 /* static */ void ProximityInfoStateUtils::dump(const bool isGeometric, const int inputSize,
         const int *const inputXCoordinates, const int *const inputYCoordinates,
         const int sampledInputSize, const std::vector<int> *const sampledInputXs,
diff --git a/native/jni/src/proximity_info_state_utils.h b/native/jni/src/proximity_info_state_utils.h
index 17ef1c3..9315490 100644
--- a/native/jni/src/proximity_info_state_utils.h
+++ b/native/jni/src/proximity_info_state_utils.h
@@ -107,6 +107,12 @@
             const std::vector<int> *const sampledTimes,
             const std::vector<float> *const sampledSpeedRates,
             const std::vector<int> *const sampledBeelineSpeedPercentiles);
+    static bool checkAndReturnIsContinuationPossible(const int inputSize,
+            const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+            const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs,
+            const std::vector<int> *const sampledTimes,
+            const std::vector<int> *const sampledInputIndices);
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfoStateUtils);
 
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index 9af6dbc..e37fef7 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -42,8 +42,10 @@
     InputMethodSubtype ZZ;
     InputMethodSubtype DE_QWERTY;
     InputMethodSubtype FR_QWERTZ;
-    InputMethodSubtype US_AZERTY;
-    InputMethodSubtype ZZ_AZERTY;
+    InputMethodSubtype EN_US_AZERTY;
+    InputMethodSubtype EN_UK_DVORAK;
+    InputMethodSubtype ES_US_COLEMAK;
+    InputMethodSubtype ZZ_PC;
 
     @Override
     protected void setUp() throws Exception {
@@ -72,10 +74,14 @@
                 Locale.GERMAN.toString(), "qwerty", null);
         FR_QWERTZ = AdditionalSubtype.createAdditionalSubtype(
                 Locale.FRENCH.toString(), "qwertz", null);
-        US_AZERTY = AdditionalSubtype.createAdditionalSubtype(
+        EN_US_AZERTY = AdditionalSubtype.createAdditionalSubtype(
                 Locale.US.toString(), "azerty", null);
-        ZZ_AZERTY = AdditionalSubtype.createAdditionalSubtype(
-                SubtypeLocale.NO_LANGUAGE, "azerty", null);
+        EN_UK_DVORAK = AdditionalSubtype.createAdditionalSubtype(
+                Locale.UK.toString(), "dvorak", null);
+        ES_US_COLEMAK = AdditionalSubtype.createAdditionalSubtype(
+                "es_US", "colemak", null);
+        ZZ_PC = AdditionalSubtype.createAdditionalSubtype(
+                SubtypeLocale.NO_LANGUAGE, "pcqwerty", null);
 
     }
 
@@ -106,8 +112,10 @@
     //  zz    qwerty  F  No language (QWERTY)    in system locale
     //  fr    qwertz  T  Français (QWERTZ)
     //  de    qwerty  T  Deutsch (QWERTY)
-    //  en_US azerty  T  English (US) (AZERTY)
-    //  zz    azerty  T  No language (AZERTY)    in system locale
+    //  en_US azerty  T  English (US) (AZERTY)   exception
+    //  en_UK dvorak  T  English (UK) (Dvorak)   exception
+    //  es_US colemak T  Español (EE.UU.) (Colemak)  exception
+    //  zz    pc      T  No language (PC)        in system locale
 
     public void testPredefinedSubtypesInEnglish() {
         assertEquals("en_US", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_US));
@@ -150,9 +158,13 @@
                 assertEquals("de qwerty",    "Deutsch (QWERTY)",
                         SubtypeLocale.getSubtypeDisplayName(DE_QWERTY));
                 assertEquals("en_US azerty", "English (US) (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(US_AZERTY));
-                assertEquals("zz azerty",    "No language (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(ZZ_AZERTY));
+                        SubtypeLocale.getSubtypeDisplayName(EN_US_AZERTY));
+                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
+                        SubtypeLocale.getSubtypeDisplayName(EN_UK_DVORAK));
+                assertEquals("es_US colemak","Español (EE.UU.) (Colemak)",
+                        SubtypeLocale.getSubtypeDisplayName(ES_US_COLEMAK));
+                assertEquals("zz pc",        "No language (PC)",
+                        SubtypeLocale.getSubtypeDisplayName(ZZ_PC));
                 return null;
             }
         };
@@ -192,9 +204,149 @@
                 assertEquals("de qwerty",    "Deutsch (QWERTY)",
                         SubtypeLocale.getSubtypeDisplayName(DE_QWERTY));
                 assertEquals("en_US azerty", "English (US) (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(US_AZERTY));
-                assertEquals("zz azerty",    "Aucune langue (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(ZZ_AZERTY));
+                        SubtypeLocale.getSubtypeDisplayName(EN_US_AZERTY));
+                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
+                        SubtypeLocale.getSubtypeDisplayName(EN_UK_DVORAK));
+                assertEquals("es_US colemak","Español (EE.UU.) (Colemak)",
+                        SubtypeLocale.getSubtypeDisplayName(ES_US_COLEMAK));
+                assertEquals("zz azerty",    "Aucune langue (PC)",
+                        SubtypeLocale.getSubtypeDisplayName(ZZ_PC));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.FRENCH);
+    }
+
+    // InputMethodSubtype's display name in system locale (en_US).
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  display name
+    // ------ ------- - ----------------------
+    //  en_US qwerty  F  English (US)            exception
+    //  en_GB qwerty  F  English (UK)            exception
+    //  es_US spanish F  Spanish (US)            exception
+    //  fr    azerty  F  French
+    //  fr_CA qwerty  F  French (Canada)
+    //  de    qwertz  F  German
+    //  zz    qwerty  F  No language (QWERTY)
+    //  fr    qwertz  T  French (QWERTZ)
+    //  de    qwerty  T  German (QWERTY)
+    //  en_US azerty  T  English (US) (AZERTY)   exception
+    //  en_UK dvorak  T  English (UK) (Dvorak)   exception
+    //  es_US colemak T  Spanish (US) (Colemak)  exception
+    //  zz    pc      T  No language (PC)
+
+    public void testPredefinedSubtypesInEnglishSystemLocale() {
+        assertEquals("en_US", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_US));
+        assertEquals("en_GB", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_GB));
+        assertEquals("es_US", "spanish", SubtypeLocale.getKeyboardLayoutSetName(ES_US));
+        assertEquals("fr   ", "azerty", SubtypeLocale.getKeyboardLayoutSetName(FR));
+        assertEquals("fr_CA", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(FR_CA));
+        assertEquals("de   ", "qwertz", SubtypeLocale.getKeyboardLayoutSetName(DE));
+        assertEquals("zz   ", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(ZZ));
+
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(Resources res) {
+                assertEquals("en_US", "English (US)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US));
+                assertEquals("en_GB", "English (UK)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_GB));
+                assertEquals("es_US", "Spanish (US)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US));
+                assertEquals("fr   ", "French",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR));
+                assertEquals("fr_CA", "French (Canada)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("de   ", "German",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("zz   ", "No language (QWERTY)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    public void testAdditionalSubtypesInEnglishSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(Resources res) {
+                assertEquals("fr qwertz",    "French (QWERTZ)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
+                assertEquals("de qwerty",    "German (QWERTY)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
+                assertEquals("en_US azerty", "English (US) (AZERTY)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
+                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
+                assertEquals("es_US colemak","Spanish (US) (Colemak)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
+                assertEquals("zz azerty",    "No language (PC)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    // InputMethodSubtype's display name in system locale (fr).
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  display name
+    // ------ ------- - ----------------------
+    //  en_US qwerty  F  Anglais (États-Unis)            exception
+    //  en_GB qwerty  F  Anglais (Royaume-Uni)            exception
+    //  es_US spanish F  Espagnol (États-Unis)            exception
+    //  fr    azerty  F  Français
+    //  fr_CA qwerty  F  Français (Canada)
+    //  de    qwertz  F  Allemand
+    //  zz    qwerty  F  Pas de langue (QWERTY)
+    //  fr    qwertz  T  Français (QWERTZ)
+    //  de    qwerty  T  Allemand (QWERTY)
+    //  en_US azerty  T  Anglais (États-Unis) (AZERTY)   exception
+    //  en_UK dvorak  T  Anglais (Royaume-Uni) (Dvorak)   exception
+    //  es_US colemak T  Espagnol (États-Unis) (Colemak)  exception
+    //  zz    pc      T  Aucune langue (PC)
+
+    public void testPredefinedSubtypesInFrenchSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(Resources res) {
+                assertEquals("en_US", "Anglais (États-Unis)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US));
+                assertEquals("en_GB", "Anglais (Royaume-Uni)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_GB));
+                assertEquals("es_US", "Espagnol (États-Unis)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US));
+                assertEquals("fr   ", "Français",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR));
+                assertEquals("fr_CA", "Français (Canada)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("de   ", "Allemand",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("zz   ", "Pas de langue (QWERTY)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.FRENCH);
+    }
+
+    public void testAdditionalSubtypesInFrenchSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(Resources res) {
+                assertEquals("fr qwertz",    "Français (QWERTZ)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
+                assertEquals("de qwerty",    "Allemand (QWERTY)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
+                assertEquals("en_US azerty", "Anglais (États-Unis) (AZERTY)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
+                assertEquals("en_UK dvorak", "Anglais (Royaume-Uni) (Dvorak)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
+                assertEquals("es_US colemak","Espagnol (États-Unis) (Colemak)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
+                assertEquals("zz azerty",    "Aucune langue (PC)",
+                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
                 return null;
             }
         };