Add custom subtype settings

Bug: 4460018
Change-Id: I4919d79516dcf574be2761bbaf9adcdc381b2ddc
diff --git a/java/res/layout/additional_subtype_dialog.xml b/java/res/layout/additional_subtype_dialog.xml
new file mode 100644
index 0000000..f97c006
--- /dev/null
+++ b/java/res/layout/additional_subtype_dialog.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:columnCount="2"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginLeft="8dip"
+    android:layout_marginRight="8dip"
+    android:padding="8dip">
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="left|center_vertical"
+        style="?android:attr/textAppearanceSmall"
+        android:text="@string/subtype_locale" />
+    <Spinner
+        android:id="@+id/subtype_locale_spinner"
+        android:layout_width="wrap_content"
+        android:layout_marginLeft="8dip"
+        android:layout_marginBottom="8dip"
+        android:layout_marginTop="8dip"
+        android:layout_gravity="fill_horizontal|center_vertical"
+        android:prompt="@string/subtype_locale" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="left|center_vertical"
+        style="?android:attr/textAppearanceSmall"
+        android:text="@string/keyboard_layout_set" />
+    <Spinner
+        android:id="@+id/keyboard_layout_set_spinner"
+        android:layout_width="wrap_content"
+        android:layout_marginLeft="8dip"
+        android:layout_marginBottom="8dip"
+        android:layout_marginTop="8dip"
+        android:layout_gravity="fill_horizontal|center_vertical"
+        android:prompt="@string/keyboard_layout_set" />
+</GridLayout>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 28acd0f..93bd268 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -252,9 +252,26 @@
     <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 keyboard subtype [CHAR LIMIT=22] -->
+    <string name="subtype_no_language">No language</string>
     <!-- Description for language agnostic QWERTY keyboard subtype [CHAR LIMIT=22] -->
     <string name="subtype_no_language_qwerty">No language (QWERTY)</string>
 
+    <!-- Title of the preference settings for custom input styles (language and keyboard layout pairs) [CHAR LIMIT=22]-->
+    <string name="custom_input_styles_title">Custom input styles</string>
+    <!-- Title of the option menu to add a new style entry in the preference settings [CHAR_LIMIT=12] -->
+    <string name="add_style">Add style</string>
+    <!-- Title of the button to add custom style entry in the settings dialog [CHAR_LIMIT=12]  -->
+    <string name="add">Add</string>
+    <!-- Title of the button to remove a custom style entry in the settings dialog [CHAR_LIMIT=12]  -->
+    <string name="remove">Remove</string>
+    <!-- Title of the button to save a custom style entry in the settings dialog [CHAR_LIMIT=12]  -->
+    <string name="save">Save</string>
+    <!-- Title of the spinner for choosing a language of custom style in the settings dialog [CHAR_LIMIT=12]  -->
+    <string name="subtype_locale">Language</string>
+    <!-- Title of the spinner for choosing a keyboard layout of custom style in the settings dialog [CHAR_LIMIT=12]  -->
+    <string name="keyboard_layout_set">Layout</string>
+
     <!-- Title of an option for usability study mode -->
     <string name="prefs_usability_study_mode">Usability study mode</string>
     <!-- Title of the settings for keypress vibration duration -->
diff --git a/java/res/xml/additional_subtype_settings.xml b/java/res/xml/additional_subtype_settings.xml
new file mode 100644
index 0000000..ad280c1
--- /dev/null
+++ b/java/res/xml/additional_subtype_settings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Items will be added at runtime -->
+</PreferenceScreen>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index ab5d44b..3598a68 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -100,6 +100,10 @@
                 android:summary="@string/include_other_imes_in_language_switch_list_summary"
                 android:persistent="true"
                 android:defaultValue="false" />
+            <PreferenceScreen
+                android:fragment="com.android.inputmethod.latin.AdditionalSubtypeSettings"
+                android:key="custom_input_styles"
+                android:title="@string/custom_input_styles_title" />
             <!-- Values for popup dismiss delay are added programatically -->
             <ListPreference
                 android:key="pref_key_preview_popup_dismiss_delay"
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index 323f74a..777dab4 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -24,6 +24,11 @@
     public static final String QWERTY = "qwerty";
     public static final String QWERTZ = "qwertz";
     public static final String AZERTY = "azerty";
+    public static final String[] PREDEFINED_KEYBOARD_LAYOUT_SET = {
+        QWERTY,
+        QWERTZ,
+        AZERTY
+    };
 
     private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
     private static final String SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
@@ -38,40 +43,60 @@
         sKeyboardLayoutToNameIdsMap.put(AZERTY, R.string.subtype_generic_azerty);
     }
 
+    private AdditionalSubtype() {
+        // This utility class is not publicly instantiable.
+    }
+
     public static boolean isAdditionalSubtype(InputMethodSubtype subtype) {
         return subtype.containsExtraValueKey(SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE);
     }
 
+    private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
+    public static final String PREF_SUBTYPE_SEPARATOR = ";";
+
     public static InputMethodSubtype createAdditionalSubtype(
             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));
+                SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE, 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, filteredExtraValue, false, false);
+                localeString, SUBTYPE_MODE_KEYBOARD,
+                layoutExtraValue + "," + filteredExtraValue, false, false);
     }
 
-    private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
-    private static final String PREF_SUBTYPE_SEPARATOR = ";";
+    public static String getPrefSubtype(InputMethodSubtype subtype) {
+        final String localeString = subtype.getLocale();
+        final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+        final String layoutExtraValue = LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET + "="
+                + keyboardLayoutSetName;
+        final String extraValue = StringUtils.removeFromCsvIfExists(layoutExtraValue,
+                StringUtils.removeFromCsvIfExists(SUBTYPE_EXTRA_VALUE_IS_ADDITIONAL_SUBTYPE,
+                        subtype.getExtraValue()));
+        final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR
+                + keyboardLayoutSetName;
+        return extraValue.isEmpty() ? basePrefSubtype
+                : basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
+    }
+
+    public static InputMethodSubtype createAdditionalSubtype(String prefSubtype) {
+        final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
+        if (elems.length < 2 || elems.length > 3) {
+            throw new RuntimeException("Unknown additional subtype specified: " + prefSubtype);
+        }
+        final String localeString = elems[0];
+        final String keyboardLayoutSetName = elems[1];
+        final String extraValue = (elems.length == 3) ? elems[2] : null;
+        return createAdditionalSubtype(localeString, keyboardLayoutSetName, extraValue);
+    }
 
     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, extraValue);
+            subtypesArray[i] = createAdditionalSubtype(prefSubtypeArray[i]);
         }
         return subtypesArray;
     }
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
new file mode 100644
index 0000000..6ff8aa2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -0,0 +1,438 @@
+/**
+ * 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.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.DialogPreference;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+
+import java.util.Locale;
+import java.util.TreeSet;
+
+public class AdditionalSubtypeSettings extends PreferenceFragment {
+    private SharedPreferences mPrefs;
+    private SubtypeLocaleAdapter mSubtypeLocaleAdapter;
+    private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
+
+    private PreferenceGroup mSubtypePrefGroup;
+
+    private static final int MENU_ADD_SUBTYPE = Menu.FIRST;
+
+    static class SubtypeLocaleItem extends Pair<String, String>
+            implements Comparable<SubtypeLocaleItem> {
+        public SubtypeLocaleItem(String localeString, String displayName) {
+            super(localeString, displayName);
+        }
+
+        public SubtypeLocaleItem(String localeString) {
+            this(localeString, getDisplayName(localeString));
+        }
+
+        @Override
+        public String toString() {
+            return second;
+        }
+
+        @Override
+        public int compareTo(SubtypeLocaleItem o) {
+            return first.compareTo(o.first);
+        }
+
+        private static String getDisplayName(String localeString) {
+            final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+            return StringUtils.toTitleCase(locale.getDisplayName(locale), locale);
+        }
+    }
+
+    static class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> {
+        public SubtypeLocaleAdapter(Context context) {
+            super(context, android.R.layout.simple_spinner_item);
+            setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+            final TreeSet<SubtypeLocaleItem> items = new TreeSet<SubtypeLocaleItem>();
+            final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context);
+            final int count = imi.getSubtypeCount();
+            for (int i = 0; i < count; i++) {
+                final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+                if (subtype.containsExtraValueKey(LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE)) {
+                    items.add(createItem(context, subtype.getLocale()));
+                }
+            }
+            // TODO: Should filter out already existing combinations of locale and layout.
+            addAll(items);
+        }
+
+        public static SubtypeLocaleItem createItem(Context context, String localeString) {
+            if (localeString.equals(SubtypeLocale.NO_LANGUAGE)) {
+                final String displayName = context.getString(R.string.subtype_no_language);
+                return new SubtypeLocaleItem(localeString, displayName);
+            } else {
+                return new SubtypeLocaleItem(localeString);
+            }
+        }
+    }
+
+    static class KeyboardLayoutSetItem extends Pair<String, String> {
+        public KeyboardLayoutSetItem(String keyboardLayoutSetName) {
+            super(keyboardLayoutSetName, getDisplayName(keyboardLayoutSetName));
+        }
+
+        @Override
+        public String toString() {
+            return second;
+        }
+
+        private static String getDisplayName(String keyboardLayoutSetName) {
+            return keyboardLayoutSetName.toUpperCase();
+        }
+    }
+
+    static class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> {
+        public KeyboardLayoutSetAdapter(Context context) {
+            super(context, android.R.layout.simple_spinner_item);
+            setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+            // TODO: Should filter out already existing combinations of locale and layout.
+            for (final String layout : AdditionalSubtype.PREDEFINED_KEYBOARD_LAYOUT_SET) {
+                add(new KeyboardLayoutSetItem(layout));
+            }
+        }
+    }
+
+    private interface SubtypeDialogProxy {
+        public void onRemovePressed(SubtypePreference subtypePref);
+        public SubtypeLocaleAdapter getSubtypeLocaleAdapter();
+        public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter();
+    }
+
+    static class SubtypePreference extends DialogPreference {
+        private InputMethodSubtype mSubtype;
+
+        private final SubtypeDialogProxy mProxy;
+        private Spinner mSubtypeLocaleSpinner;
+        private Spinner mKeyboardLayoutSetSpinner;
+
+        public SubtypePreference(Context context, InputMethodSubtype subtype,
+                    SubtypeDialogProxy proxy) {
+            super(context, null);
+            setPersistent(false);
+            mProxy = proxy;
+            setSubtype(subtype);
+        }
+
+        public void show() {
+            showDialog(null);
+        }
+
+        public InputMethodSubtype getSubtype() {
+            return mSubtype;
+        }
+
+        public void setSubtype(InputMethodSubtype subtype) {
+            mSubtype = subtype;
+            if (subtype == null) {
+                setTitle(null);
+                setDialogTitle(R.string.add_style);
+            } else {
+                final String displayName = SubtypeLocale.getFullDisplayName(subtype);
+                setTitle(displayName);
+                setDialogTitle(displayName);
+            }
+        }
+
+        @Override
+        protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+            final Context context = builder.getContext();
+            final View v = LayoutInflater.from(context).inflate(
+                    R.layout.additional_subtype_dialog, null);
+            builder.setView(v);
+            mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner);
+            mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter());
+            mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner);
+            mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter());
+
+            if (mSubtype == null) {
+                builder.setPositiveButton(R.string.add, this)
+                        .setNegativeButton(android.R.string.cancel, this);
+            } else {
+                builder.setPositiveButton(R.string.save, this)
+                        .setNeutralButton(android.R.string.cancel, this)
+                        .setNegativeButton(R.string.remove, this);
+                final SubtypeLocaleItem localeItem = SubtypeLocaleAdapter.createItem(
+                        context, mSubtype.getLocale());
+                final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(
+                        SubtypeLocale.getKeyboardLayoutSetName(mSubtype));
+                setSpinnerPosition(mSubtypeLocaleSpinner, localeItem);
+                setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem);
+            }
+        }
+
+        private static void setSpinnerPosition(Spinner spinner, Object itemToSelect) {
+            final SpinnerAdapter adapter = spinner.getAdapter();
+            final int count = adapter.getCount();
+            for (int i = 0; i < count; i++) {
+                final Object item = spinner.getItemAtPosition(i);
+                if (item.equals(itemToSelect)) {
+                    spinner.setSelection(i);
+                    return;
+                }
+            }
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            super.onClick(dialog, which);
+            switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                final SubtypeLocaleItem locale =
+                        (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
+                final KeyboardLayoutSetItem layout =
+                        (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
+                final InputMethodSubtype subtype = AdditionalSubtype.createAdditionalSubtype(
+                        locale.first, layout.first, LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
+                setSubtype(subtype);
+                notifyChanged();
+                break;
+            case DialogInterface.BUTTON_NEUTRAL:
+                // Nothing to do
+                break;
+            case DialogInterface.BUTTON_NEGATIVE:
+                mProxy.onRemovePressed(this);
+                break;
+            }
+        }
+
+        @Override
+        protected Parcelable onSaveInstanceState() {
+            final SavedState myState = new SavedState(super.onSaveInstanceState());
+            myState.mSubtype = mSubtype;
+            return myState;
+        }
+
+        @Override
+        protected void onRestoreInstanceState(Parcelable state) {
+            if (state instanceof SavedState) {
+                final SavedState myState = (SavedState) state;
+                super.onRestoreInstanceState(state);
+                setSubtype(myState.mSubtype);
+            } else {
+                super.onRestoreInstanceState(state);
+            }
+        }
+
+        static class SavedState extends Preference.BaseSavedState {
+            InputMethodSubtype mSubtype;
+            private static final byte VALID = 1;
+            private static final byte INVALID = 0;
+
+            public SavedState(Parcelable superState) {
+                super(superState);
+            }
+
+            @Override
+            public void writeToParcel(Parcel dest, int flags) {
+                super.writeToParcel(dest, flags);
+                if (mSubtype != null) {
+                    dest.writeByte(VALID);
+                    mSubtype.writeToParcel(dest, 0);
+                } else {
+                    dest.writeByte(INVALID);
+                }
+            }
+
+            public SavedState(Parcel source) {
+                super(source);
+                if (source.readByte() == VALID) {
+                    mSubtype = source.readParcelable(null);
+                } else {
+                    mSubtype = null;
+                }
+            }
+
+            public static final Parcelable.Creator<SavedState> CREATOR =
+                    new Parcelable.Creator<SavedState>() {
+                        @Override
+                        public SavedState createFromParcel(Parcel source) {
+                            return new SavedState(source);
+                        }
+
+                        @Override
+                        public SavedState[] newArray(int size) {
+                            return new SavedState[size];
+                        }
+                    };
+        }
+    }
+
+    public AdditionalSubtypeSettings() {
+        // Empty constructor for fragment generation.
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        addPreferencesFromResource(R.xml.additional_subtype_settings);
+        setHasOptionsMenu(true);
+        mSubtypePrefGroup = getPreferenceScreen();
+
+        mPrefs = getPreferenceManager().getSharedPreferences();
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        final Context context = getActivity();
+        mSubtypeLocaleAdapter = new SubtypeLocaleAdapter(context);
+        mKeyboardLayoutSetAdapter = new KeyboardLayoutSetAdapter(context);
+
+        // TODO: Restore editing dialog if any.
+    }
+
+    private final SubtypeDialogProxy mSubtypeProxy = new SubtypeDialogProxy() {
+        @Override
+        public void onRemovePressed(SubtypePreference subtypePref) {
+            final PreferenceGroup group = mSubtypePrefGroup;
+            if (group != null) {
+                group.removePreference(subtypePref);
+            }
+        }
+
+        @Override
+        public SubtypeLocaleAdapter getSubtypeLocaleAdapter() {
+            return mSubtypeLocaleAdapter;
+        }
+
+        @Override
+        public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() {
+            return mKeyboardLayoutSetAdapter;
+        }
+    };
+
+    private void setPrefSubtypes(String prefSubtypes, Context context) {
+        final PreferenceGroup group = mSubtypePrefGroup;
+        group.removeAll();
+        final String[] prefSubtypeArray = prefSubtypes.split(
+                AdditionalSubtype.PREF_SUBTYPE_SEPARATOR);
+        for (final String prefSubtype : prefSubtypeArray) {
+            final InputMethodSubtype subtype =
+                    AdditionalSubtype.createAdditionalSubtype(prefSubtype);
+            final SubtypePreference pref = new SubtypePreference(
+                    context, subtype, mSubtypeProxy);
+            group.addPreference(pref);
+        }
+    }
+
+    private String getPrefSubtypes() {
+        final StringBuilder sb = new StringBuilder();
+        final int count = mSubtypePrefGroup.getPreferenceCount();
+        for (int i = 0; i < count; i++) {
+            final Preference pref = mSubtypePrefGroup.getPreference(i);
+            if (pref instanceof SubtypePreference) {
+                final InputMethodSubtype subtype = ((SubtypePreference)pref).getSubtype();
+                if (sb.length() > 0) {
+                    sb.append(AdditionalSubtype.PREF_SUBTYPE_SEPARATOR);
+                }
+                sb.append(AdditionalSubtype.getPrefSubtype(subtype));
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        final String prefSubtypes =
+                SettingsValues.getPrefAdditionalSubtypes(mPrefs, getResources());
+        setPrefSubtypes(prefSubtypes, getActivity());
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        final String oldSubtypes = SettingsValues.getPrefAdditionalSubtypes(mPrefs, getResources());
+        final String prefSubtypes = getPrefSubtypes();
+        if (prefSubtypes.equals(oldSubtypes)) {
+            return;
+        }
+
+        final SharedPreferences.Editor editor = mPrefs.edit();
+        try {
+            editor.putString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes);
+        } finally {
+            editor.apply();
+        }
+        final InputMethodSubtype[] subtypes =
+                AdditionalSubtype.createAdditionalSubtypesArray(prefSubtypes);
+        ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), subtypes);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        // TODO: save editing dialog state.
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen prefScreen, Preference pref) {
+        if (pref instanceof SubtypePreference) {
+            return true;
+        }
+        return super.onPreferenceTreeClick(prefScreen, pref);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        final MenuItem addSubtypeMenu = menu.add(0, MENU_ADD_SUBTYPE, 0, R.string.add_style);
+        addSubtypeMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == MENU_ADD_SUBTYPE) {
+            final SubtypePreference subtypePref = new SubtypePreference(
+                    getActivity(), null, mSubtypeProxy);
+            mSubtypePrefGroup.addPreference(subtypePref);
+            subtypePref.show();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 29825df..13264f7 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -33,6 +33,7 @@
 import android.preference.PreferenceScreen;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.inputmethod.InputMethodSubtype;
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
 import android.widget.TextView;
@@ -255,6 +256,7 @@
         }
         updateShowCorrectionSuggestionsSummary();
         updateKeyPreviewPopupDelaySummary();
+        updateCustomInputStylesSummary();
     }
 
     @Override
@@ -294,6 +296,21 @@
                         mShowCorrectionSuggestionsPreference.getValue())]);
     }
 
+    private void updateCustomInputStylesSummary() {
+        final PreferenceScreen customInputStyles =
+                (PreferenceScreen)findPreference(PREF_CUSTOM_INPUT_STYLES);
+        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+        final String prefSubtype = SettingsValues.getPrefAdditionalSubtypes(prefs, getResources());
+        final InputMethodSubtype[] subtypes =
+                AdditionalSubtype.createAdditionalSubtypesArray(prefSubtype);
+        final StringBuilder styles = new StringBuilder();
+        for (final InputMethodSubtype subtype : subtypes) {
+            if (styles.length() > 0) styles.append(", ");
+            styles.append(SubtypeLocale.getFullDisplayName(subtype));
+        }
+        customInputStyles.setSummary(styles);
+    }
+
     private void updateKeyPreviewPopupDelaySummary() {
         final ListPreference lp = mKeyPreviewPopupDismissDelay;
         lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 5567013..68f8582 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -20,11 +20,15 @@
 import android.preference.PreferenceActivity;
 
 public class SettingsActivity extends PreferenceActivity {
+    private static final String DEFAULT_FRAGMENT = Settings.class.getName();
+
     @Override
     public Intent getIntent() {
-        final Intent modIntent = new Intent(super.getIntent());
-        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, Settings.class.getName());
-        modIntent.putExtra(EXTRA_NO_HEADERS, true);
-        return modIntent;
+        final Intent intent = super.getIntent();
+        if (!intent.hasExtra(EXTRA_SHOW_FRAGMENT)) {
+            intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
+        }
+        intent.putExtra(EXTRA_NO_HEADERS, true);
+        return intent;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index b83cec4..5f9e1bc 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -28,7 +28,6 @@
 
 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
@@ -148,7 +147,6 @@
                 mAutoCorrectionThresholdRawValue);
         mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
         mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
-
         mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray(
                 getPrefAdditionalSubtypes(prefs, res));
     }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 37f46fc..787e86a 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -24,6 +24,8 @@
 import java.util.Locale;
 
 public class SubtypeLocale {
+    private static final String TAG = SubtypeLocale.class.getSimpleName();
+
     // Special language code to represent "no language".
     public static final String NO_LANGUAGE = "zz";
 
@@ -56,26 +58,29 @@
     //  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)
+    //  en_US azerty T  En  English   English (US) (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 getKeyboardLayoutSetDisplayName(subtype);
         }
 
+        final String exceptionalValue = sExceptionalDisplayNamesMap.get(subtype.getLocale());
+
         final Locale locale = getSubtypeLocale(subtype);
-        final String language = StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale);
         if (AdditionalSubtype.isAdditionalSubtype(subtype)) {
-            return String.format("%s (%s)",
-                    language, getKeyboardLayoutSetDisplayName(subtype));
+            final String language = (exceptionalValue != null) ? exceptionalValue
+                    : StringUtils.toTitleCase(locale.getDisplayLanguage(locale), locale);
+            final String layout = getKeyboardLayoutSetDisplayName(subtype);
+            return String.format("%s (%s)", language, layout);
         }
+
+        if (exceptionalValue != null) {
+            return exceptionalValue;
+        }
+
         return StringUtils.toTitleCase(locale.getDisplayName(locale), locale);
     }
 
@@ -116,7 +121,11 @@
                 LatinIME.SUBTYPE_EXTRA_VALUE_KEYBOARD_LAYOUT_SET);
         // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is
         // fixed.
-        if (keyboardLayoutSet == null) return AdditionalSubtype.QWERTY;
+        if (keyboardLayoutSet == null) {
+            android.util.Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " +
+                    getFullDisplayName(subtype) + " extraValue=" + subtype.getExtraValue());
+            return AdditionalSubtype.QWERTY;
+        }
         return keyboardLayoutSet;
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index 971fbc2..b294770 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -110,6 +110,7 @@
     //  zz    qwerty F      QWERTY    QWERTY
     //  fr    qwertz T  Fr  Français  Français (QWERTZ)
     //  de    qwerty T  De  Deutsch   Deutsch (QWERTY)
+    //  en_US azerty T  En  English   English (US) (AZERTY)
     //  zz    azerty T      AZERTY    AZERTY
 
     public void testSampleSubtypes() {
@@ -168,29 +169,33 @@
                 Locale.GERMAN.toString(), AdditionalSubtype.QWERTY, null);
         final InputMethodSubtype FR_QWERTZ = AdditionalSubtype.createAdditionalSubtype(
                 Locale.FRENCH.toString(), AdditionalSubtype.QWERTZ, null);
-        final InputMethodSubtype EN_AZERTY = AdditionalSubtype.createAdditionalSubtype(
-                Locale.ENGLISH.toString(), AdditionalSubtype.AZERTY, null);
+        final InputMethodSubtype US_AZERTY = AdditionalSubtype.createAdditionalSubtype(
+                Locale.US.toString(), AdditionalSubtype.AZERTY, null);
         final InputMethodSubtype ZZ_AZERTY = AdditionalSubtype.createAdditionalSubtype(
                 SubtypeLocale.NO_LANGUAGE, AdditionalSubtype.AZERTY, null);
 
         assertTrue(AdditionalSubtype.isAdditionalSubtype(FR_QWERTZ));
         assertTrue(AdditionalSubtype.isAdditionalSubtype(DE_QWERTY));
-        assertTrue(AdditionalSubtype.isAdditionalSubtype(EN_AZERTY));
+        assertTrue(AdditionalSubtype.isAdditionalSubtype(US_AZERTY));
         assertTrue(AdditionalSubtype.isAdditionalSubtype(ZZ_AZERTY));
 
-        assertEquals("fr qwertz", "Français (QWERTZ)", SubtypeLocale.getFullDisplayName(FR_QWERTZ));
-        assertEquals("de qwerty", "Deutsch (QWERTY)",  SubtypeLocale.getFullDisplayName(DE_QWERTY));
-        assertEquals("en azerty", "English (AZERTY)",  SubtypeLocale.getFullDisplayName(EN_AZERTY));
-        assertEquals("zz azerty", "AZERTY",            SubtypeLocale.getFullDisplayName(ZZ_AZERTY));
+        assertEquals("fr qwertz",    "Français (QWERTZ)",
+                SubtypeLocale.getFullDisplayName(FR_QWERTZ));
+        assertEquals("de qwerty",    "Deutsch (QWERTY)",
+                SubtypeLocale.getFullDisplayName(DE_QWERTY));
+        assertEquals("en_US azerty", "English (US) (AZERTY)",
+                SubtypeLocale.getFullDisplayName(US_AZERTY));
+        assertEquals("zz azerty",    "AZERTY",
+                SubtypeLocale.getFullDisplayName(ZZ_AZERTY));
 
-        assertEquals("fr qwertz", "Français", SubtypeLocale.getMiddleDisplayName(FR_QWERTZ));
-        assertEquals("de qwerty", "Deutsch",  SubtypeLocale.getMiddleDisplayName(DE_QWERTY));
-        assertEquals("en azerty", "English",  SubtypeLocale.getMiddleDisplayName(EN_AZERTY));
-        assertEquals("zz azerty", "AZERTY",   SubtypeLocale.getMiddleDisplayName(ZZ_AZERTY));
+        assertEquals("fr qwertz",    "Français", SubtypeLocale.getMiddleDisplayName(FR_QWERTZ));
+        assertEquals("de qwerty",    "Deutsch",  SubtypeLocale.getMiddleDisplayName(DE_QWERTY));
+        assertEquals("en_US azerty", "English",  SubtypeLocale.getMiddleDisplayName(US_AZERTY));
+        assertEquals("zz azerty",    "AZERTY",   SubtypeLocale.getMiddleDisplayName(ZZ_AZERTY));
 
-        assertEquals("fr qwertz", "Fr", SubtypeLocale.getShortDisplayName(FR_QWERTZ));
-        assertEquals("de qwerty", "De", SubtypeLocale.getShortDisplayName(DE_QWERTY));
-        assertEquals("en azerty", "En", SubtypeLocale.getShortDisplayName(EN_AZERTY));
-        assertEquals("zz azerty", "", SubtypeLocale.getShortDisplayName(ZZ_AZERTY));
+        assertEquals("fr qwertz",    "Fr", SubtypeLocale.getShortDisplayName(FR_QWERTZ));
+        assertEquals("de qwerty",    "De", SubtypeLocale.getShortDisplayName(DE_QWERTY));
+        assertEquals("en_US azerty", "En", SubtypeLocale.getShortDisplayName(US_AZERTY));
+        assertEquals("zz azerty",    "", SubtypeLocale.getShortDisplayName(ZZ_AZERTY));
     }
 }