diff --git a/java/res/values/predefined-subtypes.xml b/java/res/values/predefined-subtypes.xml
index e67fee53f2661208f4a018f4587d62973171d0ef..602f53eac1388d8af9db4ce62b5f4342ef5fc61d 100644
--- a/java/res/values/predefined-subtypes.xml
+++ b/java/res/values/predefined-subtypes.xml
@@ -18,6 +18,6 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Predefined subtypes (language:layout) in CSV format -->
-    <string name="predefined_subtypes" translatable="false">de:qwerty,fr:qwertz</string>
+    <!-- Predefined subtypes (language:layout[:extraValue]) in semicolon separated format -->
+    <string name="predefined_subtypes" translatable="false">de:qwerty:AsciiCapable;fr:qwertz:AsciiCapable</string>
 </resources>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 35209e0ad35040881a66e25362a6957084c051cc..07f71ee17bd33c924659b1df38d51e69f1172a5a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -29,11 +29,11 @@ import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet.Params.ElementParams;
+import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SubtypeLocale;
 import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.XmlParseUtils;
@@ -229,7 +229,7 @@ public class KeyboardLayoutSet {
 
             params.mMode = getKeyboardMode(editorInfo);
             params.mEditorInfo = (editorInfo != null) ? editorInfo : EMPTY_EDITOR_INFO;
-            params.mNoSettingsKey = StringUtils.inPrivateImeOptions(
+            params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
                     mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, mEditorInfo);
         }
 
@@ -242,7 +242,7 @@ public class KeyboardLayoutSet {
         public Builder setSubtype(InputMethodSubtype subtype) {
             final boolean asciiCapable = subtype.containsExtraValueKey(
                     LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
-            final boolean deprecatedForceAscii = StringUtils.inPrivateImeOptions(
+            final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions(
                     mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, mEditorInfo);
             final boolean forceAscii = EditorInfoCompatUtils.hasFlagForceAscii(
                     mParams.mEditorInfo.imeOptions)
@@ -259,9 +259,9 @@ public class KeyboardLayoutSet {
         public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain,
                 boolean languageSwitchKeyEnabled) {
             @SuppressWarnings("deprecation")
-            final boolean deprecatedNoMicrophone = StringUtils.inPrivateImeOptions(
+            final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
                     null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo);
-            final boolean noMicrophone = StringUtils.inPrivateImeOptions(
+            final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
                     mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, mEditorInfo)
                     || deprecatedNoMicrophone;
             mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index b24f064720cc950c6064559e8917ea0c2467a626..323f74ab965b28eba966f50679b1cc06ad97000f 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -43,28 +43,35 @@ public class AdditionalSubtype {
     }
 
     public static InputMethodSubtype createAdditionalSubtype(
-            String localeString, 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);
+            String localeString, String keyboardLayoutSetName, String extraValue) {
+        final String layoutExtraValue = LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET + "="
+                + keyboardLayoutSetName;
+        final String filteredExtraValue = StringUtils.appendToCsvIfNotExists(
+                SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE,
+                StringUtils.removeFromCsvIfExists(layoutExtraValue, extraValue));
+        Integer nameId = sKeyboardLayoutToNameIdsMap.get(keyboardLayoutSetName);
         if (nameId == null) nameId = R.string.subtype_generic;
         return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard,
-                localeString, SUBTYPE_MODE_KEYBOARD, extraValue, false, false);
+                localeString, SUBTYPE_MODE_KEYBOARD, filteredExtraValue, false, false);
     }
 
     private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
-    private static final String SUBTYPE_SEPARATOR = ",";
+    private static final String PREF_SUBTYPE_SEPARATOR = ";";
 
-    public static InputMethodSubtype[] createAdditionalSubtypesArray(String csvSubtypes) {
-        final String[] subtypeSpecs = csvSubtypes.split(SUBTYPE_SEPARATOR);
-        final InputMethodSubtype[] subtypesArray = new InputMethodSubtype[subtypeSpecs.length];
-        for (int i = 0; i < subtypeSpecs.length; i++) {
-            final String elems[] = subtypeSpecs[i].split(LOCALE_AND_LAYOUT_SEPARATOR);
+    public static InputMethodSubtype[] createAdditionalSubtypesArray(String prefSubtypes) {
+        final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
+        final InputMethodSubtype[] subtypesArray = new InputMethodSubtype[prefSubtypeArray.length];
+        for (int i = 0; i < prefSubtypeArray.length; i++) {
+            final String prefSubtype = prefSubtypeArray[i];
+            final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
+            if (elems.length < 2 || elems.length > 3) {
+                throw new RuntimeException("Unknown subtype found in preference: " + prefSubtype);
+            }
             final String localeString = elems[0];
             final String keyboardLayoutSetName = elems[1];
+            final String extraValue = (elems.length == 3) ? elems[2] : null;
             subtypesArray[i] = AdditionalSubtype.createAdditionalSubtype(
-                    localeString, keyboardLayoutSetName);
+                    localeString, keyboardLayoutSetName, extraValue);
         }
         return subtypesArray;
     }
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index a6ce040693e3745f5fb88303ed2ee9a3f1109e7c..9c32f947c304e52d17eccf562f3835343d950c0d 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -162,4 +162,12 @@ public class InputAttributes {
                 + "\n mIsSettingsSuggestionStripOn = " + mIsSettingsSuggestionStripOn
                 + "\n mApplicationSpecifiedCompletionOn = " + mApplicationSpecifiedCompletionOn;
     }
+
+    public static boolean inPrivateImeOptions(String packageName, String key,
+            EditorInfo editorInfo) {
+        if (editorInfo == null) return false;
+        final String findingKey = (packageName != null) ? packageName + "." + key
+                : key;
+        return StringUtils.containsInCsv(findingKey, editorInfo.privateImeOptions);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 682901cbc8f65c1f09da918496f058e61a514617..9cfde8b77a8beead6ace63ddf487f042ff74ea49 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -669,12 +669,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         if (ProductionFlag.IS_EXPERIMENTAL) {
             ResearchLogger.latinIME_onStartInputViewInternal(editorInfo);
         }
-        if (StringUtils.inPrivateImeOptions(null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) {
+        if (InputAttributes.inPrivateImeOptions(
+                null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) {
             Log.w(TAG, "Deprecated private IME option specified: "
                     + editorInfo.privateImeOptions);
             Log.w(TAG, "Use " + getPackageName() + "." + IME_OPTION_NO_MICROPHONE + " instead");
         }
-        if (StringUtils.inPrivateImeOptions(getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) {
+        if (InputAttributes.inPrivateImeOptions(
+                getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) {
             Log.w(TAG, "Deprecated private IME option specified: "
                     + editorInfo.privateImeOptions);
             Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
@@ -1077,7 +1079,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         if (ic == null) return false;
         final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
-                && StringUtils.canBeFollowedByPeriod(lastThree.charAt(0))
+                && canBeFollowedByPeriod(lastThree.charAt(0))
                 && lastThree.charAt(1) == Keyboard.CODE_SPACE
                 && lastThree.charAt(2) == Keyboard.CODE_SPACE
                 && mHandler.isAcceptingDoubleSpaces()) {
@@ -1093,6 +1095,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         return false;
     }
 
+    private static boolean canBeFollowedByPeriod(final int codePoint) {
+        // TODO: Check again whether there really ain't a better way to check this.
+        // TODO: This should probably be language-dependant...
+        return Character.isLetterOrDigit(codePoint)
+                || codePoint == Keyboard.CODE_SINGLE_QUOTE
+                || codePoint == Keyboard.CODE_DOUBLE_QUOTE
+                || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
+                || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
+                || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+    }
+
     // "ic" may be null
     private static void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) {
         if (ic == null) return;
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index c160555f08fac0b2a46e87289246f8a78e1b187c..b83cec462cab169e2b40bd79da885ab5d4c1d838 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -150,7 +150,7 @@ public class SettingsValues {
         mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
 
         mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray(
-                getCsvAdditionalSubtypes(prefs, res));
+                getPrefAdditionalSubtypes(prefs, res));
     }
 
     // Helper functions to create member values.
@@ -315,10 +315,10 @@ public class SettingsValues {
         return mAdditionalSubtypes;
     }
 
-    public static String getCsvAdditionalSubtypes(final SharedPreferences prefs,
+    public static String getPrefAdditionalSubtypes(final SharedPreferences prefs,
             final Resources res) {
-        final String csvPredefinedSubtypes = res.getString(R.string.predefined_subtypes, "");
-        return prefs.getString(Settings.PREF_CUSTOM_INPUT_STYLES, csvPredefinedSubtypes);
+        final String prefSubtypes = res.getString(R.string.predefined_subtypes, "");
+        return prefs.getString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes);
     }
 
     // Accessed from the settings interface, hence public
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index a599933d8adf6e7dc6c710ebd0f7fae359fe086c..160581cbe54dc0f8d3f1d8dd5ca91de2e05509d1 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -17,9 +17,6 @@
 package com.android.inputmethod.latin;
 
 import android.text.TextUtils;
-import android.view.inputmethod.EditorInfo;
-
-import com.android.inputmethod.keyboard.Keyboard;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -29,39 +26,38 @@ public class StringUtils {
         // This utility class is not publicly instantiable.
     }
 
-    public static boolean canBeFollowedByPeriod(final int codePoint) {
-        // TODO: Check again whether there really ain't a better way to check this.
-        // TODO: This should probably be language-dependant...
-        return Character.isLetterOrDigit(codePoint)
-                || codePoint == Keyboard.CODE_SINGLE_QUOTE
-                || codePoint == Keyboard.CODE_DOUBLE_QUOTE
-                || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
-                || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
-                || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
-                || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
-    }
-
     public static int codePointCount(String text) {
         if (TextUtils.isEmpty(text)) return 0;
         return text.codePointCount(0, text.length());
     }
 
-    private static boolean containsInCsv(String key, String csv) {
-        if (csv == null)
-            return false;
-        for (String option : csv.split(",")) {
-            if (option.equals(key))
-                return true;
+    public static boolean containsInArray(String key, String[] array) {
+        for (final String element : array) {
+            if (key.equals(element)) return true;
         }
         return false;
     }
 
-    public static boolean inPrivateImeOptions(String packageName, String key,
-            EditorInfo editorInfo) {
-        if (editorInfo == null)
-            return false;
-        return containsInCsv(packageName != null ? packageName + "." + key : key,
-                editorInfo.privateImeOptions);
+    public static boolean containsInCsv(String key, String csv) {
+        if (TextUtils.isEmpty(csv)) return false;
+        return containsInArray(key, csv.split(","));
+    }
+
+    public static String appendToCsvIfNotExists(String key, String csv) {
+        if (TextUtils.isEmpty(csv)) return key;
+        if (containsInCsv(key, csv)) return csv;
+        return csv + "," + key;
+    }
+
+    public static String removeFromCsvIfExists(String key, String csv) {
+        if (TextUtils.isEmpty(csv)) return "";
+        final String[] elements = csv.split(",");
+        if (!containsInArray(key, elements)) return csv;
+        final ArrayList<String> result = new ArrayList<String>(elements.length - 1);
+        for (final String element : elements) {
+            if (!key.equals(element)) result.add(element);
+        }
+        return TextUtils.join(",", result);
     }
 
     /**
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a5a822467e15305fc33ba877d9b0aa159db7cf6
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -0,0 +1,91 @@
+/*
+ * 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.test.AndroidTestCase;
+
+public class StringUtilsTests extends AndroidTestCase {
+    public void testContainsInArray() {
+        assertFalse("empty array", StringUtils.containsInArray("key", new String[0]));
+        assertFalse("not in 1 element", StringUtils.containsInArray("key", new String[] {
+                "key1"
+        }));
+        assertFalse("not in 2 elements", StringUtils.containsInArray("key", new String[] {
+                "key1", "key2"
+        }));
+
+        assertTrue("in 1 element", StringUtils.containsInArray("key", new String[] {
+                "key"
+        }));
+        assertTrue("in 2 elements", StringUtils.containsInArray("key", new String[] {
+                "key1", "key"
+        }));
+    }
+
+    public void testContainsInCsv() {
+        assertFalse("null", StringUtils.containsInCsv("key", null));
+        assertFalse("empty", StringUtils.containsInCsv("key", ""));
+        assertFalse("not in 1 element", StringUtils.containsInCsv("key", "key1"));
+        assertFalse("not in 2 elements", StringUtils.containsInCsv("key", "key1,key2"));
+
+        assertTrue("in 1 element", StringUtils.containsInCsv("key", "key"));
+        assertTrue("in 2 elements", StringUtils.containsInCsv("key", "key1,key"));
+    }
+
+    public void testAppendToCsvIfNotExists() {
+        assertEquals("null", "key", StringUtils.appendToCsvIfNotExists("key", null));
+        assertEquals("empty", "key", StringUtils.appendToCsvIfNotExists("key", ""));
+
+        assertEquals("not in 1 element", "key1,key",
+                StringUtils.appendToCsvIfNotExists("key", "key1"));
+        assertEquals("not in 2 elements", "key1,key2,key",
+                StringUtils.appendToCsvIfNotExists("key", "key1,key2"));
+
+        assertEquals("in 1 element", "key",
+                StringUtils.appendToCsvIfNotExists("key", "key"));
+        assertEquals("in 2 elements at position 1", "key,key2",
+                StringUtils.appendToCsvIfNotExists("key", "key,key2"));
+        assertEquals("in 2 elements at position 2", "key1,key",
+                StringUtils.appendToCsvIfNotExists("key", "key1,key"));
+        assertEquals("in 3 elements at position 2", "key1,key,key3",
+                StringUtils.appendToCsvIfNotExists("key", "key1,key,key3"));
+    }
+
+    public void testRemoveFromCsvIfExists() {
+        assertEquals("null", "", StringUtils.removeFromCsvIfExists("key", null));
+        assertEquals("empty", "", StringUtils.removeFromCsvIfExists("key", ""));
+
+        assertEquals("not in 1 element", "key1",
+                StringUtils.removeFromCsvIfExists("key", "key1"));
+        assertEquals("not in 2 elements", "key1,key2",
+                StringUtils.removeFromCsvIfExists("key", "key1,key2"));
+
+        assertEquals("in 1 element", "",
+                StringUtils.removeFromCsvIfExists("key", "key"));
+        assertEquals("in 2 elements at position 1", "key2",
+                StringUtils.removeFromCsvIfExists("key", "key,key2"));
+        assertEquals("in 2 elements at position 2", "key1",
+                StringUtils.removeFromCsvIfExists("key", "key1,key"));
+        assertEquals("in 3 elements at position 2", "key1,key3",
+                StringUtils.removeFromCsvIfExists("key", "key1,key,key3"));
+
+        assertEquals("in 3 elements at position 1,2,3", "",
+                StringUtils.removeFromCsvIfExists("key", "key,key,key"));
+        assertEquals("in 5 elements at position 2,4", "key1,key3,key5",
+                StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index 12711c137a8002e7c3c2ea4adfb380b78c9617a2..971fbc21ba31e3a8c578c90b168d4472e87cd71f 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -165,13 +165,13 @@ public class SubtypeLocaleTests extends AndroidTestCase {
 
     public void testAdditionalSubtype() {
         final InputMethodSubtype DE_QWERTY = AdditionalSubtype.createAdditionalSubtype(
-                Locale.GERMAN.toString(), AdditionalSubtype.QWERTY);
+                Locale.GERMAN.toString(), AdditionalSubtype.QWERTY, null);
         final InputMethodSubtype FR_QWERTZ = AdditionalSubtype.createAdditionalSubtype(
-                Locale.FRENCH.toString(), AdditionalSubtype.QWERTZ);
+                Locale.FRENCH.toString(), AdditionalSubtype.QWERTZ, null);
         final InputMethodSubtype EN_AZERTY = AdditionalSubtype.createAdditionalSubtype(
-                Locale.ENGLISH.toString(), AdditionalSubtype.AZERTY);
+                Locale.ENGLISH.toString(), AdditionalSubtype.AZERTY, null);
         final InputMethodSubtype ZZ_AZERTY = AdditionalSubtype.createAdditionalSubtype(
-                SubtypeLocale.NO_LANGUAGE, AdditionalSubtype.AZERTY);
+                SubtypeLocale.NO_LANGUAGE, AdditionalSubtype.AZERTY, null);
 
         assertTrue(AdditionalSubtype.isAdditionalSubtype(FR_QWERTZ));
         assertTrue(AdditionalSubtype.isAdditionalSubtype(DE_QWERTY));