OSDN Git Service

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