Reorganize language & input settings

Bug: 14860252
Bug: 16115751
Change-Id: I198aabebc08421764b78e137e7f26d2a7772d452
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d8c3171..b52f40d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3116,8 +3116,10 @@
     <string name="input_methods_settings_title">Text input</string>
     <!-- Setting name for Input Method chooser -->
     <string name="input_method">Input method</string>
-    <!-- Title for the option to press to choose the current input method [CHAR LIMIT=35] -->
-    <string name="current_input_method">Default</string>
+    <!-- Title for the option to press to enable or disable keyboards, also known as input methods [CHAR LIMIT=35] -->
+    <string name="choose_input_methods">Choose Keyboards</string>
+    <!-- Title for the option to press to choose the current keyboard, also known as input method [CHAR LIMIT=35] -->
+    <string name="current_input_method">Current Keyboard</string>
     <!-- Title for setting the visibility of input method selector [CHAR LIMIT=35] -->
     <string name="input_method_selector">Input method selector</string>
     <!-- An option to always show input method selector automatically when needed [CHAR LIMIT=25] -->
diff --git a/res/xml/language_settings.xml b/res/xml/language_settings.xml
index c210312..d8e7603 100644
--- a/res/xml/language_settings.xml
+++ b/res/xml/language_settings.xml
@@ -32,10 +32,17 @@
             android:key="key_user_dictionary_settings"
             android:title="@string/user_dict_settings_title" />
 
-    <PreferenceCategory android:key="keyboard_settings_category"
+    <PreferenceCategory
+            android:key="keyboard_settings_category"
             android:title="@string/keyboard_settings_category">
-        <PreferenceScreen android:key="current_input_method"
+        <!-- An intent for this preference will be populated programmatically. -->
+        <PreferenceScreen
+                android:key="choose_input_methods"
+                android:title="@string/choose_input_methods" />
+        <PreferenceScreen
+                android:key="current_input_method"
                 android:title="@string/current_input_method" />
+        <!-- Enabled input method list will be populated programmatically here. -->
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index 0f9ae19..073daa6 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -16,26 +16,15 @@
 
 package com.android.settings.inputmethod;
 
-import android.content.ComponentName;
-import android.content.pm.ServiceInfo;
-import com.android.settings.R;
-import com.android.settings.Settings.KeyboardLayoutPickerActivity;
-import com.android.settings.Settings.SpellCheckersSettingsActivity;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.UserDictionarySettings;
-import com.android.settings.Utils;
-import com.android.settings.VoiceInputOutputSettings;
-import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.search.Indexable;
-import com.android.settings.search.SearchIndexableRaw;
-
 import android.app.Activity;
 import android.app.Fragment;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -47,7 +36,6 @@
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceScreen;
@@ -60,17 +48,33 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.BaseAdapter;
 
+import com.android.settings.R;
+import com.android.settings.Settings.KeyboardLayoutPickerActivity;
+import com.android.settings.Settings.SpellCheckersSettingsActivity;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.SubSettings;
+import com.android.settings.UserDictionarySettings;
+import com.android.settings.Utils;
+import com.android.settings.VoiceInputOutputSettings;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableRaw;
+
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.TreeSet;
 
 public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener,
-        KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable {
+        KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable,
+        InputMethodPreference.onSavePreferenceListener {
     private static final String KEY_PHONE_LANGUAGE = "phone_language";
+    private static final String KEY_CHOOSE_INPUT_METHODS = "choose_input_methods";
     private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
     private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
     private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
@@ -95,37 +99,29 @@
     private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = new ArrayList<>();
     private InputManager mIm;
     private InputMethodManager mImm;
-    private boolean mIsOnlyImeSettings;
+    private boolean mShowsOnlyFullImeAndKeyboardList;
     private Handler mHandler;
     private SettingsObserver mSettingsObserver;
     private Intent mIntentWaitingForResult;
     private InputMethodSettingValuesWrapper mInputMethodSettingValues;
 
-    private final OnPreferenceChangeListener mOnImePreferenceChangedListener =
-            new OnPreferenceChangeListener() {
-                @Override
-                public boolean onPreferenceChange(Preference arg0, Object arg1) {
-                    InputMethodSettingValuesWrapper.getInstance(
-                            arg0.getContext()).refreshAllInputMethodAndSubtypes();
-                    ((BaseAdapter)getPreferenceScreen().getRootAdapter()).notifyDataSetChanged();
-                    updateInputMethodPreferenceViews();
-                    return true;
-                }
-            };
-
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
         addPreferencesFromResource(R.xml.language_settings);
 
+        final Activity activity = getActivity();
+        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
+
         try {
             mDefaultInputMethodSelectorVisibility = Integer.valueOf(
                     getString(R.string.input_method_selector_visibility_default_value));
         } catch (NumberFormatException e) {
         }
 
-        if (getActivity().getAssets().getLocales().length == 1) {
+        if (activity.getAssets().getLocales().length == 1) {
             // No "Select language" pref if there's only one system locale available.
             getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
         } else {
@@ -149,45 +145,42 @@
                 "game_controller_settings_category");
 
         // Filter out irrelevant features if invoked from IME settings button.
-        mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
-                getActivity().getIntent().getAction());
-        getActivity().getIntent().setAction(null);
-        if (mIsOnlyImeSettings) {
+        mShowsOnlyFullImeAndKeyboardList = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
+                activity.getIntent().getAction());
+        activity.getIntent().setAction(null);
+        if (mShowsOnlyFullImeAndKeyboardList) {
             getPreferenceScreen().removeAll();
             getPreferenceScreen().addPreference(mHardKeyboardCategory);
             if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
                 getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
             }
+            mKeyboardSettingsCategory.removeAll();
             getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
-        }
-
-        // Build IME preference category.
-        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(getActivity());
-
-        mKeyboardSettingsCategory.removeAll();
-        if (!mIsOnlyImeSettings) {
-            final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null);
-            currentIme.setKey(KEY_CURRENT_INPUT_METHOD);
-            currentIme.setTitle(getResources().getString(R.string.current_input_method));
-            mKeyboardSettingsCategory.addPreference(currentIme);
+        } else {
+            final Preference pref = findPreference(KEY_CHOOSE_INPUT_METHODS);
+            final Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
+            intent.setClass(activity, SubSettings.class);
+            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, getClass().getName());
+            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
+                    R.string.choose_input_methods);
+            pref.setIntent(intent);
         }
 
         // Build hard keyboard and game controller preference categories.
-        mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE);
+        mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE);
         updateInputDevices();
 
         // Spell Checker
-        final Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.setClass(getActivity(), SpellCheckersSettingsActivity.class);
         final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference(
                 "spellcheckers_settings"));
         if (scp != null) {
+            final Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClass(activity, SpellCheckersSettingsActivity.class);
             scp.setFragmentIntent(this, intent);
         }
 
         mHandler = new Handler();
-        mSettingsObserver = new SettingsObserver(mHandler, getActivity());
+        mSettingsObserver = new SettingsObserver(mHandler, activity);
     }
 
     private void updateInputMethodSelectorSummary(int value) {
@@ -246,7 +239,7 @@
         mSettingsObserver.resume();
         mIm.registerInputDeviceListener(this, null);
 
-        if (!mIsOnlyImeSettings) {
+        if (!mShowsOnlyFullImeAndKeyboardList) {
             if (mLanguagePref != null) {
                 String localeName = getLocaleName(getResources());
                 mLanguagePref.setSummary(localeName);
@@ -419,31 +412,33 @@
     private void updateInputMethodPreferenceViews() {
         synchronized (mInputMethodPreferenceList) {
             // Clear existing "InputMethodPreference"s
-            for (final InputMethodPreference imp : mInputMethodPreferenceList) {
-                mKeyboardSettingsCategory.removePreference(imp);
+            for (final InputMethodPreference pref : mInputMethodPreferenceList) {
+                mKeyboardSettingsCategory.removePreference(pref);
             }
             mInputMethodPreferenceList.clear();
-            final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList();
+            final Context context = getActivity();
+            final List<InputMethodInfo> imis = mShowsOnlyFullImeAndKeyboardList
+                    ? mInputMethodSettingValues.getInputMethodList()
+                    : mImm.getEnabledInputMethodList();
             final int N = (imis == null ? 0 : imis.size());
             for (int i = 0; i < N; ++i) {
                 final InputMethodInfo imi = imis.get(i);
-                final InputMethodPreference pref = getInputMethodPreference(imi);
-                pref.setOnImePreferenceChangeListener(mOnImePreferenceChangedListener);
+                final InputMethodPreference pref = new InputMethodPreference(
+                        context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */, this);
                 mInputMethodPreferenceList.add(pref);
             }
-
-            if (!mInputMethodPreferenceList.isEmpty()) {
-                Collections.sort(mInputMethodPreferenceList);
-                for (int i = 0; i < N; ++i) {
-                    mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i));
+            final Collator collator = Collator.getInstance();
+            Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
+                @Override
+                public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
+                    return lhs.compareTo(rhs, collator);
                 }
-            }
-
-            // update views status
-            for (Preference pref : mInputMethodPreferenceList) {
-                if (pref instanceof InputMethodPreference) {
-                    ((InputMethodPreference) pref).updatePreferenceViews();
-                }
+            });
+            for (int i = 0; i < N; ++i) {
+                final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
+                mKeyboardSettingsCategory.addPreference(pref);
+                InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+                pref.updatePreferenceViews();
             }
         }
         updateCurrentImeName();
@@ -456,6 +451,19 @@
                 mInputMethodSettingValues.getInputMethodList(), null);
     }
 
+    @Override
+    public void onSaveInputMethodPreference(final InputMethodPreference pref) {
+        final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
+                == Configuration.KEYBOARD_QWERTY;
+        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
+                mImm.getInputMethodList(), hasHardwareKeyboard);
+        // Update input method settings and preference list.
+        mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
+        for (final InputMethodPreference p : mInputMethodPreferenceList) {
+            p.updatePreferenceViews();
+        }
+    }
+
     private void updateCurrentImeName() {
         final Context context = getActivity();
         if (context == null || mImm == null) return;
@@ -471,26 +479,6 @@
         }
     }
 
-    private InputMethodPreference getInputMethodPreference(InputMethodInfo imi) {
-        final PackageManager pm = getPackageManager();
-        final CharSequence label = imi.loadLabel(pm);
-        // IME settings
-        final Intent intent;
-        final String settingsActivity = imi.getSettingsActivity();
-        if (!TextUtils.isEmpty(settingsActivity)) {
-            intent = new Intent(Intent.ACTION_MAIN);
-            intent.setClassName(imi.getPackageName(), settingsActivity);
-        } else {
-            intent = null;
-        }
-
-        // Add a check box for enabling/disabling IME
-        final InputMethodPreference pref = new InputMethodPreference(this, intent, mImm, imi);
-        pref.setKey(imi.getId());
-        pref.setTitle(label);
-        return pref;
-    }
-
     private void updateInputDevices() {
         updateHardKeyboards();
         updateGameControllers();
@@ -643,7 +631,7 @@
             if (context.getAssets().getLocales().length > 1) {
                 String localeName = getLocaleName(resources);
                 SearchIndexableRaw indexable = new SearchIndexableRaw(context);
-                indexable.key = "phone_language";
+                indexable.key = KEY_PHONE_LANGUAGE;
                 indexable.title = context.getString(R.string.phone_language);
                 indexable.summaryOn = localeName;
                 indexable.summaryOff = localeName;
@@ -681,7 +669,7 @@
             // Current IME.
             String currImeName = immValues.getCurrentInputMethodName(context).toString();
             indexable = new SearchIndexableRaw(context);
-            indexable.key = "current_input_method";
+            indexable.key = KEY_CURRENT_INPUT_METHOD;
             indexable.title = context.getString(R.string.current_input_method);
             indexable.summaryOn = currImeName;
             indexable.summaryOff = currImeName;
diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
index bf3a601..f4e66d2 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
@@ -16,13 +16,12 @@
 
 package com.android.settings.inputmethod;
 
-import com.android.internal.inputmethod.InputMethodUtils;
-import com.android.settings.SettingsPreferenceFragment;
-
 import android.content.ContentResolver;
+import android.content.SharedPreferences;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
 import android.preference.PreferenceScreen;
+import android.preference.TwoStatePreference;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
@@ -30,6 +29,9 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.settings.SettingsPreferenceFragment;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -163,10 +165,10 @@
             final String imiId = imi.getId();
             Preference pref = context.findPreference(imiId);
             if (pref == null) continue;
-            // In the Configure input method screen or in the subtype enabler screen.
-            // pref is instance of CheckBoxPreference in the Configure input method screen.
-            final boolean isImeChecked = (pref instanceof CheckBoxPreference) ?
-                    ((CheckBoxPreference) pref).isChecked()
+            // In the choose input method screen or in the subtype enabler screen,
+            // <code>pref</code> is an instance of TwoStatePreference.
+            final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
+                    ((TwoStatePreference) pref).isChecked()
                     : enabledIMEAndSubtypesMap.containsKey(imiId);
             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
             final boolean systemIme = InputMethodUtils.isSystemIme(imi);
@@ -338,4 +340,15 @@
             }
         }
     }
+
+    static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
+        final String key = pref.getKey();
+        if (pref.isPersistent() || key == null) {
+            return;
+        }
+        final SharedPreferences prefs = pref.getSharedPreferences();
+        if (prefs != null && prefs.contains(key)) {
+            prefs.edit().remove(key).apply();
+        }
+    }
 }
diff --git a/src/com/android/settings/inputmethod/InputMethodPreference.java b/src/com/android/settings/inputmethod/InputMethodPreference.java
old mode 100644
new mode 100755
index 87a0a77..1404875
--- a/src/com/android/settings/inputmethod/InputMethodPreference.java
+++ b/src/com/android/settings/inputmethod/InputMethodPreference.java
@@ -16,304 +16,233 @@
 
 package com.android.settings.inputmethod;
 
-import com.android.internal.inputmethod.InputMethodUtils;
-import com.android.settings.R;
-import com.android.settings.SettingsActivity;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
-
 import android.app.AlertDialog;
-import android.app.Fragment;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
 import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.SwitchPreference;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.ImageView;
-import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.settings.R;
+
 import java.text.Collator;
+import java.util.ArrayList;
 import java.util.List;
 
-// TODO: Make this non-persistent.
-class InputMethodPreference extends CheckBoxPreference {
+/**
+ * Input method preference.
+ *
+ * This preference represents an IME. It is used for two purposes. 1) An instance with a switch
+ * is used to enable or disable the IME. 2) An instance without a switch is used to invoke the
+ * setting activity of the IME.
+ */
+class InputMethodPreference extends SwitchPreference implements OnPreferenceClickListener,
+        OnPreferenceChangeListener {
     private static final String TAG = InputMethodPreference.class.getSimpleName();
-    private final SettingsPreferenceFragment mFragment;
+    private static final String EMPTY_TEXT = "";
+
+    interface onSavePreferenceListener {
+        /**
+         * Called when this preference needs to be saved its state.
+         *
+         * Note that this preference is non-persistent and needs explicitly to be saved its state.
+         * Because changing one IME state may change other IMEs' state, this is a place to update
+         * other IMEs' state as well.
+         *
+         * @param pref This preference.
+         */
+        public void onSaveInputMethodPreference(InputMethodPreference pref);
+    }
+
     private final InputMethodInfo mImi;
-    private final InputMethodManager mImm;
-    private final boolean mIsValidSystemNonAuxAsciiCapableIme;
-    private final Intent mSettingsIntent;
-    private final boolean mIsSystemIme;
-    private final Collator mCollator;
+    private final boolean mHasPriorityInSorting;
+    private final onSavePreferenceListener mOnSaveListener;
+    private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
 
     private AlertDialog mDialog = null;
-    private ImageView mInputMethodSettingsButton;
-    private TextView mTitleText;
-    private TextView mSummaryText;
-    private View mInputMethodPref;
-    private OnPreferenceChangeListener mOnImePreferenceChangeListener;
 
-    private final OnClickListener mPrefOnclickListener = new OnClickListener() {
-        @Override
-        public void onClick(View arg0) {
-            if (!isEnabled()) {
-                return;
-            }
-            if (isChecked()) {
-                setChecked(false, true /* save */);
-            } else {
-                if (mIsSystemIme) {
-                    setChecked(true, true /* save */);
-                } else {
-                    showSecurityWarnDialog(mImi, InputMethodPreference.this);
-                }
-            }
-        }
-    };
-
-    public InputMethodPreference(SettingsPreferenceFragment fragment, Intent settingsIntent,
-            InputMethodManager imm, InputMethodInfo imi) {
-        super(fragment.getActivity());
-        setLayoutResource(R.layout.preference_inputmethod);
-        setWidgetLayoutResource(R.layout.preference_inputmethod_widget);
-        mFragment = fragment;
-        mSettingsIntent = settingsIntent;
-        mImm = imm;
+    /**
+     * A preference entry of an input method.
+     *
+     * @param context The Context this is associated with.
+     * @param imi The {@link InputMethodInfo} of this preference.
+     * @param isImeEnabler true if this preference is the IME enabler that has enable/disable
+     *     switches for all available IMEs, not the list of enabled IMEs.
+     * @param onSaveListener The listener called when this preference has been changed and needs
+     *     to save the state to shared preference.
+     */
+    InputMethodPreference(final Context context, final InputMethodInfo imi,
+            final boolean isImeEnabler, final onSavePreferenceListener onSaveListener) {
+        super(context);
+        setPersistent(false);
         mImi = imi;
-        mIsSystemIme = InputMethodUtils.isSystemIme(imi);
-        mCollator = Collator.getInstance(fragment.getResources().getConfiguration().locale);
-        final Context context = fragment.getActivity();
-        mIsValidSystemNonAuxAsciiCapableIme = InputMethodSettingValuesWrapper
-                .getInstance(context).isValidSystemNonAuxAsciiCapableIme(imi, context);
-        updatePreferenceViews();
+        mOnSaveListener = onSaveListener;
+        if (!isImeEnabler) {
+            // Hide switch widget.
+            setWidgetLayoutResource(0 /* widgetLayoutResId */);
+        }
+        // Disable on/off switch texts.
+        setSwitchTextOn(EMPTY_TEXT);
+        setSwitchTextOff(EMPTY_TEXT);
+        setKey(imi.getId());
+        setTitle(imi.loadLabel(context.getPackageManager()));
+        final String settingsActivity = imi.getSettingsActivity();
+        if (TextUtils.isEmpty(settingsActivity)) {
+            setIntent(null);
+        } else {
+            // Set an intent to invoke settings activity of an input method.
+            final Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClassName(imi.getPackageName(), settingsActivity);
+            setIntent(intent);
+        }
+        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
+        mHasPriorityInSorting = InputMethodUtils.isSystemIme(imi)
+                && mInputMethodSettingValues.isValidSystemNonAuxAsciiCapableIme(imi, context);
+        setOnPreferenceClickListener(this);
+        setOnPreferenceChangeListener(this);
+    }
+
+    private boolean isImeEnabler() {
+        // If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the
+        // switch widget at constructor.
+        return getWidgetLayoutResource() != 0;
     }
 
     @Override
-    protected void onBindView(View view) {
-        super.onBindView(view);
-        mInputMethodPref = view.findViewById(R.id.inputmethod_pref);
-        mInputMethodPref.setOnClickListener(mPrefOnclickListener);
-        mInputMethodSettingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings);
-        mTitleText = (TextView)view.findViewById(android.R.id.title);
-        mSummaryText = (TextView)view.findViewById(android.R.id.summary);
-        final boolean hasSubtypes = mImi.getSubtypeCount() > 1;
-        final String imiId = mImi.getId();
-        if (hasSubtypes) {
-            mInputMethodPref.setOnLongClickListener(new OnLongClickListener() {
-                @Override
-                public boolean onLongClick(View arg0) {
-                    final Bundle bundle = new Bundle();
-                    bundle.putString(android.provider.Settings.EXTRA_INPUT_METHOD_ID, imiId);
-                    startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
-                            0, bundle);
-                    return true;
-                }
-            });
-        }
-
-        if (mSettingsIntent != null) {
-            mInputMethodSettingsButton.setOnClickListener(
-                    new OnClickListener() {
-                        @Override
-                        public void onClick(View arg0) {
-                            try {
-                                mFragment.startActivity(mSettingsIntent);
-                            } catch (ActivityNotFoundException e) {
-                                Log.d(TAG, "IME's Settings Activity Not Found: " + e);
-                                final String msg = mFragment.getString(
-                                        R.string.failed_to_open_app_settings_toast,
-                                        mImi.loadLabel(
-                                                mFragment.getActivity().getPackageManager()));
-                                Toast.makeText(
-                                        mFragment.getActivity(), msg, Toast.LENGTH_LONG).show();
-                            }
-                        }
-                    });
-        }
-        if (hasSubtypes) {
-            final OnLongClickListener listener = new OnLongClickListener() {
-                @Override
-                public boolean onLongClick(View arg0) {
-                    final Bundle bundle = new Bundle();
-                    bundle.putString(android.provider.Settings.EXTRA_INPUT_METHOD_ID, imiId);
-                    startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
-                            0, bundle);
-                    return true;
-                }
-            };
-            mInputMethodSettingsButton.setOnLongClickListener(listener);
-        }
-        if (mSettingsIntent == null) {
-            mInputMethodSettingsButton.setVisibility(View.GONE);
-        }
-        updatePreferenceViews();
-    }
-
-    @Override
-    public void setEnabled(boolean enabled) {
-        super.setEnabled(enabled);
-        updatePreferenceViews();
-    }
-
-    public void updatePreferenceViews() {
-        final boolean isAlwaysChecked =
-                InputMethodSettingValuesWrapper.getInstance(getContext()).isAlwaysCheckedIme(
-                        mImi, getContext());
-        if (isAlwaysChecked) {
-            super.setChecked(true);
-            super.setEnabled(false);
-        } else {
-            super.setEnabled(true);
-        }
-        final boolean checked = isChecked();
-        if (mInputMethodSettingsButton != null) {
-            mInputMethodSettingsButton.setEnabled(checked);
-            mInputMethodSettingsButton.setClickable(checked);
-            mInputMethodSettingsButton.setFocusable(checked);
-            if (!checked) {
-                mInputMethodSettingsButton.setAlpha(Utils.DISABLED_ALPHA);
-            }
-        }
-        if (mTitleText != null) {
-            mTitleText.setEnabled(true);
-        }
-        if (mSummaryText != null) {
-            mSummaryText.setEnabled(checked);
-        }
-        if (mInputMethodPref != null) {
-            mInputMethodPref.setEnabled(true);
-            mInputMethodPref.setLongClickable(checked);
-            final boolean enabled = isEnabled();
-            mInputMethodPref.setOnClickListener(enabled ? mPrefOnclickListener : null);
-            if (!enabled) {
-                mInputMethodPref.setBackgroundColor(0);
-            }
-        }
-        updateSummary();
-    }
-
-    public static boolean startFragment(
-            Fragment fragment, String fragmentClass, int requestCode, Bundle extras) {
-        if (fragment.getActivity() instanceof SettingsActivity) {
-            SettingsActivity sa = (SettingsActivity) fragment.getActivity();
-            sa.startPreferencePanel(fragmentClass, extras, 0, null, fragment, requestCode);
-            return true;
-        } else {
-            Log.w(TAG, "Parent isn't Settings, thus there's no way to launch the "
-                    + "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode
-                    + ")");
+    public boolean onPreferenceChange(final Preference preference, final Object newValue) {
+        // Always returns false to prevent default behavior.
+        // See {@link TwoStatePreference#onClick()}.
+        if (!isImeEnabler()) {
+            // Prevent disabling an IME because this preference is for invoking a settings activity.
             return false;
         }
+        if (isChecked()) {
+            // Disable this IME.
+            setChecked(false);
+            mOnSaveListener.onSaveInputMethodPreference(this);
+            return false;
+        }
+        if (InputMethodUtils.isSystemIme(mImi)) {
+            // Enable a system IME. No need to show a security warning dialog.
+            setChecked(true);
+            mOnSaveListener.onSaveInputMethodPreference(this);
+            return false;
+        }
+        // Enable a 3rd party IME.
+        showSecurityWarnDialog(mImi);
+        return false;
+    }
+
+    @Override
+    public boolean onPreferenceClick(final Preference preference) {
+        // Always returns true to prevent invoking an intent without catching exceptions.
+        // See {@link Preference#performClick(PreferenceScreen)}/
+        if (isImeEnabler()) {
+            // Prevent invoking a settings activity because this preference is for enabling and
+            // disabling an input method.
+            return true;
+        }
+        final Context context = getContext();
+        try {
+            final Intent intent = getIntent();
+            if (intent != null) {
+                // Invoke a settings activity of an input method.
+                context.startActivity(intent);
+            }
+        } catch (final ActivityNotFoundException e) {
+            Log.d(TAG, "IME's Settings Activity Not Found", e);
+            final String message = context.getString(
+                    R.string.failed_to_open_app_settings_toast,
+                    mImi.loadLabel(context.getPackageManager()));
+            Toast.makeText(context, message, Toast.LENGTH_LONG).show();
+        }
+        return true;
+    }
+
+    void updatePreferenceViews() {
+        final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(
+                mImi, getContext());
+        // Only when this preference has a switch and an input method should be always enabled,
+        // this preference should be disabled to prevent accidentally disabling an input method.
+        setEnabled(!(isAlwaysChecked && isImeEnabler()));
+        setChecked(mInputMethodSettingValues.isEnabledImi(mImi));
+        setSummary(getSummaryString());
+    }
+
+    private InputMethodManager getInputMethodManager() {
+        return (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
     }
 
     private String getSummaryString() {
-        final StringBuilder builder = new StringBuilder();
-        final List<InputMethodSubtype> subtypes = mImm.getEnabledInputMethodSubtypeList(mImi, true);
-        for (InputMethodSubtype subtype : subtypes) {
-            if (builder.length() > 0) {
-                builder.append(", ");
-            }
-            final CharSequence subtypeLabel = subtype.getDisplayName(mFragment.getActivity(),
-                    mImi.getPackageName(), mImi.getServiceInfo().applicationInfo);
-            builder.append(subtypeLabel);
+        final Context context = getContext();
+        final InputMethodManager imm = getInputMethodManager();
+        final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(mImi, true);
+        final ArrayList<CharSequence> subtypeLabels = new ArrayList<>();
+        for (final InputMethodSubtype subtype : subtypes) {
+            final CharSequence label = subtype.getDisplayName(
+                  context, mImi.getPackageName(), mImi.getServiceInfo().applicationInfo);
+            subtypeLabels.add(label);
         }
-        return builder.toString();
+        // TODO: A delimiter of subtype labels should be localized.
+        return TextUtils.join(", ", subtypeLabels);
     }
 
-    private void updateSummary() {
-        final String summary = getSummaryString();
-        if (TextUtils.isEmpty(summary)) {
-            return;
-        }
-        setSummary(summary);
-    }
-
-    /**
-     * Sets the checkbox state and optionally saves the settings.
-     * @param checked whether to check the box
-     * @param save whether to save IME settings
-     */
-    private void setChecked(boolean checked, boolean save) {
-        final boolean wasChecked = isChecked();
-        super.setChecked(checked);
-        if (save) {
-            saveImeSettings();
-            if (wasChecked != checked && mOnImePreferenceChangeListener != null) {
-                mOnImePreferenceChangeListener.onPreferenceChange(this, checked);
-            }
-        }
-    }
-
-    public void setOnImePreferenceChangeListener(OnPreferenceChangeListener listener) {
-        mOnImePreferenceChangeListener = listener;
-    }
-
-    private void showSecurityWarnDialog(InputMethodInfo imi, final InputMethodPreference chkPref) {
+    private void showSecurityWarnDialog(final InputMethodInfo imi) {
         if (mDialog != null && mDialog.isShowing()) {
             mDialog.dismiss();
         }
-        mDialog = (new AlertDialog.Builder(mFragment.getActivity()))
-                .setTitle(android.R.string.dialog_alert_title)
-                .setCancelable(true)
-                .setPositiveButton(android.R.string.ok,
-                        new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        chkPref.setChecked(true, true);
-                    }
-                })
-                .setNegativeButton(android.R.string.cancel,
-                        new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                    }
-                })
-                .create();
-        mDialog.setMessage(mFragment.getResources().getString(R.string.ime_security_warning,
-                imi.getServiceInfo().applicationInfo.loadLabel(
-                        mFragment.getActivity().getPackageManager())));
+        final Context context = getContext();
+        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        builder.setCancelable(true /* cancelable */);
+        builder.setTitle(android.R.string.dialog_alert_title);
+        final CharSequence label = imi.getServiceInfo().applicationInfo.loadLabel(
+                context.getPackageManager());
+        builder.setMessage(context.getString(R.string.ime_security_warning, label));
+        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(final DialogInterface dialog, final int which) {
+                // The user confirmed to enable a 3rd party IME.
+                setChecked(true);
+                mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
+                notifyChanged();
+            }
+        });
+        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(final DialogInterface dialog, final int which) {
+                // The user canceled to enable a 3rd party IME.
+                setChecked(false);
+                mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
+                notifyChanged();
+            }
+        });
+        mDialog = builder.create();
         mDialog.show();
     }
 
-    @Override
-    public int compareTo(Preference p) {
-        if (!(p instanceof InputMethodPreference)) {
-            return super.compareTo(p);
-        }
-        final InputMethodPreference imp = (InputMethodPreference) p;
-        final boolean priority0 = mIsSystemIme && mIsValidSystemNonAuxAsciiCapableIme;
-        final boolean priority1 = imp.mIsSystemIme && imp.mIsValidSystemNonAuxAsciiCapableIme;
-        if (priority0 == priority1) {
+    int compareTo(final InputMethodPreference rhs, final Collator collator) {
+        if (mHasPriorityInSorting == rhs.mHasPriorityInSorting) {
             final CharSequence t0 = getTitle();
-            final CharSequence t1 = imp.getTitle();
+            final CharSequence t1 = rhs.getTitle();
             if (TextUtils.isEmpty(t0)) {
                 return 1;
             }
             if (TextUtils.isEmpty(t1)) {
                 return -1;
             }
-            return mCollator.compare(t0.toString(), t1.toString());
+            return collator.compare(t0.toString(), t1.toString());
         }
         // Prefer always checked system IMEs
-        return priority0 ? -1 : 1;
-    }
-
-    private void saveImeSettings() {
-        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
-                mFragment, mFragment.getActivity().getContentResolver(), mImm.getInputMethodList(),
-                mFragment.getResources().getConfiguration().keyboard
-                        == Configuration.KEYBOARD_QWERTY);
+        return mHasPriorityInSorting ? -1 : 1;
     }
 }
diff --git a/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java b/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java
index bf63487..ada476e 100644
--- a/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/src/com/android/settings/inputmethod/InputMethodSettingValuesWrapper.java
@@ -169,7 +169,7 @@
         return count;
     }
 
-    private boolean isEnabledImi(InputMethodInfo imi) {
+    boolean isEnabledImi(InputMethodInfo imi) {
         final List<InputMethodInfo> enabledImis;
         synchronized (mMethodMap) {
             enabledImis = mSettings.getEnabledInputMethodListLocked();