Merge "Clean up UserDictionaryCompatUtils"
diff --git a/common/src/com/android/inputmethod/latin/common/Constants.java b/common/src/com/android/inputmethod/latin/common/Constants.java
index d374642..8f4a1e5 100644
--- a/common/src/com/android/inputmethod/latin/common/Constants.java
+++ b/common/src/com/android/inputmethod/latin/common/Constants.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.common;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
 public final class Constants {
     public static final class Color {
         /**
@@ -305,11 +307,13 @@
     public static final int SCREEN_METRICS_LARGE_TABLET = 2;
     public static final int SCREEN_METRICS_SMALL_TABLET = 3;
 
+    @UsedForTesting
     public static boolean isPhone(final int screenMetrics) {
         return screenMetrics == SCREEN_METRICS_SMALL_PHONE
                 || screenMetrics == SCREEN_METRICS_LARGE_PHONE;
     }
 
+    @UsedForTesting
     public static boolean isTablet(final int screenMetrics) {
         return screenMetrics == SCREEN_METRICS_SMALL_TABLET
                 || screenMetrics == SCREEN_METRICS_LARGE_TABLET;
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/common/src/com/android/inputmethod/latin/common/StringUtils.java
similarity index 88%
rename from java/src/com/android/inputmethod/latin/utils/StringUtils.java
rename to common/src/com/android/inputmethod/latin/common/StringUtils.java
index f96ed04..bbe4664 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/common/src/com/android/inputmethod/latin/common/StringUtils.java
@@ -14,19 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin.utils;
-
-import android.text.Spanned;
-import android.text.TextUtils;
+package com.android.inputmethod.latin.common;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.common.Constants;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public final class StringUtils {
     public static final int CAPITALIZE_NONE = 0;  // No caps, or mixed case
@@ -47,8 +41,67 @@
         // This utility class is not publicly instantiable.
     }
 
+    // Taken from android.text.TextUtils. We are extensively using this method in many places,
+    // some of which don't have the android libraries available.
+    /**
+     * Returns true if the string is null or 0-length.
+     * @param str the string to be examined
+     * @return true if str is null or zero length
+     */
+    public static boolean isEmpty(CharSequence str) {
+        if (str == null || str.length() == 0)
+            return true;
+        else
+            return false;
+    }
+
+    // Taken from android.text.TextUtils to cut the dependency to the Android framework.
+    /**
+     * Returns a string containing the tokens joined by delimiters.
+     * @param tokens an array objects to be joined. Strings will be formed from
+     *     the objects by calling object.toString().
+     */
+    public static String join(CharSequence delimiter, Iterable tokens) {
+        StringBuilder sb = new StringBuilder();
+        boolean firstTime = true;
+        for (Object token: tokens) {
+            if (firstTime) {
+                firstTime = false;
+            } else {
+                sb.append(delimiter);
+            }
+            sb.append(token);
+        }
+        return sb.toString();
+    }
+
+    // Taken from android.text.TextUtils to cut the dependency to the Android framework.
+    /**
+     * Returns true if a and b are equal, including if they are both null.
+     * <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if
+     * both the arguments were instances of String.</i></p>
+     * @param a first CharSequence to check
+     * @param b second CharSequence to check
+     * @return true if a and b are equal
+     */
+    public static boolean equals(CharSequence a, CharSequence b) {
+        if (a == b) return true;
+        int length;
+        if (a != null && b != null && (length = a.length()) == b.length()) {
+            if (a instanceof String && b instanceof String) {
+                return a.equals(b);
+            } else {
+                for (int i = 0; i < length; i++) {
+                    if (a.charAt(i) != b.charAt(i)) return false;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
     public static int codePointCount(final CharSequence text) {
-        if (TextUtils.isEmpty(text)) return 0;
+        if (isEmpty(text)) return 0;
         return Character.codePointCount(text, 0, text.length());
     }
 
@@ -78,7 +131,7 @@
 
     public static boolean containsInCommaSplittableText(final String text,
             final String extraValues) {
-        if (TextUtils.isEmpty(extraValues)) {
+        if (isEmpty(extraValues)) {
             return false;
         }
         return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
@@ -86,7 +139,7 @@
 
     public static String removeFromCommaSplittableTextIfExists(final String text,
             final String extraValues) {
-        if (TextUtils.isEmpty(extraValues)) {
+        if (isEmpty(extraValues)) {
             return EMPTY_STRING;
         }
         final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
@@ -99,7 +152,7 @@
                 result.add(element);
             }
         }
-        return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
+        return join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
     }
 
     /**
@@ -117,7 +170,7 @@
             // Compare each suggestion with each previous suggestion
             for (int j = 0; j < i; j++) {
                 final String previous = suggestions.get(j);
-                if (TextUtils.equals(cur, previous)) {
+                if (equals(cur, previous)) {
                     suggestions.remove(i);
                     i--;
                     break;
@@ -471,7 +524,7 @@
      */
     @UsedForTesting
     public static byte[] hexStringToByteArray(final String hexString) {
-        if (TextUtils.isEmpty(hexString)) {
+        if (isEmpty(hexString)) {
             return null;
         }
         final int N = hexString.length();
@@ -512,67 +565,15 @@
         return lastIndex - i;
     }
 
-    /**
-     * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
-     * <p>
-     * This is equivalent to
-     * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
-     * except that the spans are preserved in the result array.
-     * </p>
-     * @param charSequence the character sequence to be split.
-     * @param regex the regex pattern to be used as the separator.
-     * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
-     * segments. Otherwise, trailing empty segments will be removed before being returned.
-     * @return the array which contains the result. All the spans in the <code>charSequence</code>
-     * is preserved.
-     */
-    @UsedForTesting
-    public static CharSequence[] split(final CharSequence charSequence, final String regex,
-            final boolean preserveTrailingEmptySegments) {
-        // A short-cut for non-spanned strings.
-        if (!(charSequence instanceof Spanned)) {
-            // -1 means that trailing empty segments will be preserved.
-            return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
-        }
-
-        // Hereafter, emulate String.split for CharSequence.
-        final ArrayList<CharSequence> sequences = new ArrayList<>();
-        final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
-        int nextStart = 0;
-        boolean matched = false;
-        while (matcher.find()) {
-            sequences.add(charSequence.subSequence(nextStart, matcher.start()));
-            nextStart = matcher.end();
-            matched = true;
-        }
-        if (!matched) {
-            // never matched. preserveTrailingEmptySegments is ignored in this case.
-            return new CharSequence[] { charSequence };
-        }
-        sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
-        if (!preserveTrailingEmptySegments) {
-            for (int i = sequences.size() - 1; i >= 0; --i) {
-                if (!TextUtils.isEmpty(sequences.get(i))) {
-                    break;
-                }
-                sequences.remove(i);
-            }
-        }
-        return sequences.toArray(new CharSequence[sequences.size()]);
-    }
-
-    @UsedForTesting
     public static class Stringizer<E> {
         public String stringize(final E element) {
             return element != null ? element.toString() : "null";
         }
 
-        @UsedForTesting
         public final String join(final E[] array) {
             return joinStringArray(toStringArray(array), null /* delimiter */);
         }
 
-        @UsedForTesting
         public final String join(final E[] array, final String delimiter) {
             return joinStringArray(toStringArray(array), delimiter);
         }
@@ -606,9 +607,8 @@
      * @param text the text to be examined.
      * @return {@code true} if the last composed word contains line-breaking separator.
      */
-    @UsedForTesting
     public static boolean hasLineBreakCharacter(final String text) {
-        if (TextUtils.isEmpty(text)) {
+        if (isEmpty(text)) {
             return false;
         }
         for (int i = text.length() - 1; i >= 0; --i) {
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 817bb51..08d8bb2 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -39,6 +39,8 @@
         <item>hi_ZZ</item>
         <item>sr_ZZ</item>
     </string-array>
+    <string name="subtype_in_root_locale_hi_ZZ">Hinglish</string>
+    <string name="subtype_in_root_locale_sr_ZZ">Srpski</string>
 
     <!-- Generic subtype label -->
     <string name="subtype_generic">%s</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 583c3b1..e1a72c4 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -214,8 +214,7 @@
     <!-- Description for Hinglish (https://en.wikipedia.org/wiki/Hinglish) keyboard subtype [CHAR LIMIT=25] -->
     <string name="subtype_hi_ZZ">Hinglish</string>
     <!-- Description for Serbian (Latin) keyboard subtype [CHAR LIMIT=25]
-         (Latin) can be an abbreviation to fit in the CHAR LIMIT.
-         Note for Serbian translator: this should be translated with Latin script and (Latin) should be omitted. -->
+         (Latin) can be an abbreviation to fit in the CHAR LIMIT. -->
     <string name="subtype_sr_ZZ">Serbian (Latin)</string>
     <!-- Description for English (UK) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
          (UK) should be an abbreviation of United Kingdom to fit in the CHAR LIMIT.
@@ -233,16 +232,14 @@
          This should be identical to subtype_hi_ZZ aside from the trailing (%s). -->
     <string name="subtype_with_layout_hi_ZZ">Hinglish (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
     <!-- Description for Serbian (Latin) keyboard subtype with explicit keyboard layout [CHAR LIMIT=25]
-         This should be identical to subtype_sr_ZZ aside from the trailing (%s).
-         Note for Serbian translator: this should be translated with Latin script. -->
+         This should be identical to subtype_sr_ZZ aside from the trailing (%s). -->
     <string name="subtype_with_layout_sr_ZZ">Serbian (<xliff:g id="KEYBOARD_LAYOUT" example="QWERTY">%s</xliff:g>)</string>
     <!-- Description for "LANGUAGE_NAME" (Traditional) keyboard subtype [CHAR LIMIT=25]
          (Traditional) can be an abbreviation to fit in the CHAR LIMIT. -->
     <string name="subtype_generic_traditional"><xliff:g id="LANGUAGE_NAME" example="Nepali">%s</xliff:g> (Traditional)</string>
     <!-- Description for "LANGUAGE_NAME" (Compact) keyboard subtype [CHAR LIMIT=25]
-         (Compact) can be an abbreviation to fit in the CHAR LIMIT.
-         TODO: Remove translatable=false once we are settled down with the naming. -->
-    <string name="subtype_generic_compact" translatable="false"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compact)</string>
+         (Compact) can be an abbreviation to fit in the CHAR LIMIT. -->
+    <string name="subtype_generic_compact"><xliff:g id="LANGUAGE_NAME" example="Hindi">%s</xliff:g> (Compact)</string>
     <!-- This string is displayed in a language list that allows to choose a language for
 suggestions in a software keyboard. This setting won't give suggestions in any particular
 language, hence "No language".
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 9f7caa4..bbda9f8 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -28,7 +28,7 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Locale;
 
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index 92bcda4..a1226dc 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -19,7 +19,7 @@
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import javax.annotation.Nonnull;
 
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 04a0f1e..6b2094b 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -38,7 +38,7 @@
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index e66523b..f3aaaef 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -56,9 +56,9 @@
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.DebugSettings;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index abcfff8..f0de86f 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -24,7 +24,7 @@
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreKeysKeyboard extends Keyboard {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
index 5797b7e..2e2ed52 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.keyboard.internal;
 
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import android.text.TextUtils;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index d33e53a..63aab96 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -20,7 +20,7 @@
 import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED;
 
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 /**
  * The string parser of the key specification.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 6c75fb6..c739bf3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -34,8 +34,8 @@
 import com.android.inputmethod.keyboard.KeyboardTheme;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 import com.android.inputmethod.latin.utils.XmlParseUtils;
 import com.android.inputmethod.latin.utils.XmlParseUtils.ParseException;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index 4392dbc..a0bb406 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -22,9 +22,9 @@
 import com.android.inputmethod.compat.CharacterCompat;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 76459f8..1c4ff96 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -24,6 +24,7 @@
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.makedict.DictionaryHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
@@ -33,7 +34,6 @@
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.WordInputEventForPersonalization;
 
 import java.io.File;
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 08e1983..59763c0 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -30,9 +30,9 @@
 
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.personalization.AccountUtils;
 import com.android.inputmethod.latin.utils.ExecutorUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.io.File;
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 0022220..37effee 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -24,8 +24,8 @@
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java
index 0ccea47..82a1327 100644
--- a/java/src/com/android/inputmethod/latin/NgramContext.java
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -20,7 +20,7 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 
diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
index 93598d2..a65304c 100644
--- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
@@ -18,7 +18,7 @@
 
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 0763ef8..834f747 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -35,6 +35,7 @@
 
 import com.android.inputmethod.compat.InputConnectionCompatUtils;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
@@ -42,7 +43,6 @@
 import com.android.inputmethod.latin.utils.NgramContextUtils;
 import com.android.inputmethod.latin.utils.ScriptUtils;
 import com.android.inputmethod.latin.utils.SpannableStringUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TextRange;
 
 import javax.annotation.Nonnull;
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 17df1ea..bb7d243 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -21,11 +21,11 @@
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SuggestionResults;
 
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index a642889..b84de69 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -21,8 +21,8 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 8830521..35436f5 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -20,9 +20,9 @@
 import com.android.inputmethod.event.Event;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index a67f461..f3a2873 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -52,6 +52,7 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
@@ -61,7 +62,6 @@
 import com.android.inputmethod.latin.utils.InputTypeUtils;
 import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 import com.android.inputmethod.latin.utils.StatsUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TextRange;
 
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
index e7808e4..388d578 100644
--- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -21,8 +21,8 @@
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.CombinedFormatUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 6d23fcb..19db606 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -29,11 +29,11 @@
 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.StatsUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.Collections;
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
index 46aef89..70d97a5 100644
--- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -23,7 +23,7 @@
 import com.android.inputmethod.latin.PunctuationSuggestions;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index ac395bf..2c690ae 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -28,7 +28,7 @@
 
 import com.android.inputmethod.compat.TextInfoCompatUtils;
 import com.android.inputmethod.latin.NgramContext;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.SpannableStringUtils;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -71,9 +71,10 @@
             if (!subText.toString().contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
                 continue;
             }
-            final CharSequence[] splitTexts = StringUtils.split(subText,
+            // Split preserving spans.
+            final CharSequence[] splitTexts = SpannableStringUtils.split(subText,
                     AndroidSpellCheckerService.SINGLE_QUOTE,
-                    true /* preserveTrailingEmptySegments */ );
+                    true /* preserveTrailingEmptySegments */);
             if (splitTexts == null || splitTexts.length <= 1) {
                 continue;
             }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 514bfca..3ad8fb9 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -34,11 +34,11 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SuggestionResults;
 
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 02ace6a..2aac7c5 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -31,6 +31,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
index ce25fe6..23ffde2 100644
--- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -18,6 +18,7 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.makedict.DictionaryHeader;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.personalization.PersonalizationHelper;
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index b087e9e..0db63fd 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -21,6 +21,7 @@
 
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 8f0f9bb..56c8249 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -40,6 +40,7 @@
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
 
 /**
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index e3cac97..21daddc 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.utils;
 
+import com.android.inputmethod.latin.common.StringUtils;
+
 import java.util.Locale;
 
 /**
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index cea1d1d..c41817f 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -24,6 +24,12 @@
 import android.text.style.SuggestionSpan;
 import android.text.style.URLSpan;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 public final class SpannableStringUtils {
     /**
      * Copies the spans from the region <code>start...end</code> in
@@ -125,4 +131,53 @@
         final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class);
         return null != spans && spans.length > 0;
     }
+
+    /**
+     * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
+     * <p>
+     * This is equivalent to
+     * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
+     * except that the spans are preserved in the result array.
+     * </p>
+     * @param charSequence the character sequence to be split.
+     * @param regex the regex pattern to be used as the separator.
+     * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
+     * segments. Otherwise, trailing empty segments will be removed before being returned.
+     * @return the array which contains the result. All the spans in the <code>charSequence</code>
+     * is preserved.
+     */
+    @UsedForTesting
+    public static CharSequence[] split(final CharSequence charSequence, final String regex,
+            final boolean preserveTrailingEmptySegments) {
+        // A short-cut for non-spanned strings.
+        if (!(charSequence instanceof Spanned)) {
+            // -1 means that trailing empty segments will be preserved.
+            return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
+        }
+
+        // Hereafter, emulate String.split for CharSequence.
+        final ArrayList<CharSequence> sequences = new ArrayList<>();
+        final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
+        int nextStart = 0;
+        boolean matched = false;
+        while (matcher.find()) {
+            sequences.add(charSequence.subSequence(nextStart, matcher.start()));
+            nextStart = matcher.end();
+            matched = true;
+        }
+        if (!matched) {
+            // never matched. preserveTrailingEmptySegments is ignored in this case.
+            return new CharSequence[] { charSequence };
+        }
+        sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
+        if (!preserveTrailingEmptySegments) {
+            for (int i = sequences.size() - 1; i >= 0; --i) {
+                if (!TextUtils.isEmpty(sequences.get(i))) {
+                    break;
+                }
+                sequences.remove(i);
+            }
+        }
+        return sequences.toArray(new CharSequence[sequences.size()]);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index eb85c1b..55c1dc9 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -28,10 +28,10 @@
 
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Locale;
 
 /**
@@ -59,7 +59,8 @@
     // Keyboard layout to subtype name resource id map.
     private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
     // Exceptional locale whose name should be displayed in Locale.ROOT.
-    static final HashSet<String> sExceptionalLocaleDisplayedInRootLocale = new HashSet<>();
+    private static final HashMap<String, Integer> sExceptionalLocaleDisplayedInRootLocale =
+            new HashMap<>();
     // Exceptional locale to subtype name resource id map.
     private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
     // Exceptional locale to subtype name with layout resource id map.
@@ -73,6 +74,8 @@
             "string/subtype_with_layout_";
     private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX =
             "string/subtype_no_language_";
+    private static final String SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX =
+            "string/subtype_in_root_locale_";
     // 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 =
@@ -117,7 +120,10 @@
         final String[] exceptionalLocaleInRootLocale = res.getStringArray(
                 R.array.subtype_locale_displayed_in_root_locale);
         for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) {
-            sExceptionalLocaleDisplayedInRootLocale.add(exceptionalLocaleInRootLocale[i]);
+            final String localeString = exceptionalLocaleInRootLocale[i];
+            final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString;
+            final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
+            sExceptionalLocaleDisplayedInRootLocale.put(localeString, resId);
         }
 
         final String[] exceptionalLocales = res.getStringArray(
@@ -171,7 +177,7 @@
         if (NO_LANGUAGE.equals(localeString)) {
             return sResources.getConfiguration().locale;
         }
-        if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+        if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
             return Locale.ROOT;
         }
         return LocaleUtils.constructLocaleFromString(localeString);
@@ -190,7 +196,7 @@
     public static String getSubtypeLanguageDisplayName(final String localeString) {
         final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
         final String languageString;
-        if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) {
+        if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
             languageString = localeString;
         } else {
             final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
@@ -205,7 +211,16 @@
             // No language subtype should be displayed in system locale.
             return sResources.getString(R.string.subtype_no_language);
         }
-        final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+        final Integer exceptionalNameResId;
+        if (displayLocale.equals(Locale.ROOT)
+                && sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+            exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString);
+        } else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) {
+            exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+        } else {
+            exceptionalNameResId = null;
+        }
+
         final String displayName;
         if (exceptionalNameResId != null) {
             final RunInLocale<String> getExceptionalName = new RunInLocale<String>() {
diff --git a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
index d3fa0c7..86a5b19 100644
--- a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
+++ b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
@@ -21,6 +21,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
 
diff --git a/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
index a06e7d0..450203d 100644
--- a/native/jni/src/suggest/core/policy/weighting.cpp
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -119,7 +119,7 @@
         return weighting->getSubstitutionCost()
                 + weighting->getMatchedCost(traverseSession, dicNode, inputStateG);
     case CT_NEW_WORD_SPACE_OMISSION:
-        return weighting->getNewWordSpatialCost(traverseSession, dicNode, inputStateG);
+        return weighting->getSpaceOmissionCost(traverseSession, dicNode, inputStateG);
     case CT_MATCH:
         return weighting->getMatchedCost(traverseSession, dicNode, inputStateG);
     case CT_COMPLETION:
diff --git a/native/jni/src/suggest/core/policy/weighting.h b/native/jni/src/suggest/core/policy/weighting.h
index bd6b3cf..863c4ea 100644
--- a/native/jni/src/suggest/core/policy/weighting.h
+++ b/native/jni/src/suggest/core/policy/weighting.h
@@ -57,7 +57,7 @@
             const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
 
-    virtual float getNewWordSpatialCost(const DicTraverseSession *const traverseSession,
+    virtual float getSpaceOmissionCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode, DicNode_InputStateG *const inputStateG) const = 0;
 
     virtual float getNewWordBigramLanguageCost(
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index c715262..68a3645 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -160,8 +160,7 @@
             // TODO: Remove. Do not prune node here.
             const bool allowsErrorCorrections = TRAVERSAL->allowsErrorCorrections(&dicNode);
             // Process for handling space substitution (e.g., hevis => he is)
-            if (allowsErrorCorrections
-                    && TRAVERSAL->isSpaceSubstitutionTerminal(traverseSession, &dicNode)) {
+            if (TRAVERSAL->isSpaceSubstitutionTerminal(traverseSession, &dicNode)) {
                 createNextWordDicNode(traverseSession, &dicNode, true /* spaceSubstitution */);
             }
 
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
index 6a2db68..a6f9a8b 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
@@ -48,17 +48,17 @@
 const float ScoringParams::INSERTION_COST_PROXIMITY_CHAR = 0.674f;
 const float ScoringParams::INSERTION_COST_FIRST_CHAR = 0.639f;
 const float ScoringParams::TRANSPOSITION_COST = 0.5608f;
-const float ScoringParams::SPACE_SUBSTITUTION_COST = 0.334f;
+const float ScoringParams::SPACE_SUBSTITUTION_COST = 0.33f;
+const float ScoringParams::SPACE_OMISSION_COST = 0.1f;
 const float ScoringParams::ADDITIONAL_PROXIMITY_COST = 0.37972f;
 const float ScoringParams::SUBSTITUTION_COST = 0.3806f;
-const float ScoringParams::COST_NEW_WORD = 0.0314f;
 const float ScoringParams::COST_SECOND_OR_LATER_WORD_FIRST_CHAR_UPPERCASE = 0.3224f;
 const float ScoringParams::DISTANCE_WEIGHT_LANGUAGE = 1.1214f;
 const float ScoringParams::COST_FIRST_COMPLETION = 0.4836f;
 const float ScoringParams::COST_COMPLETION = 0.00624f;
 const float ScoringParams::HAS_PROXIMITY_TERMINAL_COST = 0.0683f;
 const float ScoringParams::HAS_EDIT_CORRECTION_TERMINAL_COST = 0.0362f;
-const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.4182f;
+const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.3482f;
 const float ScoringParams::TYPING_BASE_OUTPUT_SCORE = 1.0f;
 const float ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT = 0.1f;
 const float ScoringParams::NORMALIZED_SPATIAL_DISTANCE_THRESHOLD_FOR_EDIT = 0.095f;
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.h b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
index 731424f..b8f8895 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.h
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
@@ -56,9 +56,9 @@
     static const float INSERTION_COST_FIRST_CHAR;
     static const float TRANSPOSITION_COST;
     static const float SPACE_SUBSTITUTION_COST;
+    static const float SPACE_OMISSION_COST;
     static const float ADDITIONAL_PROXIMITY_COST;
     static const float SUBSTITUTION_COST;
-    static const float COST_NEW_WORD;
     static const float COST_SECOND_OR_LATER_WORD_FIRST_CHAR_UPPERCASE;
     static const float DISTANCE_WEIGHT_LANGUAGE;
     static const float COST_FIRST_COMPLETION;
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 8407717..1338ac8 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -150,9 +150,10 @@
         return cost + weightedDistance;
     }
 
-    float getNewWordSpatialCost(const DicTraverseSession *const traverseSession,
+    float getSpaceOmissionCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode, DicNode_InputStateG *inputStateG) const {
-        return ScoringParams::COST_NEW_WORD * traverseSession->getMultiWordCostMultiplier();
+        const float cost = ScoringParams::SPACE_OMISSION_COST;
+        return cost * traverseSession->getMultiWordCostMultiplier();
     }
 
     float getNewWordBigramLanguageCost(const DicTraverseSession *const traverseSession,
@@ -202,7 +203,10 @@
 
     AK_FORCE_INLINE float getSpaceSubstitutionCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const {
-        const float cost = ScoringParams::SPACE_SUBSTITUTION_COST + ScoringParams::COST_NEW_WORD;
+        const int inputIndex = dicNode->getInputIndex(0);
+        const float distanceToSpaceKey = traverseSession->getProximityInfoState(0)
+                ->getPointToKeyLength(inputIndex, KEYCODE_SPACE);
+        const float cost = ScoringParams::SPACE_SUBSTITUTION_COST * distanceToSpaceKey;
         return cost * traverseSession->getMultiWordCostMultiplier();
     }
 
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
index 2361968..0246c49 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetTestsBase.java
@@ -17,9 +17,7 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.Resources;
-import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 import android.view.ContextThemeWrapper;
 import android.view.inputmethod.EditorInfo;
@@ -62,8 +60,8 @@
         }
     };
 
-    private SharedPreferences mSharedPreferences;
-    private String mSavedAdditionalSubtypes;
+    private RichInputMethodManager mRichImm;
+    private InputMethodSubtype[] mSavedAdditionalSubtypes;
     private int mScreenMetrics;
 
     protected abstract int getKeyboardThemeForTests();
@@ -72,15 +70,17 @@
     protected void setUp() throws Exception {
         super.setUp();
         final Context context = getContext();
-        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
         final Resources res = context.getResources();
+        RichInputMethodManager.init(context);
+        mRichImm = RichInputMethodManager.getInstance();
 
-        // Save additional subtypes preference.
-        mSavedAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(mSharedPreferences, res);
-        final String predefinedSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
-                res.getStringArray(R.array.predefined_subtypes));
-        // Reset additional subtypes to predefined ones.
-        Settings.writePrefAdditionalSubtypes(mSharedPreferences, predefinedSubtypes);
+        // Save and reset additional subtypes preference.
+        mSavedAdditionalSubtypes = mRichImm.getAdditionalSubtypes(context);
+        final InputMethodSubtype[] predefinedAdditionalSubtypes =
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(
+                        AdditionalSubtypeUtils.createPrefSubtypes(
+                                res.getStringArray(R.array.predefined_subtypes)));
+        mRichImm.setAdditionalInputMethodSubtypes(predefinedAdditionalSubtypes);
 
         final KeyboardTheme keyboardTheme = KeyboardTheme.searchKeyboardThemeById(
                 getKeyboardThemeForTests(), KeyboardTheme.KEYBOARD_THEMES);
@@ -88,10 +88,8 @@
         KeyboardLayoutSet.onKeyboardThemeChanged();
 
         mScreenMetrics = Settings.readScreenMetrics(res);
-        RichInputMethodManager.init(context);
-        final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
 
-        final InputMethodInfo imi = richImm.getInputMethodInfoOfThisIme();
+        final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme();
         final int subtypeCount = imi.getSubtypeCount();
         for (int index = 0; index < subtypeCount; index++) {
             mAllSubtypesList.add(imi.getSubtypeAt(index));
@@ -101,7 +99,7 @@
     @Override
     protected void tearDown() throws Exception {
         // Restore additional subtypes preference.
-        Settings.writePrefAdditionalSubtypes(mSharedPreferences, mSavedAdditionalSubtypes);
+        mRichImm.setAdditionalInputMethodSubtypes(mSavedAdditionalSubtypes);
         super.tearDown();
     }
 
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
index a6aac35..afc5448 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
@@ -20,7 +20,7 @@
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
index 0547ea3..9bb5f18 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyOutput.java
@@ -19,7 +19,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.common.Constants;
-import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.common.StringUtils;
 
 import java.util.Locale;
 
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index 7d356a4..c76f6f4 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -674,10 +674,15 @@
                 mEditText.getText().toString());
     }
 
-    private void typeWordAndPutCursorInside(final String word, final int startPos) {
+    private void typeOrGestureWordAndPutCursorInside(final boolean gesture, final String word,
+            final int startPos) {
         final int END_OF_WORD = startPos + word.length();
         final int NEW_CURSOR_POSITION = startPos + word.length() / 2;
-        type(word);
+        if (gesture) {
+            gesture(word);
+        } else {
+            type(word);
+        }
         sendUpdateForCursorMoveTo(END_OF_WORD);
         runMessages();
         sendUpdateForCursorMoveTo(NEW_CURSOR_POSITION);
@@ -687,6 +692,14 @@
                 startPos, END_OF_WORD);
     }
 
+    private void typeWordAndPutCursorInside(final String word, final int startPos) {
+        typeOrGestureWordAndPutCursorInside(false /* gesture */, word, startPos);
+    }
+
+    private void gestureWordAndPutCursorInside(final String word, final int startPos) {
+        typeOrGestureWordAndPutCursorInside(true /* gesture */, word, startPos);
+    }
+
     private void ensureComposingSpanPos(final String message, final int from, final int to) {
         assertEquals(message, from, BaseInputConnection.getComposingSpanStart(mEditText.getText()));
         assertEquals(message, to, BaseInputConnection.getComposingSpanEnd(mEditText.getText()));
@@ -703,6 +716,23 @@
         int cursorPos = sendUpdateForCursorMoveToEndOfLine();
         runMessages();
         type(" ");
+        assertEquals("mbo", "some thing ", mEditText.getText().toString());
+        typeWordAndPutCursorInside(WORD_TO_TYPE, cursorPos + 1 /* startPos */);
+        type(Constants.CODE_DELETE);
+        ensureComposingSpanPos("space while in the middle of a word cancels composition", -1, -1);
+    }
+
+    public void testTypeWithinGestureComposing() {
+        final String WORD_TO_TYPE = "something";
+        final String EXPECTED_RESULT = "some thing";
+        gestureWordAndPutCursorInside(WORD_TO_TYPE, 0 /* startPos */);
+        type(" ");
+        ensureComposingSpanPos("space while in the middle of a word cancels composition", -1, -1);
+        assertEquals("space in the middle of a composing word", EXPECTED_RESULT,
+                mEditText.getText().toString());
+        int cursorPos = sendUpdateForCursorMoveToEndOfLine();
+        runMessages();
+        type(" ");
         typeWordAndPutCursorInside(WORD_TO_TYPE, cursorPos + 1 /* startPos */);
         type(Constants.CODE_DELETE);
         ensureComposingSpanPos("space while in the middle of a word cancels composition", -1, -1);
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 78b4b18..161336a 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -43,10 +43,10 @@
 import com.android.inputmethod.latin.Dictionary.PhonyDictionary;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.DebugSettings;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.LocaleUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.Locale;
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index 34c2600..bcf016a 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -31,11 +31,11 @@
 import android.view.inputmethod.InputConnectionWrapper;
 
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
 import com.android.inputmethod.latin.utils.NgramContextUtils;
 import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.ScriptUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.TextRange;
 
 import java.util.Locale;
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
index 8f5e8f7..20256e6 100644
--- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -20,8 +20,8 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
-import com.android.inputmethod.latin.utils.StringUtils;
 
 /**
  * Unit tests for WordComposer.
diff --git a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java
index e6131cf..83afd78 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtilsTests.java
@@ -23,6 +23,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 
@@ -36,6 +37,7 @@
 
     private RichInputMethodManager mRichImm;
     private Resources mRes;
+    private InputMethodSubtype mSavedAddtionalSubtypes[];
 
     RichInputMethodSubtype EN_US;
     RichInputMethodSubtype EN_GB;
@@ -45,6 +47,8 @@
     RichInputMethodSubtype FR_CH;
     RichInputMethodSubtype DE;
     RichInputMethodSubtype DE_CH;
+    RichInputMethodSubtype HI;
+    RichInputMethodSubtype SR;
     RichInputMethodSubtype ZZ;
     RichInputMethodSubtype DE_QWERTY;
     RichInputMethodSubtype FR_QWERTZ;
@@ -54,17 +58,27 @@
     RichInputMethodSubtype ZZ_AZERTY;
     RichInputMethodSubtype ZZ_PC;
 
-    // This is a preliminary subtype and may not exist.
-    RichInputMethodSubtype HI_LATN;
+    // These are preliminary subtypes and may not exist.
+    RichInputMethodSubtype HI_LATN; // Hinglish
+    RichInputMethodSubtype SR_LATN; // Serbian Latin
+    RichInputMethodSubtype HI_LATN_DVORAK;
+    RichInputMethodSubtype SR_LATN_QWERTY;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         final Context context = getContext();
+        mRes = context.getResources();
         RichInputMethodManager.init(context);
         mRichImm = RichInputMethodManager.getInstance();
-        mRes = context.getResources();
-        SubtypeLocaleUtils.init(context);
+
+        // Save and reset additional subtypes
+        mSavedAddtionalSubtypes = mRichImm.getAdditionalSubtypes(context);
+        final InputMethodSubtype[] predefinedAddtionalSubtypes =
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(
+                        AdditionalSubtypeUtils.createPrefSubtypes(
+                                mRes.getStringArray(R.array.predefined_subtypes)));
+        mRichImm.setAdditionalInputMethodSubtypes(predefinedAddtionalSubtypes);
 
         final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme();
         final int subtypeCount = imi.getSubtypeCount();
@@ -89,6 +103,10 @@
                 Locale.GERMAN.toString(), "qwertz"));
         DE_CH = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 "de_CH", "swiss"));
+        HI = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "hi", "hindi"));
+        SR = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "sr", "south_slavic"));
         ZZ = new RichInputMethodSubtype(mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 SubtypeLocaleUtils.NO_LANGUAGE, "qwerty"));
         DE_QWERTY = new RichInputMethodSubtype(
@@ -117,7 +135,25 @@
                 "hi_ZZ", "qwerty");
         if (hiLatn != null) {
             HI_LATN = new RichInputMethodSubtype(hiLatn);
+            HI_LATN_DVORAK = new RichInputMethodSubtype(
+                    AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                            "hi_ZZ", "dvorak"));
         }
+        final InputMethodSubtype srLatn = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "sr_ZZ", "serbian_qwertz");
+        if (srLatn != null) {
+            SR_LATN = new RichInputMethodSubtype(srLatn);
+            SR_LATN_QWERTY = new RichInputMethodSubtype(
+                    AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                            "sr_ZZ", "qwerty"));
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Restore additional subtypes.
+        mRichImm.setAdditionalInputMethodSubtypes(mSavedAddtionalSubtypes);
+        super.tearDown();
     }
 
     public void testAllFullDisplayNameForSpacebar() {
@@ -150,10 +186,11 @@
                 continue;
             }
             final Locale locale = locales[0];
-            if (SubtypeLocaleUtils.sExceptionalLocaleDisplayedInRootLocale.contains(
-                    locale.toString())) {
+            final Locale displayLocale = SubtypeLocaleUtils.getDisplayLocaleOfSubtypeLocale(
+                    locale.toString());
+            if (Locale.ROOT.equals(displayLocale)) {
                 // Skip test because the language part of this locale string doesn't represent
-                // the locale to be displayed on the spacebar (for example hi_ZZ and Hinglish).
+                // the locale to be displayed on the spacebar (for example Hinglish).
                 continue;
             }
             final String spacebarText = subtype.getMiddleDisplayName();
@@ -162,30 +199,36 @@
                         subtype.getRawSubtype()), spacebarText);
             } else {
                 assertEquals(subtypeName,
-                        SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale.getLanguage()),
+                        SubtypeLocaleUtils.getSubtypeLanguageDisplayName(locale.toString()),
                         spacebarText);
             }
         }
     }
 
     // InputMethodSubtype's display name for spacebar text in its locale.
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  Middle     Full
-    // ------ ------- - --------- ----------------------
-    //  en_US qwerty  F  English   English (US)           exception
-    //  en_GB qwerty  F  English   English (UK)           exception
-    //  es_US spanish F  Español   Español (EE.UU.)       exception
-    //  fr    azerty  F  Français  Français
-    //  fr_CA qwerty  F  Français  Français (Canada)
-    //  fr_CH swiss   F  Français  Français (Suisse)
-    //  de    qwertz  F  Deutsch   Deutsch
-    //  de_CH swiss   F  Deutsch   Deutsch (Schweiz)
-    //  hi_ZZ qwerty  F  Hinglish  Hinglish
-    //  zz    qwerty  F  QWERTY    QWERTY
-    //  fr    qwertz  T  Français  Français
-    //  de    qwerty  T  Deutsch   Deutsch
-    //  en_US azerty  T  English   English (US)
-    //  zz    azerty  T  AZERTY    AZERTY
+    //               isAdditionalSubtype (T=true, F=false)
+    // locale layout         |  Middle     Full
+    // ------ -------------- - --------- ----------------------
+    //  en_US qwerty         F  English   English (US)           exception
+    //  en_GB qwerty         F  English   English (UK)           exception
+    //  es_US spanish        F  Español   Español (EE.UU.)       exception
+    //  fr    azerty         F  Français  Français
+    //  fr_CA qwerty         F  Français  Français (Canada)
+    //  fr_CH swiss          F  Français  Français (Suisse)
+    //  de    qwertz         F  Deutsch   Deutsch
+    //  de_CH swiss          F  Deutsch   Deutsch (Schweiz)
+    //  hi    hindi          F  हिन्दी       हिन्दी
+    //  hi_ZZ qwerty         F  Hinglish  Hinglish               exception
+    //  sr    south_slavic   F  Српски    Српски
+    //  sr_ZZ serbian_qwertz F  Srpski    Srpski                 exception
+    //  zz    qwerty         F  QWERTY    QWERTY
+    //  fr    qwertz         T  Français  Français
+    //  de    qwerty         T  Deutsch   Deutsch
+    //  en_US azerty         T  English   English (US)
+    //  en_GB dvorak         T  English   English (UK)
+    //  hi_ZZ dvorak         T  Hinglish  Hinglish               exception
+    //  sr_ZZ qwerty         T  Srpski    Srpski                 exception
+    //  zz    azerty         T  AZERTY    AZERTY
 
     private final RunInLocale<Void> testsPredefinedSubtypesForSpacebar = new RunInLocale<Void>() {
         @Override
@@ -198,11 +241,9 @@
             assertEquals("fr_CH", "Français (Suisse)", FR_CH.getFullDisplayName());
             assertEquals("de", "Deutsch", DE.getFullDisplayName());
             assertEquals("de_CH", "Deutsch (Schweiz)", DE_CH.getFullDisplayName());
+            assertEquals("hi", "हिन्दी", HI.getFullDisplayName());
+            assertEquals("sr", "Српски", SR.getFullDisplayName());
             assertEquals("zz", "QWERTY", ZZ.getFullDisplayName());
-            // This is a preliminary subtype and may not exist.
-            if (HI_LATN != null) {
-                assertEquals("hi_ZZ", "Hinglish", HI_LATN.getFullDisplayName());
-            }
 
             assertEquals("en_US", "English", EN_US.getMiddleDisplayName());
             assertEquals("en_GB", "English", EN_GB.getMiddleDisplayName());
@@ -213,10 +254,16 @@
             assertEquals("de", "Deutsch", DE.getMiddleDisplayName());
             assertEquals("de_CH", "Deutsch", DE_CH.getMiddleDisplayName());
             assertEquals("zz", "QWERTY", ZZ.getMiddleDisplayName());
-            // This is a preliminary subtype and may not exist.
+
+            // These are preliminary subtypes and may not exist.
             if (HI_LATN != null) {
+                assertEquals("hi_ZZ", "Hinglish", HI_LATN.getFullDisplayName());
                 assertEquals("hi_ZZ", "Hinglish", HI_LATN.getMiddleDisplayName());
             }
+            if (SR_LATN != null) {
+                assertEquals("sr_ZZ", "Srpski", SR_LATN.getFullDisplayName());
+                assertEquals("sr_ZZ", "Srpski", SR_LATN.getMiddleDisplayName());
+            }
             return null;
         }
     };
@@ -239,6 +286,16 @@
             assertEquals("es_US colemak", "Español", ES_US_COLEMAK.getMiddleDisplayName());
             assertEquals("zz azerty", "AZERTY", ZZ_AZERTY.getMiddleDisplayName());
             assertEquals("zz pc", "PC", ZZ_PC.getMiddleDisplayName());
+
+            // These are preliminary subtypes and may not exist.
+            if (HI_LATN_DVORAK != null) {
+                assertEquals("hi_ZZ dvorak", "Hinglish", HI_LATN_DVORAK.getFullDisplayName());
+                assertEquals("hi_ZZ dvorak", "Hinglish", HI_LATN_DVORAK.getMiddleDisplayName());
+            }
+            if (SR_LATN_QWERTY != null) {
+                assertEquals("sr_ZZ qwerty", "Srpski", SR_LATN_QWERTY.getFullDisplayName());
+                assertEquals("sr_ZZ qwerty", "Srpski", SR_LATN_QWERTY.getMiddleDisplayName());
+            }
             return null;
         }
     };
diff --git a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
index 7d9d399..0389fef 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
@@ -23,6 +23,8 @@
 import android.text.SpannedString;
 
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
+import com.android.inputmethod.latin.utils.SpannableStringUtils;
 
 import java.util.Arrays;
 import java.util.List;
@@ -375,9 +377,9 @@
         spannableString.setSpan(span1, 0, 7, SPAN1_FLAGS);
         spannableString.setSpan(span2, 0, 5, SPAN2_FLAGS);
         spannableString.setSpan(span3, 12, 13, SPAN3_FLAGS);
-        final CharSequence[] charSequencesFromSpanned = StringUtils.split(
+        final CharSequence[] charSequencesFromSpanned = SpannableStringUtils.split(
                 spannableString, " ", true /* preserveTrailingEmptySegmengs */);
-        final CharSequence[] charSequencesFromString = StringUtils.split(
+        final CharSequence[] charSequencesFromString = SpannableStringUtils.split(
                 spannableString.toString(), " ", true /* preserveTrailingEmptySegmengs */);
 
 
@@ -456,44 +458,44 @@
     }
 
     public void testSplitCharSequencePreserveTrailingEmptySegmengs() {
-        assertEquals(1, StringUtils.split("", " ",
+        assertEquals(1, SpannableStringUtils.split("", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(1, StringUtils.split(new SpannedString(""), " ",
+        assertEquals(1, SpannableStringUtils.split(new SpannedString(""), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(1, StringUtils.split("", " ",
+        assertEquals(1, SpannableStringUtils.split("", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(1, StringUtils.split(new SpannedString(""), " ",
+        assertEquals(1, SpannableStringUtils.split(new SpannedString(""), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(0, StringUtils.split(" ", " ",
+        assertEquals(0, SpannableStringUtils.split(" ", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(0, StringUtils.split(new SpannedString(" "), " ",
+        assertEquals(0, SpannableStringUtils.split(new SpannedString(" "), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(2, StringUtils.split(" ", " ",
+        assertEquals(2, SpannableStringUtils.split(" ", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(2, StringUtils.split(new SpannedString(" "), " ",
+        assertEquals(2, SpannableStringUtils.split(new SpannedString(" "), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(3, StringUtils.split("a b c  ", " ",
+        assertEquals(3, SpannableStringUtils.split("a b c  ", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(3, StringUtils.split(new SpannedString("a b c  "), " ",
+        assertEquals(3, SpannableStringUtils.split(new SpannedString("a b c  "), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(5, StringUtils.split("a b c  ", " ",
+        assertEquals(5, SpannableStringUtils.split("a b c  ", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(5, StringUtils.split(new SpannedString("a b c  "), " ",
+        assertEquals(5, SpannableStringUtils.split(new SpannedString("a b c  "), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(6, StringUtils.split("a     b ", " ",
+        assertEquals(6, SpannableStringUtils.split("a     b ", " ",
                 false /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(6, StringUtils.split(new SpannedString("a     b "), " ",
+        assertEquals(6, SpannableStringUtils.split(new SpannedString("a     b "), " ",
                 false /* preserveTrailingEmptySegmengs */).length);
 
-        assertEquals(7, StringUtils.split("a     b ", " ",
+        assertEquals(7, SpannableStringUtils.split("a     b ", " ",
                 true /* preserveTrailingEmptySegmengs */).length);
-        assertEquals(7, StringUtils.split(new SpannedString("a     b "), " ",
+        assertEquals(7, SpannableStringUtils.split(new SpannedString("a     b "), " ",
                 true /* preserveTrailingEmptySegmengs */).length);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
index dfc3fec..54f478f 100644
--- a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -23,6 +23,7 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
 
@@ -36,6 +37,7 @@
 
     private RichInputMethodManager mRichImm;
     private Resources mRes;
+    private InputMethodSubtype mSavedAddtionalSubtypes[];
 
     InputMethodSubtype EN_US;
     InputMethodSubtype EN_GB;
@@ -45,6 +47,8 @@
     InputMethodSubtype FR_CH;
     InputMethodSubtype DE;
     InputMethodSubtype DE_CH;
+    InputMethodSubtype HI;
+    InputMethodSubtype SR;
     InputMethodSubtype ZZ;
     InputMethodSubtype DE_QWERTY;
     InputMethodSubtype FR_QWERTZ;
@@ -54,17 +58,27 @@
     InputMethodSubtype ZZ_AZERTY;
     InputMethodSubtype ZZ_PC;
 
-    // This is a preliminary subtype and may not exist.
-    InputMethodSubtype HI_LATN;
+    // These are preliminary subtypes and may not exist.
+    InputMethodSubtype HI_LATN; // Hinglish
+    InputMethodSubtype SR_LATN; // Serbian Latin
+    InputMethodSubtype HI_LATN_DVORAK; // Hinglis Dvorak
+    InputMethodSubtype SR_LATN_QWERTY; // Serbian Latin Qwerty
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         final Context context = getContext();
+        mRes = context.getResources();
         RichInputMethodManager.init(context);
         mRichImm = RichInputMethodManager.getInstance();
-        mRes = context.getResources();
-        SubtypeLocaleUtils.init(context);
+
+        // Save and reset additional subtypes
+        mSavedAddtionalSubtypes = mRichImm.getAdditionalSubtypes(context);
+        final InputMethodSubtype[] predefinedAddtionalSubtypes =
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(
+                        AdditionalSubtypeUtils.createPrefSubtypes(
+                                mRes.getStringArray(R.array.predefined_subtypes)));
+        mRichImm.setAdditionalInputMethodSubtypes(predefinedAddtionalSubtypes);
 
         final InputMethodInfo imi = mRichImm.getInputMethodInfoOfThisIme();
         final int subtypeCount = imi.getSubtypeCount();
@@ -89,6 +103,10 @@
                 Locale.GERMAN.toString(), "qwertz");
         DE_CH = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 "de_CH", "swiss");
+        HI = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "hi", "hindi");
+        SR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "sr", "south_slavic");
         ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
         DE_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
@@ -107,6 +125,22 @@
                 SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty");
 
         HI_LATN = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet("hi_ZZ", "qwerty");
+        if (HI_LATN != null) {
+            HI_LATN_DVORAK = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                    "hi_ZZ", "dvorak");
+        }
+        SR_LATN = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet("sr_ZZ", "serbian_qwertz");
+        if (SR_LATN != null) {
+            SR_LATN_QWERTY = AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                    "sr_ZZ", "qwerty");
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Restore additional subtypes.
+        mRichImm.setAdditionalInputMethodSubtypes(mSavedAddtionalSubtypes);
+        super.tearDown();
     }
 
     public void testAllFullDisplayName() {
@@ -139,11 +173,9 @@
         assertEquals("fr_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CH));
         assertEquals("de", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE));
         assertEquals("de_CH", "swiss", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_CH));
+        assertEquals("hi", "hindi", SubtypeLocaleUtils.getKeyboardLayoutSetName(HI));
+        assertEquals("sr", "south_slavic", SubtypeLocaleUtils.getKeyboardLayoutSetName(SR));
         assertEquals("zz", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ));
-        // This is a preliminary subtype and may not exist.
-        if (HI_LATN != null) {
-            assertEquals("hi_ZZ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(HI_LATN));
-        }
 
         assertEquals("de qwerty", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE_QWERTY));
         assertEquals("fr qwertz", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_QWERTZ));
@@ -155,28 +187,46 @@
                 SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US_COLEMAK));
         assertEquals("zz azerty", "azerty",
                 SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ_AZERTY));
+
+        // These are preliminary subtypes and may not exist.
+        if (HI_LATN != null) {
+            assertEquals("hi_ZZ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(HI_LATN));
+            assertEquals("hi_ZZ dvorak", "dvorak",
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(HI_LATN_DVORAK));
+        }
+        if (SR_LATN != null) {
+            assertEquals("sr_ZZ", "serbian_qwertz",
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(SR_LATN));
+            assertEquals("sr_ZZ qwerty", "qwerty",
+                    SubtypeLocaleUtils.getKeyboardLayoutSetName(SR_LATN_QWERTY));
+        }
     }
 
     // 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)
-    //  fr_CH swiss   F  French (Switzerland)
-    //  de    qwertz  F  German
-    //  de_CH swiss   F  German (Switzerland)
-    //  hi_ZZ qwerty  F  Hinglish
-    //  zz    qwerty  F  Alphabet (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  Alphabet (PC)
+    //               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)
+    //  fr_CH swiss          F  French (Switzerland)
+    //  de    qwertz         F  German
+    //  de_CH swiss          F  German (Switzerland)
+    //  hi    hindi          F  Hindi
+    //  hi_ZZ qwerty         F  Hinglish                exception
+    //  sr    south_slavic   F  Serbian
+    //  sr_ZZ serbian_qwertz F  Serbian (Latin)         exception
+    //  zz    qwerty         F  Alphabet (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
+    //  hi_ZZ dvorak         T  Hinglish (Dvorka)       exception
+    //  sr_ZZ qwerty         T  Serbian (QWERTY)        exception
+    //  zz    pc             T  Alphabet (PC)
 
     public void testPredefinedSubtypesInEnglishSystemLocale() {
         final RunInLocale<Void> tests = new RunInLocale<Void>() {
@@ -198,13 +248,21 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
                 assertEquals("de_CH", "German (Switzerland)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
+                assertEquals("hi", "Hindi",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI));
+                assertEquals("sr", "Serbian",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR));
                 assertEquals("zz", "Alphabet (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
-                // This is a preliminary subtype and may not exist.
+                // These are preliminary subtypes and may not exist.
                 if (HI_LATN != null) {
                     assertEquals("hi_ZZ", "Hinglish",
                             SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN));
                 }
+                if (SR_LATN != null) {
+                    assertEquals("sr_ZZ", "Serbian (Latin)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN));
+                }
                 return null;
             }
         };
@@ -229,6 +287,15 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY));
                 assertEquals("zz pc", "Alphabet (PC)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
+                // These are preliminary subtypes and may not exist.
+                if (HI_LATN_DVORAK != null) {
+                    assertEquals("hi_ZZ", "Hinglish (Dvorak)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN_DVORAK));
+                }
+                if (SR_LATN_QWERTY != null) {
+                    assertEquals("sr_ZZ", "Serbian (QWERTY)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY));
+                }
                 return null;
             }
         };
@@ -239,22 +306,27 @@
     //        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)
-    //  fr_CH swiss   F  Français (Suisse)
-    //  de    qwertz  F  Allemand
-    //  de_CH swiss   F  Allemand (Suisse)
-    //  hi_ZZ qwerty  F  Hinglish
-    //  zz    qwerty  F  Alphabet latin (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  Alphabet latin (PC)
+    //  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)
+    //  fr_CH swiss          F  Français (Suisse)
+    //  de    qwertz         F  Allemand
+    //  de_CH swiss          F  Allemand (Suisse)
+    //  hi    hindi          F  Hindi                            exception
+    //  hi_ZZ qwerty         F  Hindi/Anglais                    exception
+    //  sr    south_slavic   F  Serbe                            exception
+    //  sr_ZZ serbian_qwertz F  Serbe (latin)                    exception
+    //  zz    qwerty         F  Alphabet latin (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
+    //  hi_ZZ dvorak         T  Hindi/Anglais (Dvorka)           exception
+    //  sr_ZZ qwerty         T  Serbe (QWERTY)                   exception
+    //  zz    pc             T  Alphabet latin (PC)
 
     public void testPredefinedSubtypesInFrenchSystemLocale() {
         final RunInLocale<Void> tests = new RunInLocale<Void>() {
@@ -276,13 +348,21 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
                 assertEquals("de_CH", "Allemand (Suisse)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_CH));
+                assertEquals("hi", "Hindi",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI));
+                assertEquals("sr", "Serbe",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR));
                 assertEquals("zz", "Alphabet latin (QWERTY)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
-                // This is a preliminary subtype and may not exist.
+                // These are preliminary subtypes and may not exist.
                 if (HI_LATN != null) {
                     assertEquals("hi_ZZ", "Hindi/Anglais",
                             SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN));
                 }
+                if (SR_LATN != null) {
+                    assertEquals("sr_ZZ", "Serbe (latin)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN));
+                }
                 return null;
             }
         };
@@ -307,12 +387,77 @@
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_AZERTY));
                 assertEquals("zz pc", "Alphabet latin (PC)",
                         SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
+                // These are preliminary subtypes and may not exist.
+                if (HI_LATN_DVORAK != null) {
+                    assertEquals("hi_ZZ", "Hindi/Anglais (Dvorak)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN_DVORAK));
+                }
+                if (SR_LATN_QWERTY != null) {
+                    assertEquals("sr_ZZ", "Serbe (QWERTY)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY));
+                }
                 return null;
             }
         };
         tests.runInLocale(mRes, Locale.FRENCH);
     }
 
+    // InputMethodSubtype's display name in system locale (hi).
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  display name
+    // ------ ------- - ----------------------
+    //  hi    hindi   F  हिन्दी
+    //  hi_ZZ qwerty  F  हिंग्लिश
+    //  hi_ZZ dvorak  T  हिंग्लिश (Dvorak)
+
+    public void testHinglishSubtypesInHindiSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job (final Resources res) {
+                assertEquals("hi", "हिन्दी",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI));
+                // These are preliminary subtypes and may not exist.
+                if (HI_LATN != null) {
+                    assertEquals("hi_ZZ", "हिंग्लिश",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN));
+                    assertEquals("hi_ZZ", "हिंग्लिश (Dvorak)",
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(HI_LATN_DVORAK));
+                }
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, new Locale("hi"));
+    }
+
+    // InputMethodSubtype's display name in system locale (sr).
+    //               isAdditionalSubtype (T=true, F=false)
+    // locale layout         |  display name
+    // ------ -------------- - ----------------------
+    //  sr    south_slavic   F  Српски
+    //  sr_ZZ serbian_qwertz F  српски (латиница)
+    //  sr_ZZ qwerty         T  српски (QWERTY)
+
+    public void testSerbianLatinSubtypesInSerbianSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job (final Resources res) {
+                assertEquals("sr", "Српски",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR));
+                // These are preliminary subtypes and may not exist.
+                if (SR_LATN != null) {
+                    // TODO: Uncommented because of the current translation of these strings
+                    // in Seriban are described in Latin script.
+//                    assertEquals("sr_ZZ", "српски (латиница)",
+//                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN));
+//                    assertEquals("sr_ZZ", "српски (QWERTY)",
+//                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(SR_LATN_QWERTY));
+                }
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, new Locale("sr"));
+    }
+
     public void testIsRtlLanguage() {
         // Known Right-to-Left language subtypes.
         final InputMethodSubtype ARABIC = mRichImm
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index 09af9a0..90be411 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -60,8 +60,7 @@
         latin/utils/FileUtils.java \
         latin/utils/JniUtils.java \
         latin/utils/LocaleUtils.java \
-        latin/utils/ResizableIntArray.java \
-        latin/utils/StringUtils.java
+        latin/utils/ResizableIntArray.java
 
 LATINIME_OVERRIDABLE_SRC_FILES_FOR_DICTTOOL := \
         latin/define/DebugFlags.java