Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.inputmethod;
     18 
     19 import android.app.Activity;
     20 import android.app.Fragment;
     21 import android.app.admin.DevicePolicyManager;
     22 import android.content.ComponentName;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.SharedPreferences;
     27 import android.content.pm.ServiceInfo;
     28 import android.content.res.Configuration;
     29 import android.database.ContentObserver;
     30 import android.hardware.input.InputDeviceIdentifier;
     31 import android.hardware.input.InputManager;
     32 import android.hardware.input.KeyboardLayout;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.preference.ListPreference;
     36 import android.preference.Preference;
     37 import android.preference.Preference.OnPreferenceClickListener;
     38 import android.preference.PreferenceCategory;
     39 import android.preference.PreferenceManager;
     40 import android.preference.PreferenceScreen;
     41 import android.preference.SwitchPreference;
     42 import android.provider.Settings;
     43 import android.provider.Settings.System;
     44 import android.speech.tts.TtsEngines;
     45 import android.text.TextUtils;
     46 import android.view.InputDevice;
     47 import android.view.inputmethod.InputMethodInfo;
     48 import android.view.inputmethod.InputMethodManager;
     49 import android.view.inputmethod.InputMethodSubtype;
     50 import android.view.textservice.SpellCheckerInfo;
     51 import android.view.textservice.TextServicesManager;
     52 
     53 import com.android.internal.app.LocalePicker;
     54 import com.android.internal.logging.MetricsLogger;
     55 import com.android.settings.R;
     56 import com.android.settings.Settings.KeyboardLayoutPickerActivity;
     57 import com.android.settings.SettingsActivity;
     58 import com.android.settings.SettingsPreferenceFragment;
     59 import com.android.settings.SubSettings;
     60 import com.android.settings.UserDictionarySettings;
     61 import com.android.settings.Utils;
     62 import com.android.settings.VoiceInputOutputSettings;
     63 import com.android.settings.search.BaseSearchIndexProvider;
     64 import com.android.settings.search.Indexable;
     65 import com.android.settings.search.SearchIndexableRaw;
     66 
     67 import java.text.Collator;
     68 import java.util.ArrayList;
     69 import java.util.Collections;
     70 import java.util.Comparator;
     71 import java.util.HashMap;
     72 import java.util.HashSet;
     73 import java.util.List;
     74 import java.util.Locale;
     75 import java.util.TreeSet;
     76 
     77 public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
     78         implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener,
     79         KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable,
     80         InputMethodPreference.OnSavePreferenceListener {
     81     private static final String KEY_SPELL_CHECKERS = "spellcheckers_settings";
     82     private static final String KEY_PHONE_LANGUAGE = "phone_language";
     83     private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
     84     private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
     85     private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
     86     private static final String KEY_PREVIOUSLY_ENABLED_SUBTYPES = "previously_enabled_subtypes";
     87     // false: on ICS or later
     88     private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
     89 
     90     private int mDefaultInputMethodSelectorVisibility = 0;
     91     private ListPreference mShowInputMethodSelectorPref;
     92     private PreferenceCategory mKeyboardSettingsCategory;
     93     private PreferenceCategory mHardKeyboardCategory;
     94     private PreferenceCategory mGameControllerCategory;
     95     private Preference mLanguagePref;
     96     private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
     97     private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = new ArrayList<>();
     98     private InputManager mIm;
     99     private InputMethodManager mImm;
    100     private boolean mShowsOnlyFullImeAndKeyboardList;
    101     private Handler mHandler;
    102     private SettingsObserver mSettingsObserver;
    103     private Intent mIntentWaitingForResult;
    104     private InputMethodSettingValuesWrapper mInputMethodSettingValues;
    105     private DevicePolicyManager mDpm;
    106 
    107     @Override
    108     protected int getMetricsCategory() {
    109         return MetricsLogger.INPUTMETHOD_LANGUAGE;
    110     }
    111 
    112     @Override
    113     public void onCreate(Bundle icicle) {
    114         super.onCreate(icicle);
    115 
    116         addPreferencesFromResource(R.xml.language_settings);
    117 
    118         final Activity activity = getActivity();
    119         mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    120         mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
    121 
    122         try {
    123             mDefaultInputMethodSelectorVisibility = Integer.valueOf(
    124                     getString(R.string.input_method_selector_visibility_default_value));
    125         } catch (NumberFormatException e) {
    126         }
    127 
    128         if (activity.getAssets().getLocales().length == 1) {
    129             // No "Select language" pref if there's only one system locale available.
    130             getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
    131         } else {
    132             mLanguagePref = findPreference(KEY_PHONE_LANGUAGE);
    133         }
    134         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
    135             mShowInputMethodSelectorPref = (ListPreference)findPreference(
    136                     KEY_INPUT_METHOD_SELECTOR);
    137             mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
    138             // TODO: Update current input method name on summary
    139             updateInputMethodSelectorSummary(loadInputMethodSelectorVisibility());
    140         }
    141 
    142         new VoiceInputOutputSettings(this).onCreate();
    143 
    144         // Get references to dynamically constructed categories.
    145         mHardKeyboardCategory = (PreferenceCategory)findPreference("hard_keyboard");
    146         mKeyboardSettingsCategory = (PreferenceCategory)findPreference(
    147                 "keyboard_settings_category");
    148         mGameControllerCategory = (PreferenceCategory)findPreference(
    149                 "game_controller_settings_category");
    150 
    151         final Intent startingIntent = activity.getIntent();
    152         // Filter out irrelevant features if invoked from IME settings button.
    153         mShowsOnlyFullImeAndKeyboardList = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
    154                 startingIntent.getAction());
    155         if (mShowsOnlyFullImeAndKeyboardList) {
    156             getPreferenceScreen().removeAll();
    157             getPreferenceScreen().addPreference(mHardKeyboardCategory);
    158             if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
    159                 getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
    160             }
    161             mKeyboardSettingsCategory.removeAll();
    162             getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
    163         }
    164 
    165         // Build hard keyboard and game controller preference categories.
    166         mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE);
    167         updateInputDevices();
    168 
    169         // Spell Checker
    170         final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
    171         if (spellChecker != null) {
    172             // Note: KEY_SPELL_CHECKERS preference is marked as persistent="false" in XML.
    173             InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(spellChecker);
    174             final Intent intent = new Intent(Intent.ACTION_MAIN);
    175             intent.setClass(activity, SubSettings.class);
    176             intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT,
    177                     SpellCheckersSettings.class.getName());
    178             intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
    179                     R.string.spellcheckers_settings_title);
    180             spellChecker.setIntent(intent);
    181         }
    182 
    183         mHandler = new Handler();
    184         mSettingsObserver = new SettingsObserver(mHandler, activity);
    185         mDpm = (DevicePolicyManager) (getActivity().
    186                 getSystemService(Context.DEVICE_POLICY_SERVICE));
    187 
    188         // If we've launched from the keyboard layout notification, go ahead and just show the
    189         // keyboard layout dialog.
    190         final InputDeviceIdentifier identifier =
    191                 startingIntent.getParcelableExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER);
    192         if (mShowsOnlyFullImeAndKeyboardList && identifier != null) {
    193             showKeyboardLayoutDialog(identifier);
    194         }
    195     }
    196 
    197     private void updateInputMethodSelectorSummary(int value) {
    198         String[] inputMethodSelectorTitles = getResources().getStringArray(
    199                 R.array.input_method_selector_titles);
    200         if (inputMethodSelectorTitles.length > value) {
    201             mShowInputMethodSelectorPref.setSummary(inputMethodSelectorTitles[value]);
    202             mShowInputMethodSelectorPref.setValue(String.valueOf(value));
    203         }
    204     }
    205 
    206     private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
    207         final Activity activity = getActivity();
    208         final TreeSet<String> localeSet = UserDictionaryList.getUserDictionaryLocalesSet(activity);
    209         if (null == localeSet) {
    210             // The locale list is null if and only if the user dictionary service is
    211             // not present or disabled. In this case we need to remove the preference.
    212             getPreferenceScreen().removePreference(userDictionaryPreference);
    213         } else {
    214             userDictionaryPreference.setOnPreferenceClickListener(
    215                     new OnPreferenceClickListener() {
    216                         @Override
    217                         public boolean onPreferenceClick(Preference arg0) {
    218                             // Redirect to UserDictionarySettings if the user needs only one
    219                             // language.
    220                             final Bundle extras = new Bundle();
    221                             final Class<? extends Fragment> targetFragment;
    222                             if (localeSet.size() <= 1) {
    223                                 if (!localeSet.isEmpty()) {
    224                                     // If the size of localeList is 0, we don't set the locale
    225                                     // parameter in the extras. This will be interpreted by the
    226                                     // UserDictionarySettings class as meaning
    227                                     // "the current locale". Note that with the current code for
    228                                     // UserDictionaryList#getUserDictionaryLocalesSet()
    229                                     // the locale list always has at least one element, since it
    230                                     // always includes the current locale explicitly.
    231                                     // @see UserDictionaryList.getUserDictionaryLocalesSet().
    232                                     extras.putString("locale", localeSet.first());
    233                                 }
    234                                 targetFragment = UserDictionarySettings.class;
    235                             } else {
    236                                 targetFragment = UserDictionaryList.class;
    237                             }
    238                             startFragment(InputMethodAndLanguageSettings.this,
    239                                     targetFragment.getCanonicalName(), -1, -1, extras);
    240                             return true;
    241                         }
    242                     });
    243         }
    244     }
    245 
    246     @Override
    247     public void onResume() {
    248         super.onResume();
    249 
    250         mSettingsObserver.resume();
    251         mIm.registerInputDeviceListener(this, null);
    252 
    253         final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
    254         if (spellChecker != null) {
    255             final TextServicesManager tsm = (TextServicesManager) getSystemService(
    256                     Context.TEXT_SERVICES_MANAGER_SERVICE);
    257             final SpellCheckerInfo sci = tsm.getCurrentSpellChecker();
    258             spellChecker.setEnabled(sci != null);
    259             if (tsm.isSpellCheckerEnabled() && sci != null) {
    260                 spellChecker.setSummary(sci.loadLabel(getPackageManager()));
    261             } else {
    262                 spellChecker.setSummary(R.string.switch_off_text);
    263             }
    264         }
    265 
    266         if (!mShowsOnlyFullImeAndKeyboardList) {
    267             if (mLanguagePref != null) {
    268                 String localeName = getLocaleName(getActivity());
    269                 mLanguagePref.setSummary(localeName);
    270             }
    271 
    272             updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
    273             if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
    274                 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
    275             }
    276         }
    277 
    278         updateInputDevices();
    279 
    280         // Refresh internal states in mInputMethodSettingValues to keep the latest
    281         // "InputMethodInfo"s and "InputMethodSubtype"s
    282         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
    283         updateInputMethodPreferenceViews();
    284     }
    285 
    286     @Override
    287     public void onPause() {
    288         super.onPause();
    289 
    290         mIm.unregisterInputDeviceListener(this);
    291         mSettingsObserver.pause();
    292 
    293         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
    294             mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null);
    295         }
    296         // TODO: Consolidate the logic to InputMethodSettingsWrapper
    297         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
    298                 this, getContentResolver(), mInputMethodSettingValues.getInputMethodList(),
    299                 !mHardKeyboardPreferenceList.isEmpty());
    300     }
    301 
    302     @Override
    303     public void onInputDeviceAdded(int deviceId) {
    304         updateInputDevices();
    305     }
    306 
    307     @Override
    308     public void onInputDeviceChanged(int deviceId) {
    309         updateInputDevices();
    310     }
    311 
    312     @Override
    313     public void onInputDeviceRemoved(int deviceId) {
    314         updateInputDevices();
    315     }
    316 
    317     @Override
    318     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
    319         // Input Method stuff
    320         if (Utils.isMonkeyRunning()) {
    321             return false;
    322         }
    323         if (preference instanceof PreferenceScreen) {
    324             if (preference.getFragment() != null) {
    325                 // Fragment will be handled correctly by the super class.
    326             } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) {
    327                 final InputMethodManager imm = (InputMethodManager)
    328                         getSystemService(Context.INPUT_METHOD_SERVICE);
    329                 imm.showInputMethodPicker(false /* showAuxiliarySubtypes */);
    330             }
    331         } else if (preference instanceof SwitchPreference) {
    332             final SwitchPreference pref = (SwitchPreference) preference;
    333             if (pref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
    334                 System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
    335                         pref.isChecked() ? 1 : 0);
    336                 return true;
    337             }
    338         }
    339         return super.onPreferenceTreeClick(preferenceScreen, preference);
    340     }
    341 
    342     private static String getLocaleName(Context context) {
    343         // We want to show the same string that the LocalePicker used.
    344         // TODO: should this method be in LocalePicker instead?
    345         Locale currentLocale = context.getResources().getConfiguration().locale;
    346         List<LocalePicker.LocaleInfo> locales = LocalePicker.getAllAssetLocales(context, true);
    347         for (LocalePicker.LocaleInfo locale : locales) {
    348             if (locale.getLocale().equals(currentLocale)) {
    349                 return locale.getLabel();
    350             }
    351         }
    352         // This can't happen as long as the locale was one set by Settings.
    353         // Fall back in case a developer is testing an unsupported locale.
    354         return currentLocale.getDisplayName(currentLocale);
    355     }
    356 
    357     private void saveInputMethodSelectorVisibility(String value) {
    358         try {
    359             int intValue = Integer.valueOf(value);
    360             Settings.Secure.putInt(getContentResolver(),
    361                     Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue);
    362             updateInputMethodSelectorSummary(intValue);
    363         } catch(NumberFormatException e) {
    364         }
    365     }
    366 
    367     private int loadInputMethodSelectorVisibility() {
    368         return Settings.Secure.getInt(getContentResolver(),
    369                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
    370                 mDefaultInputMethodSelectorVisibility);
    371     }
    372 
    373     @Override
    374     public boolean onPreferenceChange(Preference preference, Object value) {
    375         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
    376             if (preference == mShowInputMethodSelectorPref) {
    377                 if (value instanceof String) {
    378                     saveInputMethodSelectorVisibility((String)value);
    379                 }
    380             }
    381         }
    382         return false;
    383     }
    384 
    385     private void updateInputMethodPreferenceViews() {
    386         synchronized (mInputMethodPreferenceList) {
    387             // Clear existing "InputMethodPreference"s
    388             for (final InputMethodPreference pref : mInputMethodPreferenceList) {
    389                 mKeyboardSettingsCategory.removePreference(pref);
    390             }
    391             mInputMethodPreferenceList.clear();
    392             List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
    393             final Context context = getActivity();
    394             final List<InputMethodInfo> imis = mShowsOnlyFullImeAndKeyboardList
    395                     ? mInputMethodSettingValues.getInputMethodList()
    396                     : mImm.getEnabledInputMethodList();
    397             final int N = (imis == null ? 0 : imis.size());
    398             for (int i = 0; i < N; ++i) {
    399                 final InputMethodInfo imi = imis.get(i);
    400                 final boolean isAllowedByOrganization = permittedList == null
    401                         || permittedList.contains(imi.getPackageName());
    402                 final InputMethodPreference pref = new InputMethodPreference(
    403                         context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */,
    404                         isAllowedByOrganization, this);
    405                 mInputMethodPreferenceList.add(pref);
    406             }
    407             final Collator collator = Collator.getInstance();
    408             Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
    409                 @Override
    410                 public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
    411                     return lhs.compareTo(rhs, collator);
    412                 }
    413             });
    414             for (int i = 0; i < N; ++i) {
    415                 final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
    416                 mKeyboardSettingsCategory.addPreference(pref);
    417                 InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
    418                 pref.updatePreferenceViews();
    419             }
    420         }
    421         updateCurrentImeName();
    422         // TODO: Consolidate the logic with InputMethodSettingsWrapper
    423         // CAVEAT: The preference class here does not know about the default value - that is
    424         // managed by the Input Method Manager Service, so in this case it could save the wrong
    425         // value. Hence we must update the checkboxes here.
    426         InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
    427                 this, getContentResolver(),
    428                 mInputMethodSettingValues.getInputMethodList(), null);
    429     }
    430 
    431     @Override
    432     public void onSaveInputMethodPreference(final InputMethodPreference pref) {
    433         final InputMethodInfo imi = pref.getInputMethodInfo();
    434         if (!pref.isChecked()) {
    435             // An IME is being disabled. Save enabled subtypes of the IME to shared preference to be
    436             // able to re-enable these subtypes when the IME gets re-enabled.
    437             saveEnabledSubtypesOf(imi);
    438         }
    439         final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
    440                 == Configuration.KEYBOARD_QWERTY;
    441         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
    442                 mImm.getInputMethodList(), hasHardwareKeyboard);
    443         // Update input method settings and preference list.
    444         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
    445         if (pref.isChecked()) {
    446             // An IME is being enabled. Load the previously enabled subtypes from shared preference
    447             // and enable these subtypes.
    448             restorePreviouslyEnabledSubtypesOf(imi);
    449         }
    450         for (final InputMethodPreference p : mInputMethodPreferenceList) {
    451             p.updatePreferenceViews();
    452         }
    453     }
    454 
    455     private void saveEnabledSubtypesOf(final InputMethodInfo imi) {
    456         final HashSet<String> enabledSubtypeIdSet = new HashSet<>();
    457         final List<InputMethodSubtype> enabledSubtypes = mImm.getEnabledInputMethodSubtypeList(
    458                 imi, true /* allowsImplicitlySelectedSubtypes */);
    459         for (final InputMethodSubtype subtype : enabledSubtypes) {
    460             final String subtypeId = Integer.toString(subtype.hashCode());
    461             enabledSubtypeIdSet.add(subtypeId);
    462         }
    463         final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
    464                 loadPreviouslyEnabledSubtypeIdsMap();
    465         final String imiId = imi.getId();
    466         imeToEnabledSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
    467         savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
    468     }
    469 
    470     private void restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi) {
    471         final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
    472                 loadPreviouslyEnabledSubtypeIdsMap();
    473         final String imiId = imi.getId();
    474         final HashSet<String> enabledSubtypeIdSet = imeToEnabledSubtypeIdsMap.remove(imiId);
    475         if (enabledSubtypeIdSet == null) {
    476             return;
    477         }
    478         savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
    479         InputMethodAndSubtypeUtil.enableInputMethodSubtypesOf(
    480                 getContentResolver(), imiId, enabledSubtypeIdSet);
    481     }
    482 
    483     private HashMap<String, HashSet<String>> loadPreviouslyEnabledSubtypeIdsMap() {
    484         final Context context = getActivity();
    485         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    486         final String imesAndSubtypesString = prefs.getString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, null);
    487         return InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(imesAndSubtypesString);
    488     }
    489 
    490     private void savePreviouslyEnabledSubtypeIdsMap(
    491             final HashMap<String, HashSet<String>> subtypesMap) {
    492         final Context context = getActivity();
    493         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    494         final String imesAndSubtypesString = InputMethodAndSubtypeUtil
    495                 .buildInputMethodsAndSubtypesString(subtypesMap);
    496         prefs.edit().putString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, imesAndSubtypesString).apply();
    497     }
    498 
    499     private void updateCurrentImeName() {
    500         final Context context = getActivity();
    501         if (context == null || mImm == null) return;
    502         final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD);
    503         if (curPref != null) {
    504             final CharSequence curIme =
    505                     mInputMethodSettingValues.getCurrentInputMethodName(context);
    506             if (!TextUtils.isEmpty(curIme)) {
    507                 synchronized (this) {
    508                     curPref.setSummary(curIme);
    509                 }
    510             }
    511         }
    512     }
    513 
    514     private void updateInputDevices() {
    515         updateHardKeyboards();
    516         updateGameControllers();
    517     }
    518 
    519     private void updateHardKeyboards() {
    520         mHardKeyboardPreferenceList.clear();
    521         final int[] devices = InputDevice.getDeviceIds();
    522         for (int i = 0; i < devices.length; i++) {
    523             InputDevice device = InputDevice.getDevice(devices[i]);
    524             if (device != null
    525                     && !device.isVirtual()
    526                     && device.isFullKeyboard()) {
    527                 final InputDeviceIdentifier identifier = device.getIdentifier();
    528                 final String keyboardLayoutDescriptor =
    529                     mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
    530                 final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
    531                     mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
    532 
    533                 final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
    534                 pref.setTitle(device.getName());
    535                 if (keyboardLayout != null) {
    536                     pref.setSummary(keyboardLayout.toString());
    537                 } else {
    538                     pref.setSummary(R.string.keyboard_layout_default_label);
    539                 }
    540                 pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
    541                     @Override
    542                     public boolean onPreferenceClick(Preference preference) {
    543                         showKeyboardLayoutDialog(identifier);
    544                         return true;
    545                     }
    546                 });
    547                 mHardKeyboardPreferenceList.add(pref);
    548             }
    549         }
    550 
    551         if (!mHardKeyboardPreferenceList.isEmpty()) {
    552             for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) {
    553                 final Preference pref = mHardKeyboardCategory.getPreference(i);
    554                 if (pref.getOrder() < 1000) {
    555                     mHardKeyboardCategory.removePreference(pref);
    556                 }
    557             }
    558 
    559             Collections.sort(mHardKeyboardPreferenceList);
    560             final int count = mHardKeyboardPreferenceList.size();
    561             for (int i = 0; i < count; i++) {
    562                 final Preference pref = mHardKeyboardPreferenceList.get(i);
    563                 pref.setOrder(i);
    564                 mHardKeyboardCategory.addPreference(pref);
    565             }
    566 
    567             getPreferenceScreen().addPreference(mHardKeyboardCategory);
    568         } else {
    569             getPreferenceScreen().removePreference(mHardKeyboardCategory);
    570         }
    571     }
    572 
    573     private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
    574         KeyboardLayoutDialogFragment fragment = new KeyboardLayoutDialogFragment(
    575                 inputDeviceIdentifier);
    576         fragment.setTargetFragment(this, 0);
    577         fragment.show(getActivity().getFragmentManager(), "keyboardLayout");
    578     }
    579 
    580     @Override
    581     public void onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier) {
    582         final Intent intent = new Intent(Intent.ACTION_MAIN);
    583         intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class);
    584         intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER,
    585                 inputDeviceIdentifier);
    586         mIntentWaitingForResult = intent;
    587         startActivityForResult(intent, 0);
    588     }
    589 
    590     @Override
    591     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    592         super.onActivityResult(requestCode, resultCode, data);
    593 
    594         if (mIntentWaitingForResult != null) {
    595             InputDeviceIdentifier inputDeviceIdentifier = mIntentWaitingForResult
    596                     .getParcelableExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER);
    597             mIntentWaitingForResult = null;
    598             showKeyboardLayoutDialog(inputDeviceIdentifier);
    599         }
    600     }
    601 
    602     private void updateGameControllers() {
    603         if (haveInputDeviceWithVibrator()) {
    604             getPreferenceScreen().addPreference(mGameControllerCategory);
    605 
    606             SwitchPreference pref = (SwitchPreference)
    607                     mGameControllerCategory.findPreference("vibrate_input_devices");
    608             pref.setChecked(System.getInt(getContentResolver(),
    609                     Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0);
    610         } else {
    611             getPreferenceScreen().removePreference(mGameControllerCategory);
    612         }
    613     }
    614 
    615     private static boolean haveInputDeviceWithVibrator() {
    616         final int[] devices = InputDevice.getDeviceIds();
    617         for (int i = 0; i < devices.length; i++) {
    618             InputDevice device = InputDevice.getDevice(devices[i]);
    619             if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) {
    620                 return true;
    621             }
    622         }
    623         return false;
    624     }
    625 
    626     private class SettingsObserver extends ContentObserver {
    627         private Context mContext;
    628 
    629         public SettingsObserver(Handler handler, Context context) {
    630             super(handler);
    631             mContext = context;
    632         }
    633 
    634         @Override public void onChange(boolean selfChange) {
    635             updateCurrentImeName();
    636         }
    637 
    638         public void resume() {
    639             final ContentResolver cr = mContext.getContentResolver();
    640             cr.registerContentObserver(
    641                     Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
    642             cr.registerContentObserver(Settings.Secure.getUriFor(
    643                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
    644         }
    645 
    646         public void pause() {
    647             mContext.getContentResolver().unregisterContentObserver(this);
    648         }
    649     }
    650 
    651     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
    652             new BaseSearchIndexProvider() {
    653         @Override
    654         public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
    655             List<SearchIndexableRaw> indexables = new ArrayList<>();
    656 
    657             final String screenTitle = context.getString(R.string.language_keyboard_settings_title);
    658 
    659             // Locale picker.
    660             if (context.getAssets().getLocales().length > 1) {
    661                 String localeName = getLocaleName(context);
    662                 SearchIndexableRaw indexable = new SearchIndexableRaw(context);
    663                 indexable.key = KEY_PHONE_LANGUAGE;
    664                 indexable.title = context.getString(R.string.phone_language);
    665                 indexable.summaryOn = localeName;
    666                 indexable.summaryOff = localeName;
    667                 indexable.screenTitle = screenTitle;
    668                 indexables.add(indexable);
    669             }
    670 
    671             // Spell checker.
    672             SearchIndexableRaw indexable = new SearchIndexableRaw(context);
    673             indexable.key = KEY_SPELL_CHECKERS;
    674             indexable.title = context.getString(R.string.spellcheckers_settings_title);
    675             indexable.screenTitle = screenTitle;
    676             indexable.keywords = context.getString(R.string.keywords_spell_checker);
    677             indexables.add(indexable);
    678 
    679             // User dictionary.
    680             if (UserDictionaryList.getUserDictionaryLocalesSet(context) != null) {
    681                 indexable = new SearchIndexableRaw(context);
    682                 indexable.key = "user_dict_settings";
    683                 indexable.title = context.getString(R.string.user_dict_settings_title);
    684                 indexable.screenTitle = screenTitle;
    685                 indexables.add(indexable);
    686             }
    687 
    688             // Keyboard settings.
    689             indexable = new SearchIndexableRaw(context);
    690             indexable.key = "keyboard_settings";
    691             indexable.title = context.getString(R.string.keyboard_settings_category);
    692             indexable.screenTitle = screenTitle;
    693             indexable.keywords = context.getString(R.string.keywords_keyboard_and_ime);
    694             indexables.add(indexable);
    695 
    696             InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper
    697                     .getInstance(context);
    698             immValues.refreshAllInputMethodAndSubtypes();
    699 
    700             // Current IME.
    701             String currImeName = immValues.getCurrentInputMethodName(context).toString();
    702             indexable = new SearchIndexableRaw(context);
    703             indexable.key = KEY_CURRENT_INPUT_METHOD;
    704             indexable.title = context.getString(R.string.current_input_method);
    705             indexable.summaryOn = currImeName;
    706             indexable.summaryOff = currImeName;
    707             indexable.screenTitle = screenTitle;
    708             indexables.add(indexable);
    709 
    710             InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(
    711                     Context.INPUT_METHOD_SERVICE);
    712 
    713             // All other IMEs.
    714             List<InputMethodInfo> inputMethods = immValues.getInputMethodList();
    715             final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size());
    716             for (int i = 0; i < inputMethodCount; ++i) {
    717                 InputMethodInfo inputMethod = inputMethods.get(i);
    718 
    719                 StringBuilder builder = new StringBuilder();
    720                 List<InputMethodSubtype> subtypes = inputMethodManager
    721                         .getEnabledInputMethodSubtypeList(inputMethod, true);
    722                 final int subtypeCount = subtypes.size();
    723                 for (int j = 0; j < subtypeCount; j++) {
    724                     InputMethodSubtype subtype = subtypes.get(j);
    725                     if (builder.length() > 0) {
    726                         builder.append(',');
    727                     }
    728                     CharSequence subtypeLabel = subtype.getDisplayName(context,
    729                             inputMethod.getPackageName(), inputMethod.getServiceInfo()
    730                                     .applicationInfo);
    731                     builder.append(subtypeLabel);
    732                 }
    733                 String summary = builder.toString();
    734 
    735                 ServiceInfo serviceInfo = inputMethod.getServiceInfo();
    736                 ComponentName componentName = new ComponentName(serviceInfo.packageName,
    737                         serviceInfo.name);
    738 
    739                 indexable = new SearchIndexableRaw(context);
    740                 indexable.key = componentName.flattenToString();
    741                 indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString();
    742                 indexable.summaryOn = summary;
    743                 indexable.summaryOff = summary;
    744                 indexable.screenTitle = screenTitle;
    745                 indexables.add(indexable);
    746             }
    747 
    748             // Hard keyboards
    749             InputManager inputManager = (InputManager) context.getSystemService(
    750                     Context.INPUT_SERVICE);
    751             boolean hasHardKeyboards = false;
    752 
    753             final int[] devices = InputDevice.getDeviceIds();
    754             for (int i = 0; i < devices.length; i++) {
    755                 InputDevice device = InputDevice.getDevice(devices[i]);
    756                 if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
    757                     continue;
    758                 }
    759 
    760                 hasHardKeyboards = true;
    761 
    762                 InputDeviceIdentifier identifier = device.getIdentifier();
    763                 String keyboardLayoutDescriptor =
    764                         inputManager.getCurrentKeyboardLayoutForInputDevice(identifier);
    765                 KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
    766                         inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
    767 
    768                 String summary;
    769                 if (keyboardLayout != null) {
    770                     summary = keyboardLayout.toString();
    771                 } else {
    772                     summary = context.getString(R.string.keyboard_layout_default_label);
    773                 }
    774 
    775                 indexable = new SearchIndexableRaw(context);
    776                 indexable.key = device.getName();
    777                 indexable.title = device.getName();
    778                 indexable.summaryOn = summary;
    779                 indexable.summaryOff = summary;
    780                 indexable.screenTitle = screenTitle;
    781                 indexables.add(indexable);
    782             }
    783 
    784             if (hasHardKeyboards) {
    785                 // Hard keyboard category.
    786                 indexable = new SearchIndexableRaw(context);
    787                 indexable.key = "builtin_keyboard_settings";
    788                 indexable.title = context.getString(
    789                         R.string.builtin_keyboard_settings_title);
    790                 indexable.screenTitle = screenTitle;
    791                 indexables.add(indexable);
    792             }
    793 
    794             // Text-to-speech.
    795             TtsEngines ttsEngines = new TtsEngines(context);
    796             if (!ttsEngines.getEngines().isEmpty()) {
    797                 indexable = new SearchIndexableRaw(context);
    798                 indexable.key = "tts_settings";
    799                 indexable.title = context.getString(R.string.tts_settings_title);
    800                 indexable.screenTitle = screenTitle;
    801                 indexable.keywords = context.getString(R.string.keywords_text_to_speech_output);
    802                 indexables.add(indexable);
    803             }
    804 
    805             // Pointer settings.
    806             indexable = new SearchIndexableRaw(context);
    807             indexable.key = "pointer_settings_category";
    808             indexable.title = context.getString(R.string.pointer_settings_category);
    809             indexable.screenTitle = screenTitle;
    810             indexables.add(indexable);
    811 
    812             indexable = new SearchIndexableRaw(context);
    813             indexable.key = "pointer_speed";
    814             indexable.title = context.getString(R.string.pointer_speed);
    815             indexable.screenTitle = screenTitle;
    816             indexables.add(indexable);
    817 
    818             // Game controllers.
    819             if (haveInputDeviceWithVibrator()) {
    820                 indexable = new SearchIndexableRaw(context);
    821                 indexable.key = "vibrate_input_devices";
    822                 indexable.title = context.getString(R.string.vibrate_input_devices);
    823                 indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary);
    824                 indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary);
    825                 indexable.screenTitle = screenTitle;
    826                 indexables.add(indexable);
    827             }
    828 
    829             return indexables;
    830         }
    831     };
    832 }
    833