Refactor CustomInputStyleSettingsFragment

Bug: 18003991
Change-Id: Ib005dd394faf546e4faed89ff2018ce49858a06e
diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java
new file mode 100644
index 0000000..c07b60a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStylePreference.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2014 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.settings;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.preference.DialogPreference;
+import android.preference.Preference;
+import android.util.Log;
+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 com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.compat.ViewCompatUtils;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
+import java.util.TreeSet;
+
+final class CustomInputStylePreference extends DialogPreference
+        implements DialogInterface.OnCancelListener {
+    private static final boolean DEBUG_SUBTYPE_ID = false;
+
+    interface Listener {
+        public void onRemoveCustomInputStyle(CustomInputStylePreference stylePref);
+        public void onSaveCustomInputStyle(CustomInputStylePreference stylePref);
+        public void onAddCustomInputStyle(CustomInputStylePreference stylePref);
+        public SubtypeLocaleAdapter getSubtypeLocaleAdapter();
+        public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter();
+    }
+
+    private static final String KEY_PREFIX = "subtype_pref_";
+    private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new";
+
+    private InputMethodSubtype mSubtype;
+    private InputMethodSubtype mPreviousSubtype;
+
+    private final Listener mProxy;
+    private Spinner mSubtypeLocaleSpinner;
+    private Spinner mKeyboardLayoutSetSpinner;
+
+    public static CustomInputStylePreference newIncompleteSubtypePreference(
+            final Context context, final Listener proxy) {
+        return new CustomInputStylePreference(context, null, proxy);
+    }
+
+    public CustomInputStylePreference(final Context context, final InputMethodSubtype subtype,
+            final Listener proxy) {
+        super(context, null);
+        setDialogLayoutResource(R.layout.additional_subtype_dialog);
+        setPersistent(false);
+        mProxy = proxy;
+        setSubtype(subtype);
+    }
+
+    public void show() {
+        showDialog(null);
+    }
+
+    public final boolean isIncomplete() {
+        return mSubtype == null;
+    }
+
+    public InputMethodSubtype getSubtype() {
+        return mSubtype;
+    }
+
+    public void setSubtype(final InputMethodSubtype subtype) {
+        mPreviousSubtype = mSubtype;
+        mSubtype = subtype;
+        if (isIncomplete()) {
+            setTitle(null);
+            setDialogTitle(R.string.add_style);
+            setKey(KEY_NEW_SUBTYPE);
+        } else {
+            final String displayName =
+                    SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
+            setTitle(displayName);
+            setDialogTitle(displayName);
+            setKey(KEY_PREFIX + subtype.getLocale() + "_"
+                    + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype));
+        }
+    }
+
+    public void revert() {
+        setSubtype(mPreviousSubtype);
+    }
+
+    public boolean hasBeenModified() {
+        return mSubtype != null && !mSubtype.equals(mPreviousSubtype);
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+        final View v = super.onCreateDialogView();
+        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());
+        // All keyboard layout names are in the Latin script and thus left to right. That means
+        // the view would align them to the left even if the system locale is RTL, but that
+        // would look strange. To fix this, we align them to the view's start, which will be
+        // natural for any direction.
+        ViewCompatUtils.setTextAlignment(
+                mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START);
+        return v;
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
+        builder.setCancelable(true).setOnCancelListener(this);
+        if (isIncomplete()) {
+            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 = new SubtypeLocaleItem(mSubtype);
+            final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype);
+            setSpinnerPosition(mSubtypeLocaleSpinner, localeItem);
+            setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem);
+        }
+    }
+
+    private static void setSpinnerPosition(final Spinner spinner, final 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 onCancel(final DialogInterface dialog) {
+        if (isIncomplete()) {
+            mProxy.onRemoveCustomInputStyle(this);
+        }
+    }
+
+    @Override
+    public void onClick(final DialogInterface dialog, final int which) {
+        super.onClick(dialog, which);
+        switch (which) {
+        case DialogInterface.BUTTON_POSITIVE:
+            final boolean isEditing = !isIncomplete();
+            final SubtypeLocaleItem locale =
+                    (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
+            final KeyboardLayoutSetItem layout =
+                    (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
+            final InputMethodSubtype subtype =
+                    AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
+                            locale.mLocaleString, layout.mLayoutName);
+            setSubtype(subtype);
+            notifyChanged();
+            if (isEditing) {
+                mProxy.onSaveCustomInputStyle(this);
+            } else {
+                mProxy.onAddCustomInputStyle(this);
+            }
+            break;
+        case DialogInterface.BUTTON_NEUTRAL:
+            // Nothing to do
+            break;
+        case DialogInterface.BUTTON_NEGATIVE:
+            mProxy.onRemoveCustomInputStyle(this);
+            break;
+        }
+    }
+
+    private static int getSpinnerPosition(final Spinner spinner) {
+        if (spinner == null) return -1;
+        return spinner.getSelectedItemPosition();
+    }
+
+    private static void setSpinnerPosition(final Spinner spinner, final int position) {
+        if (spinner == null || position < 0) return;
+        spinner.setSelection(position);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        final Dialog dialog = getDialog();
+        if (dialog == null || !dialog.isShowing()) {
+            return superState;
+        }
+
+        final SavedState myState = new SavedState(superState);
+        myState.mSubtype = mSubtype;
+        myState.mSubtypeLocaleSelectedPos = getSpinnerPosition(mSubtypeLocaleSpinner);
+        myState.mKeyboardLayoutSetSelectedPos = getSpinnerPosition(mKeyboardLayoutSetSpinner);
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(final Parcelable state) {
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        final SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        setSpinnerPosition(mSubtypeLocaleSpinner, myState.mSubtypeLocaleSelectedPos);
+        setSpinnerPosition(mKeyboardLayoutSetSpinner, myState.mKeyboardLayoutSetSelectedPos);
+        setSubtype(myState.mSubtype);
+    }
+
+    static final class SavedState extends Preference.BaseSavedState {
+        InputMethodSubtype mSubtype;
+        int mSubtypeLocaleSelectedPos;
+        int mKeyboardLayoutSetSelectedPos;
+
+        public SavedState(final Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(final Parcel dest, final int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(mSubtypeLocaleSelectedPos);
+            dest.writeInt(mKeyboardLayoutSetSelectedPos);
+            dest.writeParcelable(mSubtype, 0);
+        }
+
+        public SavedState(final Parcel source) {
+            super(source);
+            mSubtypeLocaleSelectedPos = source.readInt();
+            mKeyboardLayoutSetSelectedPos = source.readInt();
+            mSubtype = (InputMethodSubtype)source.readParcelable(null);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    @Override
+                    public SavedState createFromParcel(final Parcel source) {
+                        return new SavedState(source);
+                    }
+
+                    @Override
+                    public SavedState[] newArray(final int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+
+    static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> {
+        public final String mLocaleString;
+        private final String mDisplayName;
+
+        public SubtypeLocaleItem(final InputMethodSubtype subtype) {
+            mLocaleString = subtype.getLocale();
+            mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(
+                    mLocaleString);
+        }
+
+        // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
+        // to get display name.
+        @Override
+        public String toString() {
+            return mDisplayName;
+        }
+
+        @Override
+        public int compareTo(final SubtypeLocaleItem o) {
+            return mLocaleString.compareTo(o.mLocaleString);
+        }
+    }
+
+    static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> {
+        private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName();
+
+        public SubtypeLocaleAdapter(final Context context) {
+            super(context, android.R.layout.simple_spinner_item);
+            setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+            final TreeSet<SubtypeLocaleItem> items = new TreeSet<>();
+            final InputMethodInfo imi = RichInputMethodManager.getInstance()
+                    .getInputMethodInfoOfThisIme();
+            final int count = imi.getSubtypeCount();
+            for (int i = 0; i < count; i++) {
+                final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+                if (DEBUG_SUBTYPE_ID) {
+                    Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s",
+                            subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
+                }
+                if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
+                    items.add(new SubtypeLocaleItem(subtype));
+                }
+            }
+            // TODO: Should filter out already existing combinations of locale and layout.
+            addAll(items);
+        }
+    }
+
+    static final class KeyboardLayoutSetItem {
+        public final String mLayoutName;
+        private final String mDisplayName;
+
+        public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
+            mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
+            mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
+        }
+
+        // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
+        // to get display name.
+        @Override
+        public String toString() {
+            return mDisplayName;
+        }
+    }
+
+    static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> {
+        public KeyboardLayoutSetAdapter(final 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 : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) {
+                // This is a dummy subtype with NO_LANGUAGE, only for display.
+                final InputMethodSubtype subtype =
+                        AdditionalSubtypeUtils.createDummyAdditionalSubtype(
+                                SubtypeLocaleUtils.NO_LANGUAGE, layout);
+                add(new KeyboardLayoutSetItem(subtype));
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
index c633fc1..46fcc71 100644
--- a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
@@ -17,16 +17,12 @@
 package com.android.inputmethod.latin.settings;
 
 import android.app.AlertDialog;
-import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 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;
@@ -39,15 +35,9 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.ArrayAdapter;
-import android.widget.Spinner;
-import android.widget.SpinnerAdapter;
 import android.widget.Toast;
 
-import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
-import com.android.inputmethod.compat.ViewCompatUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
@@ -56,19 +46,18 @@
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.ArrayList;
-import java.util.TreeSet;
 
-public final class CustomInputStyleSettingsFragment extends PreferenceFragment {
+public final class CustomInputStyleSettingsFragment extends PreferenceFragment
+        implements CustomInputStylePreference.Listener {
     private static final String TAG = CustomInputStyleSettingsFragment.class.getSimpleName();
-    private static final boolean DEBUG_SUBTYPE_ID = false;
     // Note: We would like to turn this debug flag true in order to see what input styles are
     // defined in a bug-report.
     private static final boolean DEBUG_CUSTOM_INPUT_STYLES = true;
 
     private RichInputMethodManager mRichImm;
     private SharedPreferences mPrefs;
-    private SubtypeLocaleAdapter mSubtypeLocaleAdapter;
-    private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
+    private CustomInputStylePreference.SubtypeLocaleAdapter mSubtypeLocaleAdapter;
+    private CustomInputStylePreference.KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter;
 
     private boolean mIsAddingNewSubtype;
     private AlertDialog mSubtypeEnablerNotificationDialog;
@@ -79,320 +68,6 @@
             "is_subtype_enabler_notification_dialog_open";
     private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler";
 
-    static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> {
-        public final String mLocaleString;
-        private final String mDisplayName;
-
-        public SubtypeLocaleItem(final InputMethodSubtype subtype) {
-            mLocaleString = subtype.getLocale();
-            mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(
-                    mLocaleString);
-        }
-
-        // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
-        // to get display name.
-        @Override
-        public String toString() {
-            return mDisplayName;
-        }
-
-        @Override
-        public int compareTo(final SubtypeLocaleItem o) {
-            return mLocaleString.compareTo(o.mLocaleString);
-        }
-    }
-
-    static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> {
-        private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName();
-
-        public SubtypeLocaleAdapter(final Context context) {
-            super(context, android.R.layout.simple_spinner_item);
-            setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-
-            final TreeSet<SubtypeLocaleItem> items = new TreeSet<>();
-            final InputMethodInfo imi = RichInputMethodManager.getInstance()
-                    .getInputMethodInfoOfThisIme();
-            final int count = imi.getSubtypeCount();
-            for (int i = 0; i < count; i++) {
-                final InputMethodSubtype subtype = imi.getSubtypeAt(i);
-                if (DEBUG_SUBTYPE_ID) {
-                    Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s",
-                            subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
-                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
-                }
-                if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
-                    items.add(new SubtypeLocaleItem(subtype));
-                }
-            }
-            // TODO: Should filter out already existing combinations of locale and layout.
-            addAll(items);
-        }
-    }
-
-    static final class KeyboardLayoutSetItem {
-        public final String mLayoutName;
-        private final String mDisplayName;
-
-        public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
-            mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
-            mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
-        }
-
-        // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
-        // to get display name.
-        @Override
-        public String toString() {
-            return mDisplayName;
-        }
-    }
-
-    static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> {
-        public KeyboardLayoutSetAdapter(final 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 : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) {
-                // This is a dummy subtype with NO_LANGUAGE, only for display.
-                final InputMethodSubtype subtype =
-                        AdditionalSubtypeUtils.createDummyAdditionalSubtype(
-                                SubtypeLocaleUtils.NO_LANGUAGE, layout);
-                add(new KeyboardLayoutSetItem(subtype));
-            }
-        }
-    }
-
-    private interface SubtypeDialogProxy {
-        public void onRemovePressed(SubtypePreference subtypePref);
-        public void onSavePressed(SubtypePreference subtypePref);
-        public void onAddPressed(SubtypePreference subtypePref);
-        public SubtypeLocaleAdapter getSubtypeLocaleAdapter();
-        public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter();
-    }
-
-    static final class SubtypePreference extends DialogPreference
-            implements DialogInterface.OnCancelListener {
-        private static final String KEY_PREFIX = "subtype_pref_";
-        private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new";
-
-        private InputMethodSubtype mSubtype;
-        private InputMethodSubtype mPreviousSubtype;
-
-        private final SubtypeDialogProxy mProxy;
-        private Spinner mSubtypeLocaleSpinner;
-        private Spinner mKeyboardLayoutSetSpinner;
-
-        public static SubtypePreference newIncompleteSubtypePreference(final Context context,
-                final SubtypeDialogProxy proxy) {
-            return new SubtypePreference(context, null, proxy);
-        }
-
-        public SubtypePreference(final Context context, final InputMethodSubtype subtype,
-                final SubtypeDialogProxy proxy) {
-            super(context, null);
-            setDialogLayoutResource(R.layout.additional_subtype_dialog);
-            setPersistent(false);
-            mProxy = proxy;
-            setSubtype(subtype);
-        }
-
-        public void show() {
-            showDialog(null);
-        }
-
-        public final boolean isIncomplete() {
-            return mSubtype == null;
-        }
-
-        public InputMethodSubtype getSubtype() {
-            return mSubtype;
-        }
-
-        public void setSubtype(final InputMethodSubtype subtype) {
-            mPreviousSubtype = mSubtype;
-            mSubtype = subtype;
-            if (isIncomplete()) {
-                setTitle(null);
-                setDialogTitle(R.string.add_style);
-                setKey(KEY_NEW_SUBTYPE);
-            } else {
-                final String displayName =
-                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
-                setTitle(displayName);
-                setDialogTitle(displayName);
-                setKey(KEY_PREFIX + subtype.getLocale() + "_"
-                        + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype));
-            }
-        }
-
-        public void revert() {
-            setSubtype(mPreviousSubtype);
-        }
-
-        public boolean hasBeenModified() {
-            return mSubtype != null && !mSubtype.equals(mPreviousSubtype);
-        }
-
-        @Override
-        protected View onCreateDialogView() {
-            final View v = super.onCreateDialogView();
-            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());
-            // All keyboard layout names are in the Latin script and thus left to right. That means
-            // the view would align them to the left even if the system locale is RTL, but that
-            // would look strange. To fix this, we align them to the view's start, which will be
-            // natural for any direction.
-            ViewCompatUtils.setTextAlignment(
-                    mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START);
-            return v;
-        }
-
-        @Override
-        protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
-            builder.setCancelable(true).setOnCancelListener(this);
-            if (isIncomplete()) {
-                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 = new SubtypeLocaleItem(mSubtype);
-                final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype);
-                setSpinnerPosition(mSubtypeLocaleSpinner, localeItem);
-                setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem);
-            }
-        }
-
-        private static void setSpinnerPosition(final Spinner spinner, final 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 onCancel(final DialogInterface dialog) {
-            if (isIncomplete()) {
-                mProxy.onRemovePressed(this);
-            }
-        }
-
-        @Override
-        public void onClick(final DialogInterface dialog, final int which) {
-            super.onClick(dialog, which);
-            switch (which) {
-            case DialogInterface.BUTTON_POSITIVE:
-                final boolean isEditing = !isIncomplete();
-                final SubtypeLocaleItem locale =
-                        (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
-                final KeyboardLayoutSetItem layout =
-                        (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
-                final InputMethodSubtype subtype =
-                        AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
-                                locale.mLocaleString, layout.mLayoutName);
-                setSubtype(subtype);
-                notifyChanged();
-                if (isEditing) {
-                    mProxy.onSavePressed(this);
-                } else {
-                    mProxy.onAddPressed(this);
-                }
-                break;
-            case DialogInterface.BUTTON_NEUTRAL:
-                // Nothing to do
-                break;
-            case DialogInterface.BUTTON_NEGATIVE:
-                mProxy.onRemovePressed(this);
-                break;
-            }
-        }
-
-        private static int getSpinnerPosition(final Spinner spinner) {
-            if (spinner == null) return -1;
-            return spinner.getSelectedItemPosition();
-        }
-
-        private static void setSpinnerPosition(final Spinner spinner, final int position) {
-            if (spinner == null || position < 0) return;
-            spinner.setSelection(position);
-        }
-
-        @Override
-        protected Parcelable onSaveInstanceState() {
-            final Parcelable superState = super.onSaveInstanceState();
-            final Dialog dialog = getDialog();
-            if (dialog == null || !dialog.isShowing()) {
-                return superState;
-            }
-
-            final SavedState myState = new SavedState(superState);
-            myState.mSubtype = mSubtype;
-            myState.mSubtypeLocaleSelectedPos = getSpinnerPosition(mSubtypeLocaleSpinner);
-            myState.mKeyboardLayoutSetSelectedPos = getSpinnerPosition(mKeyboardLayoutSetSpinner);
-            return myState;
-        }
-
-        @Override
-        protected void onRestoreInstanceState(final Parcelable state) {
-            if (!(state instanceof SavedState)) {
-                super.onRestoreInstanceState(state);
-                return;
-            }
-
-            final SavedState myState = (SavedState) state;
-            super.onRestoreInstanceState(myState.getSuperState());
-            setSpinnerPosition(mSubtypeLocaleSpinner, myState.mSubtypeLocaleSelectedPos);
-            setSpinnerPosition(mKeyboardLayoutSetSpinner, myState.mKeyboardLayoutSetSelectedPos);
-            setSubtype(myState.mSubtype);
-        }
-
-        static final class SavedState extends Preference.BaseSavedState {
-            InputMethodSubtype mSubtype;
-            int mSubtypeLocaleSelectedPos;
-            int mKeyboardLayoutSetSelectedPos;
-
-            public SavedState(final Parcelable superState) {
-                super(superState);
-            }
-
-            @Override
-            public void writeToParcel(final Parcel dest, final int flags) {
-                super.writeToParcel(dest, flags);
-                dest.writeInt(mSubtypeLocaleSelectedPos);
-                dest.writeInt(mKeyboardLayoutSetSelectedPos);
-                dest.writeParcelable(mSubtype, 0);
-            }
-
-            public SavedState(final Parcel source) {
-                super(source);
-                mSubtypeLocaleSelectedPos = source.readInt();
-                mKeyboardLayoutSetSelectedPos = source.readInt();
-                mSubtype = (InputMethodSubtype)source.readParcelable(null);
-            }
-
-            public static final Parcelable.Creator<SavedState> CREATOR =
-                    new Parcelable.Creator<SavedState>() {
-                        @Override
-                        public SavedState createFromParcel(final Parcel source) {
-                            return new SavedState(source);
-                        }
-
-                        @Override
-                        public SavedState[] newArray(final int size) {
-                            return new SavedState[size];
-                        }
-                    };
-        }
-    }
-
     public CustomInputStyleSettingsFragment() {
         // Empty constructor for fragment generation.
     }
@@ -440,8 +115,9 @@
     @Override
     public void onActivityCreated(final Bundle savedInstanceState) {
         final Context context = getActivity();
-        mSubtypeLocaleAdapter = new SubtypeLocaleAdapter(context);
-        mKeyboardLayoutSetAdapter = new KeyboardLayoutSetAdapter(context);
+        mSubtypeLocaleAdapter = new CustomInputStylePreference.SubtypeLocaleAdapter(context);
+        mKeyboardLayoutSetAdapter =
+                new CustomInputStylePreference.KeyboardLayoutSetAdapter(context);
 
         final String prefSubtypes =
                 Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
@@ -454,7 +130,7 @@
                 && savedInstanceState.containsKey(KEY_IS_ADDING_NEW_SUBTYPE);
         if (mIsAddingNewSubtype) {
             getPreferenceScreen().addPreference(
-                    SubtypePreference.newIncompleteSubtypePreference(context, mSubtypeProxy));
+                    CustomInputStylePreference.newIncompleteSubtypePreference(context, this));
         }
 
         super.onActivityCreated(savedInstanceState);
@@ -482,62 +158,60 @@
         }
     }
 
-    private final SubtypeDialogProxy mSubtypeProxy = new SubtypeDialogProxy() {
-        @Override
-        public void onRemovePressed(final SubtypePreference subtypePref) {
-            mIsAddingNewSubtype = false;
-            final PreferenceGroup group = getPreferenceScreen();
-            group.removePreference(subtypePref);
+    @Override
+    public void onRemoveCustomInputStyle(final CustomInputStylePreference stylePref) {
+        mIsAddingNewSubtype = false;
+        final PreferenceGroup group = getPreferenceScreen();
+        group.removePreference(stylePref);
+        mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
+    }
+
+    @Override
+    public void onSaveCustomInputStyle(final CustomInputStylePreference stylePref) {
+        final InputMethodSubtype subtype = stylePref.getSubtype();
+        if (!stylePref.hasBeenModified()) {
+            return;
+        }
+        if (findDuplicatedSubtype(subtype) == null) {
             mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
+            return;
         }
 
-        @Override
-        public void onSavePressed(final SubtypePreference subtypePref) {
-            final InputMethodSubtype subtype = subtypePref.getSubtype();
-            if (!subtypePref.hasBeenModified()) {
-                return;
-            }
-            if (findDuplicatedSubtype(subtype) == null) {
-                mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
-                return;
-            }
+        // Saved subtype is duplicated.
+        final PreferenceGroup group = getPreferenceScreen();
+        group.removePreference(stylePref);
+        stylePref.revert();
+        group.addPreference(stylePref);
+        showSubtypeAlreadyExistsToast(subtype);
+    }
 
-            // Saved subtype is duplicated.
-            final PreferenceGroup group = getPreferenceScreen();
-            group.removePreference(subtypePref);
-            subtypePref.revert();
-            group.addPreference(subtypePref);
-            showSubtypeAlreadyExistsToast(subtype);
+    @Override
+    public void onAddCustomInputStyle(final CustomInputStylePreference stylePref) {
+        mIsAddingNewSubtype = false;
+        final InputMethodSubtype subtype = stylePref.getSubtype();
+        if (findDuplicatedSubtype(subtype) == null) {
+            mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
+            mSubtypePreferenceKeyForSubtypeEnabler = stylePref.getKey();
+            mSubtypeEnablerNotificationDialog = createDialog();
+            mSubtypeEnablerNotificationDialog.show();
+            return;
         }
 
-        @Override
-        public void onAddPressed(final SubtypePreference subtypePref) {
-            mIsAddingNewSubtype = false;
-            final InputMethodSubtype subtype = subtypePref.getSubtype();
-            if (findDuplicatedSubtype(subtype) == null) {
-                mRichImm.setAdditionalInputMethodSubtypes(getSubtypes());
-                mSubtypePreferenceKeyForSubtypeEnabler = subtypePref.getKey();
-                mSubtypeEnablerNotificationDialog = createDialog();
-                mSubtypeEnablerNotificationDialog.show();
-                return;
-            }
+        // Newly added subtype is duplicated.
+        final PreferenceGroup group = getPreferenceScreen();
+        group.removePreference(stylePref);
+        showSubtypeAlreadyExistsToast(subtype);
+    }
 
-            // Newly added subtype is duplicated.
-            final PreferenceGroup group = getPreferenceScreen();
-            group.removePreference(subtypePref);
-            showSubtypeAlreadyExistsToast(subtype);
-        }
+    @Override
+    public CustomInputStylePreference.SubtypeLocaleAdapter getSubtypeLocaleAdapter() {
+        return mSubtypeLocaleAdapter;
+    }
 
-        @Override
-        public SubtypeLocaleAdapter getSubtypeLocaleAdapter() {
-            return mSubtypeLocaleAdapter;
-        }
-
-        @Override
-        public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() {
-            return mKeyboardLayoutSetAdapter;
-        }
-    };
+    @Override
+    public CustomInputStylePreference.KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() {
+        return mKeyboardLayoutSetAdapter;
+    }
 
     private void showSubtypeAlreadyExistsToast(final InputMethodSubtype subtype) {
         final Context context = getActivity();
@@ -555,6 +229,7 @@
     }
 
     private AlertDialog createDialog() {
+        final String imeId = mRichImm.getInputMethodIdOfThisIme();
         final AlertDialog.Builder builder = new AlertDialog.Builder(
                 DialogUtils.getPlatformDialogThemeContext(getActivity()));
         builder.setTitle(R.string.custom_input_styles_title)
@@ -564,7 +239,7 @@
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                         final Intent intent = IntentUtils.getInputLanguageSelectionIntent(
-                                mRichImm.getInputMethodIdOfThisIme(),
+                                imeId,
                                 Intent.FLAG_ACTIVITY_NEW_TASK
                                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -584,8 +259,8 @@
         final InputMethodSubtype[] subtypesArray =
                 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtypes);
         for (final InputMethodSubtype subtype : subtypesArray) {
-            final SubtypePreference pref = new SubtypePreference(
-                    context, subtype, mSubtypeProxy);
+            final CustomInputStylePreference pref =
+                    new CustomInputStylePreference(context, subtype, this);
             group.addPreference(pref);
         }
     }
@@ -596,8 +271,8 @@
         final int count = group.getPreferenceCount();
         for (int i = 0; i < count; i++) {
             final Preference pref = group.getPreference(i);
-            if (pref instanceof SubtypePreference) {
-                final SubtypePreference subtypePref = (SubtypePreference)pref;
+            if (pref instanceof CustomInputStylePreference) {
+                final CustomInputStylePreference subtypePref = (CustomInputStylePreference)pref;
                 // We should not save newly adding subtype to preference because it is incomplete.
                 if (subtypePref.isIncomplete()) continue;
                 subtypes.add(subtypePref.getSubtype());
@@ -631,8 +306,8 @@
     public boolean onOptionsItemSelected(final MenuItem item) {
         final int itemId = item.getItemId();
         if (itemId == R.id.action_add_style) {
-            final SubtypePreference newSubtype =
-                    SubtypePreference.newIncompleteSubtypePreference(getActivity(), mSubtypeProxy);
+            final CustomInputStylePreference newSubtype =
+                    CustomInputStylePreference.newIncompleteSubtypePreference(getActivity(), this);
             getPreferenceScreen().addPreference(newSubtype);
             newSubtype.show();
             mIsAddingNewSubtype = true;