Fix Serbian Latin subtype handling

This CL also adds unit tests for Hinglish and Serbian Latin.

Bug: 9687668
Bug: 17169632
Change-Id: Ib9aa1bcdf5b390a9d8c61f07165beacf850e2692
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/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index eb85c1b..3e6680b 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -31,7 +31,6 @@
 
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Locale;
 
 /**
@@ -59,7 +58,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 +73,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 +119,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 +176,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 +195,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 +210,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/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/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/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