Utilize InputMethodSubtype and additional subtype

* Remove de_QY and fr_CH from supported subtypes
* Add de-qwerty and fr-qwertz predefined additional subtypes instead.

Change-Id: I49e8ba0299529302f2b91b4d018b07304cdd6897
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index bfe4232..892c72a 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -134,24 +134,24 @@
         <item>5</item>
     </string-array>
 
-    <!-- Subtype locale name exceptions -->
+    <!-- Subtype locale display name exceptions -->
     <string-array name="subtype_locale_exception_keys">
         <item>en_US</item>
         <item>en_GB</item>
-        <item>*_QY</item>
-        <item>QY</item>
     </string-array>
     <string-array name="subtype_locale_exception_values">
         <item>English (US)</item>
         <item>English (UK)</item>
-        <item>@string/subtype_generic_qwerty</item>
-        <item>QWERTY</item>
     </string-array>
 
     <!-- Generic subtype label -->
     <string name="subtype_generic">%s</string>
     <!-- Description for generic QWERTY keyboard subtype -->
     <string name="subtype_generic_qwerty">%s (QWERTY)</string>
+    <!-- Description for generic QWERTZ keyboard subtype -->
+    <string name="subtype_generic_qwertz">%s (QWERTZ)</string>
+    <!-- Description for generic AZERTY keyboard subtype -->
+    <string name="subtype_generic_azerty">%s (AZERTY)</string>
 
     <!-- dictionary pack package name /settings activity (for shared prefs and settings) -->
     <string name="dictionary_pack_package_name">com.google.android.inputmethod.latin.dictionarypack</string>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index ba45343..ca0b24e 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -28,7 +28,6 @@
     cs: Czech/qwertz
     da: Danish/nordic
     de: German/qwertz
-    de_QY: German (QWERTY)/qwerty
     el: Greek/greek
     en_US: English United States/qwerty
     en_GB: English Great Britain/qwerty
@@ -38,7 +37,6 @@
     fi: Finnish/nordic
     fr: French/azerty
     fr_CA: French Canada/qwerty
-    fr_CH: French Switzerland/qwertz
     hi: Hindi/hindi
     hr: Croatian/qwertz
     hu: Hungarian/qwertz
@@ -64,7 +62,7 @@
     tr: Turkish/qwerty
     uk: Ukrainian/east_slavic
     vi: Vietnamese/qwerty
-    zz_QY: QWERTY/qwerty
+    zz: QWERTY/qwerty
     -->
 <!-- TODO: use <lang>_keyboard icon instead of a common keyboard icon. -->
 <!-- If IME doesn't have an applicable subtype, the first subtype will be used as a default
@@ -121,12 +119,6 @@
             android:imeSubtypeExtraValue="KeyboardLayoutSet=qwertz,AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
-            android:label="@string/subtype_generic_qwerty"
-            android:imeSubtypeLocale="de"
-            android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty:de_QY,AsciiCapable"
-    />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:imeSubtypeLocale="el"
             android:imeSubtypeMode="keyboard"
@@ -170,12 +162,6 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
-            android:imeSubtypeLocale="fr_CH"
-            android:imeSubtypeMode="keyboard"
-            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwertz,AsciiCapable"
-    />
-    <subtype android:icon="@drawable/ic_subtype_keyboard"
-            android:label="@string/subtype_generic"
             android:imeSubtypeLocale="hi"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=hindi"
@@ -327,7 +313,7 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_no_language_qwerty"
-            android:imeSubtypeLocale="zz_QY"
+            android:imeSubtypeLocale="zz"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable"
     />
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 0438452..4bc82d2 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -53,6 +53,10 @@
         sInstance.mImm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
     }
 
+    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+        mImm.setAdditionalInputMethodSubtypes(imiId, subtypes);
+    }
+
     public InputMethodSubtype getCurrentInputMethodSubtype() {
         return mImm.getCurrentInputMethodSubtype();
     }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 58225e9..d54acf7 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -793,7 +793,7 @@
                     }
                 };
                 // Null means the current system locale.
-                final Locale locale = language.equals(SubtypeLocale.NO_LANGUAGE)
+                final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
                         ? null : params.mId.mLocale;
                 job.runInLocale(mResources, locale);
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index e350818..b7eb8f6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -19,9 +19,12 @@
 import android.text.InputType;
 import android.text.TextUtils;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.latin.InputTypeUtils;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.SubtypeLocale;
 
 import java.util.Arrays;
 import java.util.Locale;
@@ -53,6 +56,7 @@
 
     private static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
 
+    public final InputMethodSubtype mSubtype;
     public final Locale mLocale;
     public final int mOrientation;
     public final int mWidth;
@@ -67,10 +71,11 @@
 
     private final int mHashCode;
 
-    public KeyboardId(int elementId, Locale locale, int orientation, int width, int mode,
-            EditorInfo editorInfo, boolean clobberSettingsKey, boolean shortcutKeyEnabled,
+    public KeyboardId(int elementId, InputMethodSubtype subtype, int orientation, int width,
+            int mode, EditorInfo editorInfo, boolean clobberSettingsKey, boolean shortcutKeyEnabled,
             boolean hasShortcutKey, boolean languageSwitchKeyEnabled) {
-        mLocale = locale;
+        mSubtype = subtype;
+        mLocale = SubtypeLocale.getSubtypeLocale(subtype);
         mOrientation = orientation;
         mWidth = width;
         mMode = mode;
@@ -102,7 +107,7 @@
                 id.mCustomActionLabel,
                 id.navigateNext(),
                 id.navigatePrevious(),
-                id.mLocale
+                id.mSubtype
         });
     }
 
@@ -123,7 +128,7 @@
                 && TextUtils.equals(other.mCustomActionLabel, mCustomActionLabel)
                 && other.navigateNext() == navigateNext()
                 && other.navigatePrevious() == navigatePrevious()
-                && other.mLocale.equals(mLocale);
+                && other.mSubtype.equals(mSubtype);
     }
 
     public boolean isAlphabetKeyboard() {
@@ -176,9 +181,10 @@
 
     @Override
     public String toString() {
-        return String.format("[%s %s %s%d %s %s %s%s%s%s%s%s%s%s]",
+        return String.format("[%s %s:%s %s%d %s %s %s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
                 mLocale,
+                mSubtype.getExtraValueOf(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET),
                 (mOrientation == 1 ? "port" : "land"), mWidth,
                 modeName(mMode),
                 imeAction(),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 6d56c5c..35209e0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -44,7 +44,6 @@
 import java.io.IOException;
 import java.lang.ref.SoftReference;
 import java.util.HashMap;
-import java.util.Locale;
 
 /**
  * This class represents a set of keyboard layouts. Each of them represents a different keyboard
@@ -109,7 +108,7 @@
         boolean mVoiceKeyOnMain;
         boolean mNoSettingsKey;
         boolean mLanguageSwitchKeyEnabled;
-        Locale mLocale;
+        InputMethodSubtype mSubtype;
         int mOrientation;
         int mWidth;
         // KeyboardLayoutSet element id to element's parameters map.
@@ -203,10 +202,10 @@
         final Params params = mParams;
         final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS
                 || keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED);
-        final boolean noLanguage = params.mLocale.getLanguage().equals(SubtypeLocale.NO_LANGUAGE);
+        final boolean noLanguage = SubtypeLocale.isNoLanguage(params.mSubtype);
         final boolean voiceKeyEnabled = params.mVoiceKeyEnabled && !noLanguage;
         final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != params.mVoiceKeyOnMain);
-        return new KeyboardId(keyboardLayoutSetElementId, params.mLocale, params.mOrientation,
+        return new KeyboardId(keyboardLayoutSetElementId, params.mSubtype, params.mOrientation,
                 params.mWidth, params.mMode, params.mEditorInfo, params.mNoSettingsKey,
                 voiceKeyEnabled, hasShortcutKey, params.mLanguageSwitchKeyEnabled);
     }
@@ -251,7 +250,7 @@
             final InputMethodSubtype keyboardSubtype = (forceAscii && !asciiCapable)
                     ? SubtypeSwitcher.getInstance().getNoLanguageSubtype()
                     : subtype;
-            mParams.mLocale = SubtypeLocale.getKeyboardLayoutSetLocale(keyboardSubtype);
+            mParams.mSubtype = keyboardSubtype;
             mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
                     + SubtypeLocale.getKeyboardLayoutSetName(keyboardSubtype);
             return this;
@@ -278,7 +277,7 @@
         public KeyboardLayoutSet build() {
             if (mParams.mOrientation == Configuration.ORIENTATION_UNDEFINED)
                 throw new RuntimeException("Screen geometry is not specified");
-            if (mParams.mLocale == null)
+            if (mParams.mSubtype == null)
                 throw new RuntimeException("KeyboardLayoutSet subtype is not specified");
             final String packageName = mResources.getResourcePackageName(
                     R.xml.keyboard_layout_set_qwerty);
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 7fadb3b..341d9bc 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -35,6 +35,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodSubtype;
 import android.widget.PopupWindow;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
@@ -52,7 +53,6 @@
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
-import java.util.Locale;
 import java.util.WeakHashMap;
 
 /**
@@ -80,7 +80,6 @@
     private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
     private static final int ALPHA_OPAQUE = 255;
     private boolean mNeedsToDisplayLanguage;
-    private Locale mSpacebarLocale;
     private int mLanguageOnSpacebarAnimAlpha = ALPHA_OPAQUE;
     private final float mSpacebarTextRatio;
     private float mSpacebarTextSize;
@@ -468,7 +467,6 @@
         mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon(keyboard.mIconsSet) : null;
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
-        mSpacebarLocale = keyboard.mId.mLocale;
     }
 
     /**
@@ -904,12 +902,12 @@
     }
 
     // Layout locale language name on spacebar.
-    private String layoutLanguageOnSpacebar(Paint paint, Locale locale, int width,
+    private String layoutLanguageOnSpacebar(Paint paint, InputMethodSubtype subtype, int width,
             float origTextSize) {
         paint.setTextAlign(Align.CENTER);
         paint.setTypeface(Typeface.DEFAULT);
         // Estimate appropriate language name text size to fit in maxTextWidth.
-        String language = SubtypeLocale.getFullDisplayName(locale);
+        String language = SubtypeLocale.getFullDisplayName(subtype);
         int textWidth = getTextWidth(paint, language, origTextSize);
         // Assuming text width and text size are proportional to each other.
         float textSize = origTextSize * Math.min(width / textWidth, 1.0f);
@@ -921,7 +919,7 @@
 
         final boolean useShortName;
         if (useMiddleName) {
-            language = SubtypeLocale.getMiddleDisplayName(locale);
+            language = SubtypeLocale.getMiddleDisplayName(subtype);
             textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
             useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
@@ -931,7 +929,7 @@
         }
 
         if (useShortName) {
-            language = SubtypeLocale.getShortDisplayName(locale);
+            language = SubtypeLocale.getShortDisplayName(subtype);
             textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
         }
@@ -944,10 +942,10 @@
         final int width = key.mWidth;
         final int height = key.mHeight;
 
-        // If input subtypes are explicitly selected.
+        // If input language are explicitly selected.
         if (mNeedsToDisplayLanguage) {
-            final String language = layoutLanguageOnSpacebar(paint, mSpacebarLocale, width,
-                    mSpacebarTextSize);
+            final String language = layoutLanguageOnSpacebar(
+                    paint, getKeyboard().mId.mSubtype, width, mSpacebarTextSize);
             // Draw language text with shadow
             // In case there is no space icon, we will place the language text at the center of
             // spacebar.
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
new file mode 100644
index 0000000..deb2478
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+public class AdditionalSubtype {
+    public static final String QWERTY = "qwerty";
+    public static final String QWERTZ = "qwertz";
+    public static final String AZERTY = "azerty";
+
+    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+    private static final String SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
+
+    // Keyboard layout to subtype name resource id map.
+    private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
+            new HashMap<String, Integer>();
+
+    static {
+        sKeyboardLayoutToNameIdsMap.put(QWERTY, R.string.subtype_generic_qwerty);
+        sKeyboardLayoutToNameIdsMap.put(QWERTZ, R.string.subtype_generic_qwertz);
+        sKeyboardLayoutToNameIdsMap.put(AZERTY, R.string.subtype_generic_azerty);
+    }
+
+    public static boolean isAdditionalSubtype(InputMethodSubtype subtype) {
+        return subtype.containsExtraValueKey(SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE);
+    }
+
+    public static InputMethodSubtype createAddtionalSubtype(
+            Locale locale, String keyboardLayoutSet) {
+        final String extraValue = String.format(
+                "%s=%s,%s", LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET, keyboardLayoutSet,
+                SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE);
+        Integer nameId = sKeyboardLayoutToNameIdsMap.get(keyboardLayoutSet);
+        if (nameId == null) nameId = R.string.subtype_generic;
+        return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard,
+                locale.toString(), SUBTYPE_MODE_KEYBOARD, extraValue, false, false);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 187252a..1ab461e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -439,6 +439,10 @@
 
         loadSettings();
 
+        mImm.setAdditionalInputMethodSubtypes(
+                SubtypeUtils.getInputMethodId(getPackageName()),
+                mSettingsValues.getPrefefinedAdditionalSubtypes());
+
         // TODO: remove the following when it's not needed by updateCorrectionMode() any more
         mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
         updateCorrectionMode();
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 526acf1..8ff644f 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -21,12 +21,14 @@
 import android.content.res.Resources;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Locale;
 
 /**
  * When you call the constructor of this class, you may want to change the current system locale by
@@ -69,6 +71,7 @@
     private final int mVibrationDurationSettingsRawValue;
     @SuppressWarnings("unused") // TODO: Use this
     private final float mKeypressSoundVolumeRawValue;
+    private final InputMethodSubtype[] mPredefinedAdditionalSubtypes;
 
     // Deduced settings
     public final int mKeypressVibrationDuration;
@@ -145,6 +148,16 @@
                 mAutoCorrectionThresholdRawValue);
         mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
         mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
+
+        // Predefined additional subtypes
+        final InputMethodSubtype DE_QWERTY = AdditionalSubtype.createAddtionalSubtype(
+                Locale.GERMAN, AdditionalSubtype.QWERTY);
+        final InputMethodSubtype FR_QWERTZ = AdditionalSubtype.createAddtionalSubtype(
+                Locale.FRENCH, AdditionalSubtype.QWERTZ);
+        mPredefinedAdditionalSubtypes = new InputMethodSubtype[] {
+                DE_QWERTY,
+                FR_QWERTZ,
+        };
     }
 
     // Helper functions to create member values.
@@ -304,6 +317,11 @@
         return res.getBoolean(R.bool.config_use_fullscreen_mode);
     }
 
+    // TODO: Should be able to add/remove/edit.
+    public InputMethodSubtype[] getPrefefinedAdditionalSubtypes() {
+        return mPredefinedAdditionalSubtypes;
+    }
+
     // Accessed from the settings interface, hence public
     public static float getCurrentKeypressSoundVolume(final SharedPreferences sp,
                 final Resources res) {
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 05f9168..37da5e8 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -20,23 +20,17 @@
 import android.content.res.Resources;
 import android.view.inputmethod.InputMethodSubtype;
 
-
-
+import java.util.HashMap;
 import java.util.Locale;
 
 public class SubtypeLocale {
     // Special language code to represent "no language".
-    public static final String NO_LANGUAGE = "zz";
-    // Special country code to represent "QWERTY".
-    /* package for test */ static final String QWERTY = "QY";
+    private static final String NO_LANGUAGE = "zz";
+    public static final Locale LOCALE_NO_LANGUAGE = new Locale(NO_LANGUAGE);
 
-    public static final Locale LOCALE_NO_LANGUAGE_QWERTY = new Locale(NO_LANGUAGE, QWERTY);
-
-    private static String[] sExceptionKeys;
-    private static String[] sExceptionValues;
-
-    private static final String DEFAULT_KEYBOARD_LAYOUT_SET = "qwerty";
-    private static final char KEYBOARD_LAYOUT_SET_LOCALE_DELIMITER = ':';
+    // Exceptional locales to display name map.
+    private static final HashMap<String, String> sExceptionalDisplayNamesMap =
+            new HashMap<String, String>();
 
     private SubtypeLocale() {
         // Intentional empty constructor for utility class.
@@ -44,72 +38,74 @@
 
     public static void init(Context context) {
         final Resources res = context.getResources();
-        sExceptionKeys = res.getStringArray(R.array.subtype_locale_exception_keys);
-        sExceptionValues = res.getStringArray(R.array.subtype_locale_exception_values);
-    }
-
-    private static String lookupExceptionalLocale(String key) {
-        for (int index = 0; index < sExceptionKeys.length; index++) {
-            if (sExceptionKeys[index].equals(key)) {
-                return sExceptionValues[index];
-            }
-        }
-        return null;
-    }
-
-    // Get Locale's full display name in its locale.
-    // For example:
-    // "fr_CH" is converted to "Français (Suisse)".
-    // "de_QY" is converted to "Deutsche (QWERTY)". (Any locale that has country code "QY")
-    // "zz_QY" is converted to "QWERTY". (The language code "zz" means "No language", thus just
-    // ends up with the keyboard layout name.)
-    public static String getFullDisplayName(Locale locale) {
-        final String key;
-        if (locale.getLanguage().equals(NO_LANGUAGE)) {
-            key = locale.getCountry();
-        } else if (locale.getCountry().equals(QWERTY)) {
-            key = "*_" + QWERTY;
-        } else {
-            key = locale.toString();
-        }
-        final String value = lookupExceptionalLocale(key);
-        if (value == null) {
-            return StringUtils.toTitleCase(locale.getDisplayName(locale), locale);
-        }
-        if (value.indexOf("%s") >= 0) {
-            final String languageName = StringUtils.toTitleCase(
-                    locale.getDisplayLanguage(locale), locale);
-            return String.format(value, languageName);
-        }
-        return value;
-    }
-
-    // Get Locale's middle display name in its locale.
-    // For example:
-    // "fr_CH" is converted to "Français".
-    // "de_QY" is converted to "Deutsche". (Any locale that has country code "QY")
-    // "zz_QY" is converted to "QWERTY". (The language code "zz" means "No language", thus just
-    // ends up with the keyboard layout name.)
-    public static String getMiddleDisplayName(Locale locale) {
-        if (NO_LANGUAGE.equals(locale.getLanguage())) {
-            return lookupExceptionalLocale(locale.getCountry());
-        } else {
-            return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale);
+        final String[] locales = res.getStringArray(R.array.subtype_locale_exception_keys);
+        final String[] displayNames = res.getStringArray(R.array.subtype_locale_exception_values);
+        for (int i = 0; i < locales.length; i++) {
+            sExceptionalDisplayNamesMap.put(locales[i], displayNames[i]);
         }
     }
 
-    // Get Locale's short display name in its locale.
-    // For example:
-    // "fr_CH" is converted to "Fr".
-    // "de_QY" is converted to "De". (Any locale that has country code "QY")
-    // "zz_QY" is converter to "QY". (The language code "zz" means "No language", thus just ends
-    // up with the keyboard layout name.)
-    public static String getShortDisplayName(Locale locale) {
-        if (NO_LANGUAGE.equals(locale.getLanguage())) {
-            return locale.getCountry();
-        } else {
-            return StringUtils.toTitleCase(locale.getLanguage(), locale);
+    // Get InputMethodSubtype's display name in its locale.
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout | Short  Middle      Full
+    // ------ ------ - ---- --------- -----------------
+    //  en_US qwerty F  En  English   English (US)      exception
+    //  en_GB qwerty F  En  English   English (UK)      exception
+    //  fr    azerty F  Fr  Français  Français
+    //  fr_CA qwerty F  Fr  Français  Français (Canada)
+    //  de    qwertz F  De  Deutsch   Deutsch
+    //  zz    qwerty F      QWERTY    QWERTY
+    //  fr    qwertz T  Fr  Français  Français (QWERTZ)
+    //  de    qwerty T  De  Deutsch   Deutsch (QWERTY)
+    //  en    azerty T  En  English   English (AZERTY)
+    //  zz    azerty T      AZERTY    AZERTY
+
+    // Get InputMethodSubtype's full display name in its locale.
+    public static String getFullDisplayName(InputMethodSubtype subtype) {
+        final String value = sExceptionalDisplayNamesMap.get(subtype.getLocale());
+        if (value != null) {
+            return value;
         }
+
+        if (isNoLanguage(subtype)) {
+            return getKeyboardLayoutSetName(subtype).toUpperCase();
+        }
+
+        final Locale locale = getSubtypeLocale(subtype);
+        final String language = StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale);
+        if (AdditionalSubtype.isAdditionalSubtype(subtype)) {
+            return String.format("%s (%s)",
+                    language, getKeyboardLayoutSetName(subtype).toUpperCase());
+        }
+        return StringUtils.toTitleCase(locale.getDisplayName(locale), locale);
+    }
+
+    // Get InputMethodSubtype's middle display name in its locale.
+    public static String getMiddleDisplayName(InputMethodSubtype subtype) {
+        if (isNoLanguage(subtype)) {
+            return getKeyboardLayoutSetName(subtype).toUpperCase();
+        }
+        final Locale locale = getSubtypeLocale(subtype);
+        return StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale);
+    }
+
+    // Get InputMethodSubtype's short display name in its locale.
+    public static String getShortDisplayName(InputMethodSubtype subtype) {
+        if (isNoLanguage(subtype)) {
+            return "";
+        }
+        final Locale locale = getSubtypeLocale(subtype);
+        return StringUtils.toTitleCase(locale.getLanguage(), locale);
+    }
+
+    public static boolean isNoLanguage(InputMethodSubtype subtype) {
+        final String localeString = subtype.getLocale();
+        return localeString.equals(NO_LANGUAGE);
+    }
+
+    public static Locale getSubtypeLocale(InputMethodSubtype subtype) {
+        final String localeString = subtype.getLocale();
+        return LocaleUtils.constructLocaleFromString(localeString);
     }
 
     public static String getKeyboardLayoutSetName(InputMethodSubtype subtype) {
@@ -117,22 +113,7 @@
                 LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET);
         // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is
         // fixed.
-        if (keyboardLayoutSet == null) return DEFAULT_KEYBOARD_LAYOUT_SET;
-        final int pos = keyboardLayoutSet.indexOf(KEYBOARD_LAYOUT_SET_LOCALE_DELIMITER);
-        return (pos > 0) ? keyboardLayoutSet.substring(0, pos) : keyboardLayoutSet;
-    }
-
-    public static String getKeyboardLayoutSetLocaleString(InputMethodSubtype subtype) {
-        final String keyboardLayoutSet = subtype.getExtraValueOf(
-                LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET);
-        // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is
-        // fixed.
-        if (keyboardLayoutSet == null) return subtype.getLocale();
-        final int pos = keyboardLayoutSet.indexOf(KEYBOARD_LAYOUT_SET_LOCALE_DELIMITER);
-        return (pos > 0) ? keyboardLayoutSet.substring(pos + 1) : subtype.getLocale();
-    }
-
-    public static Locale getKeyboardLayoutSetLocale(InputMethodSubtype subtype) {
-        return LocaleUtils.constructLocaleFromString(getKeyboardLayoutSetLocaleString(subtype));
+        if (keyboardLayoutSet == null) return AdditionalSubtype.QWERTY;
+        return keyboardLayoutSet;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 2ba7423..3bb3ab4 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -105,7 +105,7 @@
         mCurrentSubtype = mImm.getCurrentInputMethodSubtype();
         mAllEnabledSubtypesOfCurrentInputMethod = null;
         mNoLanguageSubtype = SubtypeUtils.findSubtypeByLocaleAndKeyboardLayoutSet(
-                service, SubtypeLocale.LOCALE_NO_LANGUAGE_QWERTY, "qwerty");
+                service, SubtypeLocale.LOCALE_NO_LANGUAGE, AdditionalSubtype.QWERTY);
 
         final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
         mIsNetworkConnected = (info != null && info.isConnected());
@@ -135,7 +135,7 @@
         mEnabledLanguagesOfCurrentInputMethod.clear();
         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
         for (InputMethodSubtype ims : mAllEnabledSubtypesOfCurrentInputMethod) {
-            final String locale = SubtypeLocale.getKeyboardLayoutSetLocaleString(ims);
+            final String locale = ims.getLocale();
             final String mode = ims.getMode();
             mLocaleSplitter.setString(locale);
             if (mLocaleSplitter.hasNext()) {
@@ -165,8 +165,7 @@
                     + (mShortcutInputMethodInfo == null
                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
                     + (mShortcutSubtype == null ? "<null>" : (
-                            SubtypeLocale.getKeyboardLayoutSetLocaleString(mShortcutSubtype)
-                            + ", " + mShortcutSubtype.getMode())));
+                            mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
         }
         // TODO: Update an icon for shortcut IME
         final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
@@ -188,14 +187,13 @@
                     + (mShortcutInputMethodInfo == null
                             ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
                     + (mShortcutSubtype == null ? "<null>" : (
-                            SubtypeLocale.getKeyboardLayoutSetLocaleString(mShortcutSubtype)
-                            + ", " + mShortcutSubtype.getMode())));
+                            mShortcutSubtype.getLocale() + ", " + mShortcutSubtype.getMode())));
         }
     }
 
     // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
     public void updateSubtype(InputMethodSubtype newSubtype) {
-        final String newLocale = SubtypeLocale.getKeyboardLayoutSetLocaleString(newSubtype);
+        final String newLocale = newSubtype.getLocale();
         final String newMode = newSubtype.getMode();
         final String oldMode = mCurrentSubtype.getMode();
         if (DBG) {
@@ -335,7 +333,7 @@
     }
 
     public boolean needsToDisplayLanguage(Locale keyboardLocale) {
-        if (keyboardLocale.equals(SubtypeLocale.LOCALE_NO_LANGUAGE_QWERTY)) {
+        if (keyboardLocale.equals(SubtypeLocale.LOCALE_NO_LANGUAGE)) {
             return true;
         }
         if (!keyboardLocale.equals(mInputLocale)) {
diff --git a/java/src/com/android/inputmethod/latin/SubtypeUtils.java b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
index 4ad72fb..4d0f1c2 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
@@ -143,6 +143,7 @@
                 return subtype;
             }
         }
-        throw new RuntimeException("Can not find subtype of locale " + localeString);
+        throw new RuntimeException("Can't find subtype for locale " + localeString
+                + " and keyboard layout " + keyoardLayoutSet);
     }
 }