OSDN Git Service

Settings: move high touch sensitivity setting to Display
[android-x86/packages-apps-Settings.git] / src / com / android / settings / inputmethod / InputMethodAndLanguageSettings.java
index dbfa1bc..7c531da 100644 (file)
 
 package com.android.settings.inputmethod;
 
-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 android.app.Activity;
 import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
+import android.content.SharedPreferences;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
 import android.os.Bundle;
 import android.os.Handler;
-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.PreferenceManager;
 import android.preference.PreferenceScreen;
+import android.preference.SwitchPreference;
 import android.provider.Settings;
 import android.provider.Settings.System;
+import android.speech.tts.TtsEngines;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.InputDevice;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.BaseAdapter;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.TextServicesManager;
+
+import com.android.internal.app.LocalePicker;
+import com.android.internal.logging.MetricsLogger;
+import com.android.settings.R;
+import com.android.settings.Settings.KeyboardLayoutPickerActivity;
+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 com.android.settings.voicewakeup.VoiceWakeupSettings;
+import cyanogenmod.hardware.CMHardwareManager;
+import cyanogenmod.providers.CMSettings;
+
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.TreeSet;
 
 public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener,
-        KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener {
+        KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable,
+        InputMethodPreference.OnSavePreferenceListener {
+
+    private static final String TAG = "InputMethodAndLanguageSettings";
 
+    private static final String KEY_SPELL_CHECKERS = "spellcheckers_settings";
     private static final String KEY_PHONE_LANGUAGE = "phone_language";
     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";
+    private static final String KEY_POINTER_SETTINGS_CATEGORY = "pointer_settings_category";
+    private static final String KEY_PREVIOUSLY_ENABLED_SUBTYPES = "previously_enabled_subtypes";
+    private static final String KEY_TOUCHSCREEN_HOVERING = "touchscreen_hovering";
+    private static final String KEY_TRACKPAD_SETTINGS = "gesture_pad_settings";
+    private static final String KEY_STYLUS_GESTURES = "stylus_gestures";
+    private static final String KEY_STYLUS_ICON_ENABLED = "stylus_icon_enabled";
+    private static final String KEY_VOICE_CATEGORY = "voice_category";
+    private static final String KEY_VOICE_WAKEUP = "voice_wakeup";
+
     // false: on ICS or later
     private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
 
@@ -79,33 +113,29 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
 
     private int mDefaultInputMethodSelectorVisibility = 0;
     private ListPreference mShowInputMethodSelectorPref;
+    private SwitchPreference mStylusIconEnabled;
+    private SwitchPreference mTouchscreenHovering;
     private PreferenceCategory mKeyboardSettingsCategory;
     private PreferenceCategory mHardKeyboardCategory;
     private PreferenceCategory mGameControllerCategory;
     private Preference mLanguagePref;
-    private final ArrayList<InputMethodPreference> mInputMethodPreferenceList =
-            new ArrayList<InputMethodPreference>();
-    private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList =
-            new ArrayList<PreferenceScreen>();
+    private PreferenceScreen mStylusGestures;
+    private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
+    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 DevicePolicyManager mDpm;
+    private CMHardwareManager mHardware;
 
-    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
+    protected int getMetricsCategory() {
+        return MetricsLogger.INPUTMETHOD_LANGUAGE;
+    }
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -113,13 +143,19 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
 
         addPreferencesFromResource(R.xml.language_settings);
 
+        final Activity activity = getActivity();
+        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
+
+        mHardware = CMHardwareManager.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 {
@@ -142,46 +178,103 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         mGameControllerCategory = (PreferenceCategory)findPreference(
                 "game_controller_settings_category");
 
+        final Intent startingIntent = activity.getIntent();
         // 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(
+                startingIntent.getAction());
+        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());
+        // Build hard keyboard and game controller preference categories.
+        mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE);
+        updateInputDevices();
+
+        PreferenceCategory pointerSettingsCategory = (PreferenceCategory)
+                        findPreference(KEY_POINTER_SETTINGS_CATEGORY);
+
+        mStylusGestures = (PreferenceScreen) findPreference(KEY_STYLUS_GESTURES);
+        mStylusIconEnabled = (SwitchPreference) findPreference(KEY_STYLUS_ICON_ENABLED);
 
-        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);
+        mTouchscreenHovering = (SwitchPreference) findPreference(KEY_TOUCHSCREEN_HOVERING);
+
+        if (pointerSettingsCategory != null) {
+            if (!getResources().getBoolean(com.android.internal.R.bool.config_stylusGestures)) {
+                pointerSettingsCategory.removePreference(mStylusGestures);
+                pointerSettingsCategory.removePreference(mStylusIconEnabled);
+            }
+
+            if (!mHardware.isSupported(CMHardwareManager.FEATURE_TOUCH_HOVERING)) {
+                pointerSettingsCategory.removePreference(mTouchscreenHovering);
+                mTouchscreenHovering = null;
+            } else {
+                mTouchscreenHovering.setChecked(
+                        mHardware.get(CMHardwareManager.FEATURE_TOUCH_HOVERING));
+            }
+
+            Utils.updatePreferenceToSpecificActivityFromMetaDataOrRemove(getActivity(),
+                            pointerSettingsCategory, KEY_TRACKPAD_SETTINGS);
+            if (pointerSettingsCategory.getPreferenceCount() == 0) {
+                getPreferenceScreen().removePreference(pointerSettingsCategory);
+            }
         }
 
-        // Build hard keyboard and game controller preference categories.
-        mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE);
-        updateInputDevices();
+        // Enable or disable mStatusBarImeSwitcher based on boolean: config_show_cmIMESwitcher
+        boolean showCmImeSwitcher = getResources().getBoolean(
+                com.android.internal.R.bool.config_show_cmIMESwitcher);
+        if (!showCmImeSwitcher) {
+            Preference pref = findPreference(CMSettings.System.STATUS_BAR_IME_SWITCHER);
+            if (pref != null) {
+                getPreferenceScreen().removePreference(pref);
+            }
+        }
 
         // 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) {
-            scp.setFragmentIntent(this, intent);
+        final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
+        if (spellChecker != null) {
+            // Note: KEY_SPELL_CHECKERS preference is marked as persistent="false" in XML.
+            InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(spellChecker);
+            final Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClass(activity, SubSettings.class);
+            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT,
+                    SpellCheckersSettings.class.getName());
+            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
+                    R.string.spellcheckers_settings_title);
+            spellChecker.setIntent(intent);
         }
 
         mHandler = new Handler();
-        mSettingsObserver = new SettingsObserver(mHandler, getActivity());
+        mSettingsObserver = new SettingsObserver(mHandler, activity);
+        mDpm = (DevicePolicyManager) (getActivity().
+                getSystemService(Context.DEVICE_POLICY_SERVICE));
+
+        // If we've launched from the keyboard layout notification, go ahead and just show the
+        // keyboard layout dialog.
+        final InputDeviceIdentifier identifier =
+                startingIntent.getParcelableExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER);
+        if (mShowsOnlyFullImeAndKeyboardList && identifier != null) {
+            showKeyboardLayoutDialog(identifier);
+        }
+
+        if (!Utils.isUserOwner() ||
+                !Utils.isPackageInstalled(getActivity(),
+                        VoiceWakeupSettings.VOICE_WAKEUP_PACKAGE, false)) {
+            PreferenceCategory voiceCategory = (PreferenceCategory)
+                    findPreference(KEY_VOICE_CATEGORY);
+            if (voiceCategory != null) {
+                Preference wakeup = voiceCategory.findPreference(KEY_VOICE_WAKEUP);
+                if (wakeup != null) {
+                    voiceCategory.removePreference(wakeup);
+                }
+            }
+        }
+
     }
 
     private void updateInputMethodSelectorSummary(int value) {
@@ -226,7 +319,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
                                 targetFragment = UserDictionaryList.class;
                             }
                             startFragment(InputMethodAndLanguageSettings.this,
-                                    targetFragment.getCanonicalName(), -1, extras);
+                                    targetFragment.getCanonicalName(), -1, -1, extras);
                             return true;
                         }
                     });
@@ -240,36 +333,28 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         mSettingsObserver.resume();
         mIm.registerInputDeviceListener(this, null);
 
-        if (!mIsOnlyImeSettings) {
+        final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
+        if (spellChecker != null) {
+            final TextServicesManager tsm = (TextServicesManager) getSystemService(
+                    Context.TEXT_SERVICES_MANAGER_SERVICE);
+            final SpellCheckerInfo sci = tsm.getCurrentSpellChecker();
+            spellChecker.setEnabled(sci != null);
+            if (tsm.isSpellCheckerEnabled() && sci != null) {
+                spellChecker.setSummary(sci.loadLabel(getPackageManager()));
+            } else {
+                spellChecker.setSummary(R.string.switch_off_text);
+            }
+        }
+
+        if (mStylusIconEnabled != null) {
+            mStylusIconEnabled.setChecked(Settings.System.getInt(getActivity().getContentResolver(),
+                    Settings.System.STYLUS_ICON_ENABLED, 0) == 1);
+        }
+
+        if (!mShowsOnlyFullImeAndKeyboardList) {
             if (mLanguagePref != null) {
-                Configuration conf = getResources().getConfiguration();
-                String language = conf.locale.getLanguage();
-                String localeString;
-                // TODO: This is not an accurate way to display the locale, as it is
-                // just working around the fact that we support limited dialects
-                // and want to pretend that the language is valid for all locales.
-                // We need a way to support languages that aren't tied to a particular
-                // locale instead of hiding the locale qualifier.
-                if (language.equals("zz")) {
-                    String country = conf.locale.getCountry();
-                    if (country.equals("ZZ")) {
-                        localeString = "[Developer] Accented English (zz_ZZ)";
-                    } else if (country.equals("ZY")) {
-                        localeString = "[Developer] Fake Bi-Directional (zz_ZY)";
-                    } else {
-                        localeString = "";
-                    }
-                } else if (hasOnlyOneLanguageInstance(language,
-                        Resources.getSystem().getAssets().getLocales())) {
-                    localeString = conf.locale.getDisplayLanguage(conf.locale);
-                } else {
-                    localeString = conf.locale.getDisplayName(conf.locale);
-                }
-                if (localeString.length() > 1) {
-                    localeString = Character.toUpperCase(localeString.charAt(0))
-                            + localeString.substring(1);
-                    mLanguagePref.setSummary(localeString);
-                }
+                String localeName = getLocaleName(getActivity());
+                mLanguagePref.setSummary(localeName);
             }
 
             updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
@@ -281,9 +366,9 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         // Hard keyboard
         if (!mHardKeyboardPreferenceList.isEmpty()) {
             for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
-                CheckBoxPreference chkPref = (CheckBoxPreference)
+                SwitchPreference swPref = (SwitchPreference)
                         mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]);
-                chkPref.setChecked(
+                swPref.setChecked(
                         System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0);
             }
         }
@@ -333,46 +418,56 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         if (Utils.isMonkeyRunning()) {
             return false;
         }
-        if (preference instanceof PreferenceScreen) {
+        if (preference == mStylusIconEnabled) {
+            Settings.System.putInt(getActivity().getContentResolver(),
+                    Settings.System.STYLUS_ICON_ENABLED, mStylusIconEnabled.isChecked() ? 1 : 0);
+        } else if (preference == mTouchscreenHovering) {
+            boolean touchHoveringEnable = mTouchscreenHovering.isChecked();
+            CMSettings.Secure.putInt(getActivity().getContentResolver(),
+                    CMSettings.Secure.FEATURE_TOUCH_HOVERING,
+                    touchHoveringEnable ? 1 : 0);
+            return true;
+        } else if (preference instanceof PreferenceScreen) {
             if (preference.getFragment() != null) {
                 // Fragment will be handled correctly by the super class.
             } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) {
                 final InputMethodManager imm = (InputMethodManager)
                         getSystemService(Context.INPUT_METHOD_SERVICE);
-                imm.showInputMethodPicker();
+                imm.showInputMethodPicker(false /* showAuxiliarySubtypes */);
+            }
+        } else if (preference instanceof SwitchPreference) {
+            final SwitchPreference pref = (SwitchPreference) preference;
+            if (pref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
+                System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
+                        pref.isChecked() ? 1 : 0);
+                return true;
             }
-        } else if (preference instanceof CheckBoxPreference) {
-            final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
             if (!mHardKeyboardPreferenceList.isEmpty()) {
                 for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
-                    if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
+                    if (pref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
                         System.putInt(getContentResolver(), sSystemSettingNames[i],
-                                chkPref.isChecked() ? 1 : 0);
+                                pref.isChecked() ? 1 : 0);
                         return true;
                     }
                 }
             }
-            if (chkPref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
-                System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
-                        chkPref.isChecked() ? 1 : 0);
-                return true;
-            }
         }
         return super.onPreferenceTreeClick(preferenceScreen, preference);
     }
 
-    private boolean hasOnlyOneLanguageInstance(String languageCode, String[] locales) {
-        int count = 0;
-        for (String localeCode : locales) {
-            if (localeCode.length() > 2
-                    && localeCode.startsWith(languageCode)) {
-                count++;
-                if (count > 1) {
-                    return false;
-                }
+    private static String getLocaleName(Context context) {
+        // We want to show the same string that the LocalePicker used.
+        // TODO: should this method be in LocalePicker instead?
+        Locale currentLocale = context.getResources().getConfiguration().locale;
+        List<LocalePicker.LocaleInfo> locales = LocalePicker.getAllAssetLocales(context, true);
+        for (LocalePicker.LocaleInfo locale : locales) {
+            if (locale.getLocale().equals(currentLocale)) {
+                return locale.getLabel();
             }
         }
-        return count == 1;
+        // This can't happen as long as the locale was one set by Settings.
+        // Fall back in case a developer is testing an unsupported locale.
+        return currentLocale.getDisplayName(currentLocale);
     }
 
     private void saveInputMethodSelectorVisibility(String value) {
@@ -406,31 +501,37 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
     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();
+            List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
+            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 boolean isAllowedByOrganization = permittedList == null
+                        || permittedList.contains(imi.getPackageName());
+                final InputMethodPreference pref = new InputMethodPreference(
+                        context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */,
+                        isAllowedByOrganization, this);
                 mInputMethodPreferenceList.add(pref);
             }
-
-            if (!mInputMethodPreferenceList.isEmpty()) {
-                Collections.sort(mInputMethodPreferenceList);
-                for (int i = 0; i < N; ++i) {
-                    mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i));
-                }
-            }
-
-            // update views status
-            for (Preference pref : mInputMethodPreferenceList) {
-                if (pref instanceof InputMethodPreference) {
-                    ((InputMethodPreference) pref).updatePreferenceViews();
+            final Collator collator = Collator.getInstance();
+            Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
+                @Override
+                public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
+                    return lhs.compareTo(rhs, collator);
                 }
+            });
+            for (int i = 0; i < N; ++i) {
+                final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
+                mKeyboardSettingsCategory.addPreference(pref);
+                InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+                pref.updatePreferenceViews();
             }
         }
         updateCurrentImeName();
@@ -443,6 +544,74 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
                 mInputMethodSettingValues.getInputMethodList(), null);
     }
 
+    @Override
+    public void onSaveInputMethodPreference(final InputMethodPreference pref) {
+        final InputMethodInfo imi = pref.getInputMethodInfo();
+        if (!pref.isChecked()) {
+            // An IME is being disabled. Save enabled subtypes of the IME to shared preference to be
+            // able to re-enable these subtypes when the IME gets re-enabled.
+            saveEnabledSubtypesOf(imi);
+        }
+        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();
+        if (pref.isChecked()) {
+            // An IME is being enabled. Load the previously enabled subtypes from shared preference
+            // and enable these subtypes.
+            restorePreviouslyEnabledSubtypesOf(imi);
+        }
+        for (final InputMethodPreference p : mInputMethodPreferenceList) {
+            p.updatePreferenceViews();
+        }
+    }
+
+    private void saveEnabledSubtypesOf(final InputMethodInfo imi) {
+        final HashSet<String> enabledSubtypeIdSet = new HashSet<>();
+        final List<InputMethodSubtype> enabledSubtypes = mImm.getEnabledInputMethodSubtypeList(
+                imi, true /* allowsImplicitlySelectedSubtypes */);
+        for (final InputMethodSubtype subtype : enabledSubtypes) {
+            final String subtypeId = Integer.toString(subtype.hashCode());
+            enabledSubtypeIdSet.add(subtypeId);
+        }
+        final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
+                loadPreviouslyEnabledSubtypeIdsMap();
+        final String imiId = imi.getId();
+        imeToEnabledSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
+        savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
+    }
+
+    private void restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi) {
+        final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
+                loadPreviouslyEnabledSubtypeIdsMap();
+        final String imiId = imi.getId();
+        final HashSet<String> enabledSubtypeIdSet = imeToEnabledSubtypeIdsMap.remove(imiId);
+        if (enabledSubtypeIdSet == null) {
+            return;
+        }
+        savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
+        InputMethodAndSubtypeUtil.enableInputMethodSubtypesOf(
+                getContentResolver(), imiId, enabledSubtypeIdSet);
+    }
+
+    private HashMap<String, HashSet<String>> loadPreviouslyEnabledSubtypeIdsMap() {
+        final Context context = getActivity();
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        final String imesAndSubtypesString = prefs.getString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, null);
+        return InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(imesAndSubtypesString);
+    }
+
+    private void savePreviouslyEnabledSubtypeIdsMap(
+            final HashMap<String, HashSet<String>> subtypesMap) {
+        final Context context = getActivity();
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        final String imesAndSubtypesString = InputMethodAndSubtypeUtil
+                .buildInputMethodsAndSubtypesString(subtypesMap);
+        prefs.edit().putString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, imesAndSubtypesString).apply();
+    }
+
     private void updateCurrentImeName() {
         final Context context = getActivity();
         if (context == null || mImm == null) return;
@@ -451,34 +620,13 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
             final CharSequence curIme =
                     mInputMethodSettingValues.getCurrentInputMethodName(context);
             if (!TextUtils.isEmpty(curIme)) {
-                synchronized(this) {
+                synchronized (this) {
                     curPref.setSummary(curIme);
                 }
             }
         }
     }
 
-    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();
@@ -486,35 +634,33 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
 
     private void updateHardKeyboards() {
         mHardKeyboardPreferenceList.clear();
-        if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) {
-            final int[] devices = InputDevice.getDeviceIds();
-            for (int i = 0; i < devices.length; i++) {
-                InputDevice device = InputDevice.getDevice(devices[i]);
-                if (device != null
-                        && !device.isVirtual()
-                        && device.isFullKeyboard()) {
-                    final InputDeviceIdentifier identifier = device.getIdentifier();
-                    final String keyboardLayoutDescriptor =
-                            mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
-                    final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
-                            mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
-
-                    final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
-                    pref.setTitle(device.getName());
-                    if (keyboardLayout != null) {
-                        pref.setSummary(keyboardLayout.toString());
-                    } else {
-                        pref.setSummary(R.string.keyboard_layout_default_label);
-                    }
-                    pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
-                        @Override
-                        public boolean onPreferenceClick(Preference preference) {
-                            showKeyboardLayoutDialog(identifier);
-                            return true;
-                        }
-                    });
-                    mHardKeyboardPreferenceList.add(pref);
+        final int[] devices = InputDevice.getDeviceIds();
+        for (int i = 0; i < devices.length; i++) {
+            InputDevice device = InputDevice.getDevice(devices[i]);
+            if (device != null
+                    && !device.isVirtual()
+                    && device.isFullKeyboard()) {
+                final InputDeviceIdentifier identifier = device.getIdentifier();
+                final String keyboardLayoutDescriptor =
+                    mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
+                final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
+                    mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
+
+                final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
+                pref.setTitle(device.getName());
+                if (keyboardLayout != null) {
+                    pref.setSummary(keyboardLayout.toString());
+                } else {
+                    pref.setSummary(R.string.keyboard_layout_default_label);
                 }
+                pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                    @Override
+                    public boolean onPreferenceClick(Preference preference) {
+                        showKeyboardLayoutDialog(identifier);
+                        return true;
+                    }
+                });
+                mHardKeyboardPreferenceList.add(pref);
             }
         }
 
@@ -541,8 +687,8 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
     }
 
     private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
-        KeyboardLayoutDialogFragment fragment =
-                new KeyboardLayoutDialogFragment(inputDeviceIdentifier);
+        KeyboardLayoutDialogFragment fragment = new KeyboardLayoutDialogFragment(
+                inputDeviceIdentifier);
         fragment.setTargetFragment(this, 0);
         fragment.show(getActivity().getFragmentManager(), "keyboardLayout");
     }
@@ -573,16 +719,16 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         if (haveInputDeviceWithVibrator()) {
             getPreferenceScreen().addPreference(mGameControllerCategory);
 
-            CheckBoxPreference chkPref = (CheckBoxPreference)
+            SwitchPreference pref = (SwitchPreference)
                     mGameControllerCategory.findPreference("vibrate_input_devices");
-            chkPref.setChecked(System.getInt(getContentResolver(),
+            pref.setChecked(System.getInt(getContentResolver(),
                     Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0);
         } else {
             getPreferenceScreen().removePreference(mGameControllerCategory);
         }
     }
 
-    private boolean haveInputDeviceWithVibrator() {
+    private static boolean haveInputDeviceWithVibrator() {
         final int[] devices = InputDevice.getDeviceIds();
         for (int i = 0; i < devices.length; i++) {
             InputDevice device = InputDevice.getDevice(devices[i]);
@@ -617,4 +763,225 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
             mContext.getContentResolver().unregisterContentObserver(this);
         }
     }
+
+    public static void restore(Context context) {
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        final CMHardwareManager hardware = CMHardwareManager.getInstance(context);
+        if (hardware.isSupported(CMHardwareManager.FEATURE_TOUCH_HOVERING)) {
+            final boolean enabled = prefs.getBoolean(KEY_TOUCHSCREEN_HOVERING,
+                    hardware.get(CMHardwareManager.FEATURE_TOUCH_HOVERING));
+            CMSettings.Secure.putInt(context.getContentResolver(),
+                    CMSettings.Secure.FEATURE_TOUCH_HOVERING,
+                    enabled ? 1 : 0);
+        }
+    }
+
+    public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+            new BaseSearchIndexProvider() {
+        @Override
+        public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
+            List<SearchIndexableRaw> indexables = new ArrayList<>();
+
+            final String screenTitle = context.getString(R.string.language_keyboard_settings_title);
+
+            // Locale picker.
+            if (context.getAssets().getLocales().length > 1) {
+                String localeName = getLocaleName(context);
+                SearchIndexableRaw indexable = new SearchIndexableRaw(context);
+                indexable.key = KEY_PHONE_LANGUAGE;
+                indexable.title = context.getString(R.string.phone_language);
+                indexable.summaryOn = localeName;
+                indexable.summaryOff = localeName;
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+            }
+
+            // Spell checker.
+            SearchIndexableRaw indexable = new SearchIndexableRaw(context);
+            indexable.key = KEY_SPELL_CHECKERS;
+            indexable.title = context.getString(R.string.spellcheckers_settings_title);
+            indexable.screenTitle = screenTitle;
+            indexable.keywords = context.getString(R.string.keywords_spell_checker);
+            indexables.add(indexable);
+
+            // User dictionary.
+            if (UserDictionaryList.getUserDictionaryLocalesSet(context) != null) {
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = "user_dict_settings";
+                indexable.title = context.getString(R.string.user_dict_settings_title);
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+            }
+
+            // Keyboard settings.
+            indexable = new SearchIndexableRaw(context);
+            indexable.key = "keyboard_settings";
+            indexable.title = context.getString(R.string.keyboard_settings_category);
+            indexable.screenTitle = screenTitle;
+            indexable.keywords = context.getString(R.string.keywords_keyboard_and_ime);
+            indexables.add(indexable);
+
+            InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper
+                    .getInstance(context);
+            immValues.refreshAllInputMethodAndSubtypes();
+
+            // Current IME.
+            String currImeName = immValues.getCurrentInputMethodName(context).toString();
+            indexable = new SearchIndexableRaw(context);
+            indexable.key = KEY_CURRENT_INPUT_METHOD;
+            indexable.title = context.getString(R.string.current_input_method);
+            indexable.summaryOn = currImeName;
+            indexable.summaryOff = currImeName;
+            indexable.screenTitle = screenTitle;
+            indexables.add(indexable);
+
+            InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(
+                    Context.INPUT_METHOD_SERVICE);
+
+            // All other IMEs.
+            List<InputMethodInfo> inputMethods = immValues.getInputMethodList();
+            final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size());
+            for (int i = 0; i < inputMethodCount; ++i) {
+                InputMethodInfo inputMethod = inputMethods.get(i);
+
+                StringBuilder builder = new StringBuilder();
+                List<InputMethodSubtype> subtypes = inputMethodManager
+                        .getEnabledInputMethodSubtypeList(inputMethod, true);
+                final int subtypeCount = subtypes.size();
+                for (int j = 0; j < subtypeCount; j++) {
+                    InputMethodSubtype subtype = subtypes.get(j);
+                    if (builder.length() > 0) {
+                        builder.append(',');
+                    }
+                    CharSequence subtypeLabel = subtype.getDisplayName(context,
+                            inputMethod.getPackageName(), inputMethod.getServiceInfo()
+                                    .applicationInfo);
+                    builder.append(subtypeLabel);
+                }
+                String summary = builder.toString();
+
+                ServiceInfo serviceInfo = inputMethod.getServiceInfo();
+                ComponentName componentName = new ComponentName(serviceInfo.packageName,
+                        serviceInfo.name);
+
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = componentName.flattenToString();
+                indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString();
+                indexable.summaryOn = summary;
+                indexable.summaryOff = summary;
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+            }
+
+            // Hard keyboards
+            InputManager inputManager = (InputManager) context.getSystemService(
+                    Context.INPUT_SERVICE);
+            boolean hasHardKeyboards = false;
+
+            final int[] devices = InputDevice.getDeviceIds();
+            for (int i = 0; i < devices.length; i++) {
+                InputDevice device = InputDevice.getDevice(devices[i]);
+                if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
+                    continue;
+                }
+
+                hasHardKeyboards = true;
+
+                InputDeviceIdentifier identifier = device.getIdentifier();
+                String keyboardLayoutDescriptor =
+                        inputManager.getCurrentKeyboardLayoutForInputDevice(identifier);
+                KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
+                        inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
+
+                String summary;
+                if (keyboardLayout != null) {
+                    summary = keyboardLayout.toString();
+                } else {
+                    summary = context.getString(R.string.keyboard_layout_default_label);
+                }
+
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = device.getName();
+                indexable.title = device.getName();
+                indexable.summaryOn = summary;
+                indexable.summaryOff = summary;
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+            }
+
+            if (hasHardKeyboards) {
+                // Hard keyboard category.
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = "builtin_keyboard_settings";
+                indexable.title = context.getString(
+                        R.string.builtin_keyboard_settings_title);
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+
+                // Auto replace.
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = "auto_replace";
+                indexable.title = context.getString(R.string.auto_replace);
+                indexable.summaryOn = context.getString(R.string.auto_replace_summary);
+                indexable.summaryOff = context.getString(R.string.auto_replace_summary);
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+
+                // Auto caps.
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = "auto_caps";
+                indexable.title = context.getString(R.string.auto_caps);
+                indexable.summaryOn = context.getString(R.string.auto_caps_summary);
+                indexable.summaryOff = context.getString(R.string.auto_caps_summary);
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+
+                // Auto punctuate.
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = "auto_punctuate";
+                indexable.title = context.getString(R.string.auto_punctuate);
+                indexable.summaryOn = context.getString(R.string.auto_punctuate_summary);
+                indexable.summaryOff = context.getString(R.string.auto_punctuate_summary);
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+            }
+
+            // Text-to-speech.
+            TtsEngines ttsEngines = new TtsEngines(context);
+            if (!ttsEngines.getEngines().isEmpty()) {
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = "tts_settings";
+                indexable.title = context.getString(R.string.tts_settings_title);
+                indexable.screenTitle = screenTitle;
+                indexable.keywords = context.getString(R.string.keywords_text_to_speech_output);
+                indexables.add(indexable);
+            }
+
+            // Pointer settings.
+            indexable = new SearchIndexableRaw(context);
+            indexable.key = "pointer_settings_category";
+            indexable.title = context.getString(R.string.pointer_settings_category);
+            indexable.screenTitle = screenTitle;
+            indexables.add(indexable);
+
+            indexable = new SearchIndexableRaw(context);
+            indexable.key = "pointer_speed";
+            indexable.title = context.getString(R.string.pointer_speed);
+            indexable.screenTitle = screenTitle;
+            indexables.add(indexable);
+
+            // Game controllers.
+            if (haveInputDeviceWithVibrator()) {
+                indexable = new SearchIndexableRaw(context);
+                indexable.key = "vibrate_input_devices";
+                indexable.title = context.getString(R.string.vibrate_input_devices);
+                indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary);
+                indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary);
+                indexable.screenTitle = screenTitle;
+                indexables.add(indexable);
+            }
+
+            return indexables;
+        }
+    };
 }