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