diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 68a8cabce4f781a1c041eb165433370dd8502db8..9c8bd3cd4e8374f1f50131b5fa55a7b849bfe207 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -156,22 +156,20 @@
     <string-array name="subtype_locale_exception_keys">
         <item>en_US</item>
         <item>en_GB</item>
-        <item>de_QY</item>
-        <item>zz_QY</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>@string/subtype_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 language agnostic QWERTY keyboard subtype -->
-    <string name="subtype_qwerty">QWERTY</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/values/strings.xml b/java/res/values/strings.xml
index 4f038e1a5122038ea76e9ba5728f0d6ca3c5c7cf..a22c68cb86e132f4d73824f2bae4d71fddeef2e7 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -252,6 +252,8 @@
     <string name="subtype_en_GB">English (UK)</string>
     <!-- Description for English (United States) keyboard subtype [CHAR LIMIT=22] -->
     <string name="subtype_en_US">English (US)</string>
+    <!-- Description for language agnostic QWERTY keyboard subtype [CHAR LIMIT=22] -->
+    <string name="subtype_no_language_qwerty">No language (QWERTY)</string>
 
     <!-- Title of an option for usability study mode -->
     <string name="prefs_usability_study_mode">Usability study mode</string>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 7a21a856b344f6bf6a3dcc0986c69c5e310765df..e43fb32a4dc03ba553c7dc858218a62dd06ce748 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -312,7 +312,7 @@
             android:imeSubtypeExtraValue="AsciiCapable"
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
-            android:label="@string/subtype_qwerty"
+            android:label="@string/subtype_no_language_qwerty"
             android:imeSubtypeLocale="zz_QY"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable"
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index e2af971850edf27a424d826f1dde0753e1165e79..2689e6e138f29f72fa1c40b9b1e0a1d6025898e7 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -47,7 +47,7 @@ import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResearchLogger;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeLocale;
 import com.android.inputmethod.latin.SubtypeUtils;
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
@@ -926,7 +926,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
         paint.setTextAlign(Align.CENTER);
         paint.setTypeface(Typeface.DEFAULT);
         // Estimate appropriate language name text size to fit in maxTextWidth.
-        String language = StringUtils.getFullDisplayName(locale, true);
+        String language = SubtypeLocale.getFullDisplayName(locale);
         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);
@@ -938,7 +938,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
 
         final boolean useShortName;
         if (useMiddleName) {
-            language = StringUtils.getMiddleDisplayLanguage(locale);
+            language = SubtypeLocale.getMiddleDisplayName(locale);
             textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
             useShortName = (textSize / origTextSize < MINIMUM_SCALE_OF_LANGUAGE_NAME)
@@ -948,7 +948,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
         }
 
         if (useShortName) {
-            language = StringUtils.getShortDisplayLanguage(locale);
+            language = SubtypeLocale.getShortDisplayName(locale);
             textWidth = getTextWidth(paint, language, origTextSize);
             textSize = origTextSize * Math.min(width / textWidth, 1.0f);
         }
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 7b34cae635dff1b96ef53b2b2f07ed861c8f1c16..7000e46337782f14833d1ce999be2e052946203b 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -22,7 +22,6 @@ import android.view.inputmethod.EditorInfo;
 import com.android.inputmethod.keyboard.Keyboard;
 
 import java.util.ArrayList;
-import java.util.Locale;
 
 public class StringUtils {
     private StringUtils() {
@@ -150,41 +149,4 @@ public class StringUtils {
             i++;
         }
     }
-
-    public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
-        if (returnsNameInThisLocale) {
-            return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
-        } else {
-            return toTitleCase(locale.getDisplayName(), locale);
-        }
-    }
-
-    public static String getDisplayLanguage(Locale locale) {
-        return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
-    }
-
-    public static String getMiddleDisplayLanguage(Locale locale) {
-        return toTitleCase((LocaleUtils.constructLocaleFromString(
-                locale.getLanguage()).getDisplayLanguage(locale)), locale);
-    }
-
-    public static String getShortDisplayLanguage(Locale locale) {
-        return toTitleCase(locale.getLanguage(), locale);
-    }
-
-    public static String toTitleCase(String s, Locale locale) {
-        if (s.length() <= 1) {
-            // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
-            return s;
-        }
-        // TODO: fix the bugs below
-        // - This does not work for Greek, because it returns upper case instead of title case.
-        // - It does not work for Serbian, because it fails to account for the "lj" character,
-        // which should be "Lj" in title case and "LJ" in upper case.
-        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
-        // are two different characters but both should be capitalized as "IJ" as if they were
-        // a single letter.
-        // - It also does not work with unicode surrogate code points.
-        return s.toUpperCase(locale).charAt(0) + s.substring(1);
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 66c13bd2e788b592432763ea4f1b22ee2b8e393f..40051a7ee3f08823fb955b0f0518e11991bedc16 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -22,6 +22,13 @@ import android.content.res.Resources;
 import java.util.Locale;
 
 public class SubtypeLocale {
+    // Special language code to represent "no language".
+    /* package for test */ static final String NO_LANGUAGE = "zz";
+    // Special country code to represent "QWERTY".
+    /* package for test */ static final String QWERTY = "QY";
+
+    public static final Locale LOCALE_NO_LANGUAGE_QWERTY = new Locale(NO_LANGUAGE, QWERTY);
+
     private static String[] sExceptionKeys;
     private static String[] sExceptionValues;
 
@@ -35,18 +42,82 @@ public class SubtypeLocale {
         sExceptionValues = res.getStringArray(R.array.subtype_locale_exception_values);
     }
 
-    public static String getFullDisplayName(Locale locale) {
-        final String localeCode = locale.toString();
+    private static String lookupExceptionalLocale(String key) {
         for (int index = 0; index < sExceptionKeys.length; index++) {
-            if (sExceptionKeys[index].equals(localeCode)) {
-                final String value = sExceptionValues[index];
-                if (value.indexOf("%s") >= 0) {
-                    final String languageName = locale.getDisplayLanguage(locale);
-                    return String.format(value, languageName);
-                }
-                return value;
+            if (sExceptionKeys[index].equals(key)) {
+                return sExceptionValues[index];
             }
         }
-        return locale.getDisplayName(locale);
+        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 toTitleCase(locale.getDisplayName(locale), locale);
+        }
+        if (value.indexOf("%s") >= 0) {
+            final String languageName = 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 toTitleCase(locale.getDisplayLanguage(locale), locale);
+        }
+    }
+
+    // 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 toTitleCase(locale.getLanguage(), locale);
+        }
+    }
+
+    public static String toTitleCase(String s, Locale locale) {
+        if (s.length() <= 1) {
+            // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
+            return s;
+        }
+        // TODO: fix the bugs below
+        // - This does not work for Greek, because it returns upper case instead of title case.
+        // - It does not work for Serbian, because it fails to account for the "lj" character,
+        // which should be "Lj" in title case and "LJ" in upper case.
+        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
+        // are two different characters but both should be capitalized as "IJ" as if they were
+        // a single letter.
+        // - It also does not work with unicode surrogate code points.
+        return s.toUpperCase(locale).charAt(0) + s.substring(1);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index e35364420b49dc39f55d95e286eb8c2682150be4..c2dafcf73dbacae02ae1e9b45911bdb18351d6ae 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -420,10 +420,6 @@ public class SubtypeSwitcher {
         return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
     }
 
-    public String getInputLanguageName() {
-        return StringUtils.getDisplayLanguage(getInputLocale());
-    }
-
     /////////////////////////////
     // Other utility functions //
     /////////////////////////////
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 7b13e40c2ef0683d8bbb2c5af80322d6fcccc18a..cd01bb1464fdcb28b72f06554b54291834c53a4a 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -37,6 +37,7 @@ import com.android.inputmethod.latin.Flag;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SubtypeLocale;
 import com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
 import com.android.inputmethod.latin.WhitelistDictionary;
@@ -325,8 +326,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService
                 } else if (CAPITALIZE_FIRST == capitalizeType) {
                     for (int i = 0; i < mSuggestions.size(); ++i) {
                         // Likewise
-                        mSuggestions.set(i, StringUtils.toTitleCase(mSuggestions.get(i).toString(),
-                                locale));
+                        mSuggestions.set(i, SubtypeLocale.toTitleCase(
+                                mSuggestions.get(i).toString(), locale));
                     }
                 }
                 // This returns a String[], while toArray() returns an Object[] which cannot be cast
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index 089fdc52c25547478a4cc920ffb4b2598b093fb7..6180ff5f94caf4398039868208d5c76385dd917c 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -23,11 +23,14 @@ import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Locale;
 
 public class SubtypeLocaleTests extends AndroidTestCase {
-    private List<InputMethodSubtype> mKeyboardSubtypes;
+    private static final Locale LOCALE_zz_QY = SubtypeLocale.LOCALE_NO_LANGUAGE_QWERTY;
+    private static final Locale LOCALE_de_QY =
+            new Locale(Locale.GERMAN.getLanguage(), SubtypeLocale.QWERTY);
+
+    private ArrayList<InputMethodSubtype> mSubtypesList;
 
     @Override
     protected void setUp() throws Exception {
@@ -42,46 +45,111 @@ public class SubtypeLocaleTests extends AndroidTestCase {
                 Context.INPUT_METHOD_SERVICE);
         for (final InputMethodInfo imi : imm.getInputMethodList()) {
             if (imi.getPackageName().equals(packageName)) {
-                mKeyboardSubtypes = new ArrayList<InputMethodSubtype>();
+                mSubtypesList = new ArrayList<InputMethodSubtype>();
                 final int subtypeCount = imi.getSubtypeCount();
-                for (int i = 0; i < subtypeCount; ++i) {
-                    InputMethodSubtype subtype = imi.getSubtypeAt(i);
-                    if (subtype.getMode().equals("keyboard")) {
-                        mKeyboardSubtypes.add(subtype);
-                    }
+                for (int i = 0; i < subtypeCount; i++) {
+                    final InputMethodSubtype ims = imi.getSubtypeAt(i);
+                    mSubtypesList.add(ims);
                 }
                 break;
             }
         }
-        assertNotNull("Can not find input method " + packageName, mKeyboardSubtypes);
-        assertTrue("Can not find keyboard subtype", mKeyboardSubtypes.size() > 0);
+        assertNotNull("Can not find input method " + packageName, mSubtypesList);
+        assertTrue("Can not find keyboard subtype", mSubtypesList.size() > 0);
+    }
+
+    private static Locale getSubtypeLocale(InputMethodSubtype subtype) {
+        return LocaleUtils.constructLocaleFromString(subtype.getLocale());
+    }
+
+    private static Locale getKeyboardLocale(InputMethodSubtype subtype) {
+        final String subtypeLocaleString = subtype.containsExtraValueKey(
+                LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
+                ? subtype.getExtraValueOf(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
+                : subtype.getLocale();
+        return LocaleUtils.constructLocaleFromString(subtypeLocaleString);
     }
 
-    public void testSubtypeLocale() {
+    public void testFullDisplayName() {
         final StringBuilder messages = new StringBuilder();
         int failedCount = 0;
-        for (final InputMethodSubtype subtype : mKeyboardSubtypes) {
-            final Locale locale = LocaleUtils.constructLocaleFromString(subtype.getLocale());
-            if (locale.getLanguage().equals("zz")) {
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final Locale locale = getKeyboardLocale(subtype);
+            if (locale.getLanguage().equals(SubtypeLocale.NO_LANGUAGE)) {
                 // This is special language name for language agnostic usage.
                 continue;
             }
-            final String subtypeLocaleString =
-                    subtype.containsExtraValueKey(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
-                    ? subtype.getExtraValueOf(LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LOCALE)
-                    : subtype.getLocale();
-            final Locale subtypeLocale = LocaleUtils.constructLocaleFromString(subtypeLocaleString);
-            // The subtype name in its locale.  For example 'English (US)' or 'Deutsch (QWERTY)'.
-            final String subtypeName = SubtypeLocale.getFullDisplayName(subtypeLocale);
-            // The locale language name in its locale.
-            final String languageName = locale.getDisplayLanguage(locale);
-            if (!subtypeName.contains(languageName)) {
+            final String keyboardName = SubtypeLocale.getFullDisplayName(locale);
+            final String languageName = SubtypeLocale.toTitleCase(
+                    locale.getDisplayLanguage(locale), locale);
+            if (!keyboardName.contains(languageName)) {
                 failedCount++;
                 messages.append(String.format(
-                        "subtype name is '%s' and should contain locale '%s' language name '%s'\n",
-                        subtypeName, subtypeLocale, languageName));
+                        "locale %s: keyboard name '%s' should contain language name '%s'\n",
+                        locale, keyboardName, languageName));
             }
         }
         assertEquals(messages.toString(), 0, failedCount);
     }
+
+    public void testFullDisplayNameNoLanguage() {
+        assertEquals("zz_QY", "QWERTY", SubtypeLocale.getFullDisplayName(LOCALE_zz_QY));
+
+        final String de_QY = SubtypeLocale.getFullDisplayName(LOCALE_de_QY);
+        assertTrue("de_QY", de_QY.contains("(QWERTY"));
+        assertTrue("de_QY", de_QY.contains(Locale.GERMAN.getDisplayLanguage(Locale.GERMAN)));
+    }
+
+    public void testMiddleDisplayName() {
+        final StringBuilder messages = new StringBuilder();
+        int failedCount = 0;
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final Locale locale = getKeyboardLocale(subtype);
+            if (locale.getLanguage().equals(SubtypeLocale.NO_LANGUAGE)) {
+                // This is special language name for language agnostic usage.
+                continue;
+            }
+            final String keyboardName = SubtypeLocale.getMiddleDisplayName(locale);
+            final String languageName = SubtypeLocale.toTitleCase(
+                    locale.getDisplayLanguage(locale), locale);
+            if (!keyboardName.equals(languageName)) {
+                failedCount++;
+                messages.append(String.format(
+                        "locale %s: keyboard name '%s' should be equals to language name '%s'\n",
+                        locale, keyboardName, languageName));
+            }
+        }
+        assertEquals(messages.toString(), 0, failedCount);
+    }
+
+    public void testMiddleDisplayNameNoLanguage() {
+        assertEquals("zz_QY", "QWERTY", SubtypeLocale.getMiddleDisplayName(LOCALE_zz_QY));
+        assertEquals("de_QY", "Deutsch", SubtypeLocale.getMiddleDisplayName(LOCALE_de_QY));
+    }
+
+    public void testShortDisplayName() {
+        final StringBuilder messages = new StringBuilder();
+        int failedCount = 0;
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final Locale locale = getKeyboardLocale(subtype);
+            if (locale.getCountry().equals(SubtypeLocale.QWERTY)) {
+                // This is special country code for QWERTY keyboard.
+                continue;
+            }
+            final String keyboardName = SubtypeLocale.getShortDisplayName(locale);
+            final String languageCode = SubtypeLocale.toTitleCase(locale.getLanguage(), locale);
+            if (!keyboardName.equals(languageCode)) {
+                failedCount++;
+                messages.append(String.format(
+                        "locale %s: keyboard name '%s' should be equals to language code '%s'\n",
+                        locale, keyboardName, languageCode));
+            }
+        }
+        assertEquals(messages.toString(), 0, failedCount);
+    }
+
+    public void testShortDisplayNameNoLanguage() {
+        assertEquals("zz_QY", "QY", SubtypeLocale.getShortDisplayName(LOCALE_zz_QY));
+        assertEquals("de_QY", "De", SubtypeLocale.getShortDisplayName(LOCALE_de_QY));
+    }
 }