OSDN Git Service

Merge "Fix crash in settings -> apps after adding profile" into mnc-dev
[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.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             if (tsm.isSpellCheckerEnabled()) {
258                 final SpellCheckerInfo sci = tsm.getCurrentSpellChecker();
259                 spellChecker.setSummary(sci.loadLabel(getPackageManager()));
260             } else {
261                 spellChecker.setSummary(R.string.switch_off_text);
262             }
263         }
264
265         if (!mShowsOnlyFullImeAndKeyboardList) {
266             if (mLanguagePref != null) {
267                 String localeName = getLocaleName(getActivity());
268                 mLanguagePref.setSummary(localeName);
269             }
270
271             updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
272             if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
273                 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
274             }
275         }
276
277         updateInputDevices();
278
279         // Refresh internal states in mInputMethodSettingValues to keep the latest
280         // "InputMethodInfo"s and "InputMethodSubtype"s
281         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
282         updateInputMethodPreferenceViews();
283     }
284
285     @Override
286     public void onPause() {
287         super.onPause();
288
289         mIm.unregisterInputDeviceListener(this);
290         mSettingsObserver.pause();
291
292         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
293             mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null);
294         }
295         // TODO: Consolidate the logic to InputMethodSettingsWrapper
296         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
297                 this, getContentResolver(), mInputMethodSettingValues.getInputMethodList(),
298                 !mHardKeyboardPreferenceList.isEmpty());
299     }
300
301     @Override
302     public void onInputDeviceAdded(int deviceId) {
303         updateInputDevices();
304     }
305
306     @Override
307     public void onInputDeviceChanged(int deviceId) {
308         updateInputDevices();
309     }
310
311     @Override
312     public void onInputDeviceRemoved(int deviceId) {
313         updateInputDevices();
314     }
315
316     @Override
317     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
318         // Input Method stuff
319         if (Utils.isMonkeyRunning()) {
320             return false;
321         }
322         if (preference instanceof PreferenceScreen) {
323             if (preference.getFragment() != null) {
324                 // Fragment will be handled correctly by the super class.
325             } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) {
326                 final InputMethodManager imm = (InputMethodManager)
327                         getSystemService(Context.INPUT_METHOD_SERVICE);
328                 imm.showInputMethodPicker(false /* showAuxiliarySubtypes */);
329             }
330         } else if (preference instanceof SwitchPreference) {
331             final SwitchPreference pref = (SwitchPreference) preference;
332             if (pref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
333                 System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
334                         pref.isChecked() ? 1 : 0);
335                 return true;
336             }
337         }
338         return super.onPreferenceTreeClick(preferenceScreen, preference);
339     }
340
341     private static String getLocaleName(Context context) {
342         // We want to show the same string that the LocalePicker used.
343         // TODO: should this method be in LocalePicker instead?
344         Locale currentLocale = context.getResources().getConfiguration().locale;
345         List<LocalePicker.LocaleInfo> locales = LocalePicker.getAllAssetLocales(context, true);
346         for (LocalePicker.LocaleInfo locale : locales) {
347             if (locale.getLocale().equals(currentLocale)) {
348                 return locale.getLabel();
349             }
350         }
351         // This can't happen as long as the locale was one set by Settings.
352         // Fall back in case a developer is testing an unsupported locale.
353         return currentLocale.getDisplayName(currentLocale);
354     }
355
356     private void saveInputMethodSelectorVisibility(String value) {
357         try {
358             int intValue = Integer.valueOf(value);
359             Settings.Secure.putInt(getContentResolver(),
360                     Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue);
361             updateInputMethodSelectorSummary(intValue);
362         } catch(NumberFormatException e) {
363         }
364     }
365
366     private int loadInputMethodSelectorVisibility() {
367         return Settings.Secure.getInt(getContentResolver(),
368                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
369                 mDefaultInputMethodSelectorVisibility);
370     }
371
372     @Override
373     public boolean onPreferenceChange(Preference preference, Object value) {
374         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
375             if (preference == mShowInputMethodSelectorPref) {
376                 if (value instanceof String) {
377                     saveInputMethodSelectorVisibility((String)value);
378                 }
379             }
380         }
381         return false;
382     }
383
384     private void updateInputMethodPreferenceViews() {
385         synchronized (mInputMethodPreferenceList) {
386             // Clear existing "InputMethodPreference"s
387             for (final InputMethodPreference pref : mInputMethodPreferenceList) {
388                 mKeyboardSettingsCategory.removePreference(pref);
389             }
390             mInputMethodPreferenceList.clear();
391             List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser();
392             final Context context = getActivity();
393             final List<InputMethodInfo> imis = mShowsOnlyFullImeAndKeyboardList
394                     ? mInputMethodSettingValues.getInputMethodList()
395                     : mImm.getEnabledInputMethodList();
396             final int N = (imis == null ? 0 : imis.size());
397             for (int i = 0; i < N; ++i) {
398                 final InputMethodInfo imi = imis.get(i);
399                 final boolean isAllowedByOrganization = permittedList == null
400                         || permittedList.contains(imi.getPackageName());
401                 final InputMethodPreference pref = new InputMethodPreference(
402                         context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */,
403                         isAllowedByOrganization, this);
404                 mInputMethodPreferenceList.add(pref);
405             }
406             final Collator collator = Collator.getInstance();
407             Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
408                 @Override
409                 public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
410                     return lhs.compareTo(rhs, collator);
411                 }
412             });
413             for (int i = 0; i < N; ++i) {
414                 final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
415                 mKeyboardSettingsCategory.addPreference(pref);
416                 InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
417                 pref.updatePreferenceViews();
418             }
419         }
420         updateCurrentImeName();
421         // TODO: Consolidate the logic with InputMethodSettingsWrapper
422         // CAVEAT: The preference class here does not know about the default value - that is
423         // managed by the Input Method Manager Service, so in this case it could save the wrong
424         // value. Hence we must update the checkboxes here.
425         InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
426                 this, getContentResolver(),
427                 mInputMethodSettingValues.getInputMethodList(), null);
428     }
429
430     @Override
431     public void onSaveInputMethodPreference(final InputMethodPreference pref) {
432         final InputMethodInfo imi = pref.getInputMethodInfo();
433         if (!pref.isChecked()) {
434             // An IME is being disabled. Save enabled subtypes of the IME to shared preference to be
435             // able to re-enable these subtypes when the IME gets re-enabled.
436             saveEnabledSubtypesOf(imi);
437         }
438         final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
439                 == Configuration.KEYBOARD_QWERTY;
440         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
441                 mImm.getInputMethodList(), hasHardwareKeyboard);
442         // Update input method settings and preference list.
443         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
444         if (pref.isChecked()) {
445             // An IME is being enabled. Load the previously enabled subtypes from shared preference
446             // and enable these subtypes.
447             restorePreviouslyEnabledSubtypesOf(imi);
448         }
449         for (final InputMethodPreference p : mInputMethodPreferenceList) {
450             p.updatePreferenceViews();
451         }
452     }
453
454     private void saveEnabledSubtypesOf(final InputMethodInfo imi) {
455         final HashSet<String> enabledSubtypeIdSet = new HashSet<>();
456         final List<InputMethodSubtype> enabledSubtypes = mImm.getEnabledInputMethodSubtypeList(
457                 imi, true /* allowsImplicitlySelectedSubtypes */);
458         for (final InputMethodSubtype subtype : enabledSubtypes) {
459             final String subtypeId = Integer.toString(subtype.hashCode());
460             enabledSubtypeIdSet.add(subtypeId);
461         }
462         final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
463                 loadPreviouslyEnabledSubtypeIdsMap();
464         final String imiId = imi.getId();
465         imeToEnabledSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
466         savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
467     }
468
469     private void restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi) {
470         final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
471                 loadPreviouslyEnabledSubtypeIdsMap();
472         final String imiId = imi.getId();
473         final HashSet<String> enabledSubtypeIdSet = imeToEnabledSubtypeIdsMap.remove(imiId);
474         if (enabledSubtypeIdSet == null) {
475             return;
476         }
477         savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
478         InputMethodAndSubtypeUtil.enableInputMethodSubtypesOf(
479                 getContentResolver(), imiId, enabledSubtypeIdSet);
480     }
481
482     private HashMap<String, HashSet<String>> loadPreviouslyEnabledSubtypeIdsMap() {
483         final Context context = getActivity();
484         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
485         final String imesAndSubtypesString = prefs.getString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, null);
486         return InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(imesAndSubtypesString);
487     }
488
489     private void savePreviouslyEnabledSubtypeIdsMap(
490             final HashMap<String, HashSet<String>> subtypesMap) {
491         final Context context = getActivity();
492         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
493         final String imesAndSubtypesString = InputMethodAndSubtypeUtil
494                 .buildInputMethodsAndSubtypesString(subtypesMap);
495         prefs.edit().putString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, imesAndSubtypesString).apply();
496     }
497
498     private void updateCurrentImeName() {
499         final Context context = getActivity();
500         if (context == null || mImm == null) return;
501         final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD);
502         if (curPref != null) {
503             final CharSequence curIme =
504                     mInputMethodSettingValues.getCurrentInputMethodName(context);
505             if (!TextUtils.isEmpty(curIme)) {
506                 synchronized (this) {
507                     curPref.setSummary(curIme);
508                 }
509             }
510         }
511     }
512
513     private void updateInputDevices() {
514         updateHardKeyboards();
515         updateGameControllers();
516     }
517
518     private void updateHardKeyboards() {
519         mHardKeyboardPreferenceList.clear();
520         final int[] devices = InputDevice.getDeviceIds();
521         for (int i = 0; i < devices.length; i++) {
522             InputDevice device = InputDevice.getDevice(devices[i]);
523             if (device != null
524                     && !device.isVirtual()
525                     && device.isFullKeyboard()) {
526                 final InputDeviceIdentifier identifier = device.getIdentifier();
527                 final String keyboardLayoutDescriptor =
528                     mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
529                 final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
530                     mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
531
532                 final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
533                 pref.setTitle(device.getName());
534                 if (keyboardLayout != null) {
535                     pref.setSummary(keyboardLayout.toString());
536                 } else {
537                     pref.setSummary(R.string.keyboard_layout_default_label);
538                 }
539                 pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
540                     @Override
541                     public boolean onPreferenceClick(Preference preference) {
542                         showKeyboardLayoutDialog(identifier);
543                         return true;
544                     }
545                 });
546                 mHardKeyboardPreferenceList.add(pref);
547             }
548         }
549
550         if (!mHardKeyboardPreferenceList.isEmpty()) {
551             for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) {
552                 final Preference pref = mHardKeyboardCategory.getPreference(i);
553                 if (pref.getOrder() < 1000) {
554                     mHardKeyboardCategory.removePreference(pref);
555                 }
556             }
557
558             Collections.sort(mHardKeyboardPreferenceList);
559             final int count = mHardKeyboardPreferenceList.size();
560             for (int i = 0; i < count; i++) {
561                 final Preference pref = mHardKeyboardPreferenceList.get(i);
562                 pref.setOrder(i);
563                 mHardKeyboardCategory.addPreference(pref);
564             }
565
566             getPreferenceScreen().addPreference(mHardKeyboardCategory);
567         } else {
568             getPreferenceScreen().removePreference(mHardKeyboardCategory);
569         }
570     }
571
572     private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
573         KeyboardLayoutDialogFragment fragment = new KeyboardLayoutDialogFragment(
574                 inputDeviceIdentifier);
575         fragment.setTargetFragment(this, 0);
576         fragment.show(getActivity().getFragmentManager(), "keyboardLayout");
577     }
578
579     @Override
580     public void onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier) {
581         final Intent intent = new Intent(Intent.ACTION_MAIN);
582         intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class);
583         intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER,
584                 inputDeviceIdentifier);
585         mIntentWaitingForResult = intent;
586         startActivityForResult(intent, 0);
587     }
588
589     @Override
590     public void onActivityResult(int requestCode, int resultCode, Intent data) {
591         super.onActivityResult(requestCode, resultCode, data);
592
593         if (mIntentWaitingForResult != null) {
594             InputDeviceIdentifier inputDeviceIdentifier = mIntentWaitingForResult
595                     .getParcelableExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER);
596             mIntentWaitingForResult = null;
597             showKeyboardLayoutDialog(inputDeviceIdentifier);
598         }
599     }
600
601     private void updateGameControllers() {
602         if (haveInputDeviceWithVibrator()) {
603             getPreferenceScreen().addPreference(mGameControllerCategory);
604
605             SwitchPreference pref = (SwitchPreference)
606                     mGameControllerCategory.findPreference("vibrate_input_devices");
607             pref.setChecked(System.getInt(getContentResolver(),
608                     Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0);
609         } else {
610             getPreferenceScreen().removePreference(mGameControllerCategory);
611         }
612     }
613
614     private static boolean haveInputDeviceWithVibrator() {
615         final int[] devices = InputDevice.getDeviceIds();
616         for (int i = 0; i < devices.length; i++) {
617             InputDevice device = InputDevice.getDevice(devices[i]);
618             if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) {
619                 return true;
620             }
621         }
622         return false;
623     }
624
625     private class SettingsObserver extends ContentObserver {
626         private Context mContext;
627
628         public SettingsObserver(Handler handler, Context context) {
629             super(handler);
630             mContext = context;
631         }
632
633         @Override public void onChange(boolean selfChange) {
634             updateCurrentImeName();
635         }
636
637         public void resume() {
638             final ContentResolver cr = mContext.getContentResolver();
639             cr.registerContentObserver(
640                     Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
641             cr.registerContentObserver(Settings.Secure.getUriFor(
642                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
643         }
644
645         public void pause() {
646             mContext.getContentResolver().unregisterContentObserver(this);
647         }
648     }
649
650     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
651             new BaseSearchIndexProvider() {
652         @Override
653         public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
654             List<SearchIndexableRaw> indexables = new ArrayList<>();
655
656             final String screenTitle = context.getString(R.string.language_keyboard_settings_title);
657
658             // Locale picker.
659             if (context.getAssets().getLocales().length > 1) {
660                 String localeName = getLocaleName(context);
661                 SearchIndexableRaw indexable = new SearchIndexableRaw(context);
662                 indexable.key = KEY_PHONE_LANGUAGE;
663                 indexable.title = context.getString(R.string.phone_language);
664                 indexable.summaryOn = localeName;
665                 indexable.summaryOff = localeName;
666                 indexable.screenTitle = screenTitle;
667                 indexables.add(indexable);
668             }
669
670             // Spell checker.
671             SearchIndexableRaw indexable = new SearchIndexableRaw(context);
672             indexable.key = KEY_SPELL_CHECKERS;
673             indexable.title = context.getString(R.string.spellcheckers_settings_title);
674             indexable.screenTitle = screenTitle;
675             indexable.keywords = context.getString(R.string.keywords_spell_checker);
676             indexables.add(indexable);
677
678             // User dictionary.
679             if (UserDictionaryList.getUserDictionaryLocalesSet(context) != null) {
680                 indexable = new SearchIndexableRaw(context);
681                 indexable.key = "user_dict_settings";
682                 indexable.title = context.getString(R.string.user_dict_settings_title);
683                 indexable.screenTitle = screenTitle;
684                 indexables.add(indexable);
685             }
686
687             // Keyboard settings.
688             indexable = new SearchIndexableRaw(context);
689             indexable.key = "keyboard_settings";
690             indexable.title = context.getString(R.string.keyboard_settings_category);
691             indexable.screenTitle = screenTitle;
692             indexable.keywords = context.getString(R.string.keywords_keyboard_and_ime);
693             indexables.add(indexable);
694
695             InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper
696                     .getInstance(context);
697             immValues.refreshAllInputMethodAndSubtypes();
698
699             // Current IME.
700             String currImeName = immValues.getCurrentInputMethodName(context).toString();
701             indexable = new SearchIndexableRaw(context);
702             indexable.key = KEY_CURRENT_INPUT_METHOD;
703             indexable.title = context.getString(R.string.current_input_method);
704             indexable.summaryOn = currImeName;
705             indexable.summaryOff = currImeName;
706             indexable.screenTitle = screenTitle;
707             indexables.add(indexable);
708
709             InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(
710                     Context.INPUT_METHOD_SERVICE);
711
712             // All other IMEs.
713             List<InputMethodInfo> inputMethods = immValues.getInputMethodList();
714             final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size());
715             for (int i = 0; i < inputMethodCount; ++i) {
716                 InputMethodInfo inputMethod = inputMethods.get(i);
717
718                 StringBuilder builder = new StringBuilder();
719                 List<InputMethodSubtype> subtypes = inputMethodManager
720                         .getEnabledInputMethodSubtypeList(inputMethod, true);
721                 final int subtypeCount = subtypes.size();
722                 for (int j = 0; j < subtypeCount; j++) {
723                     InputMethodSubtype subtype = subtypes.get(j);
724                     if (builder.length() > 0) {
725                         builder.append(',');
726                     }
727                     CharSequence subtypeLabel = subtype.getDisplayName(context,
728                             inputMethod.getPackageName(), inputMethod.getServiceInfo()
729                                     .applicationInfo);
730                     builder.append(subtypeLabel);
731                 }
732                 String summary = builder.toString();
733
734                 ServiceInfo serviceInfo = inputMethod.getServiceInfo();
735                 ComponentName componentName = new ComponentName(serviceInfo.packageName,
736                         serviceInfo.name);
737
738                 indexable = new SearchIndexableRaw(context);
739                 indexable.key = componentName.flattenToString();
740                 indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString();
741                 indexable.summaryOn = summary;
742                 indexable.summaryOff = summary;
743                 indexable.screenTitle = screenTitle;
744                 indexables.add(indexable);
745             }
746
747             // Hard keyboards
748             InputManager inputManager = (InputManager) context.getSystemService(
749                     Context.INPUT_SERVICE);
750             boolean hasHardKeyboards = false;
751
752             final int[] devices = InputDevice.getDeviceIds();
753             for (int i = 0; i < devices.length; i++) {
754                 InputDevice device = InputDevice.getDevice(devices[i]);
755                 if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
756                     continue;
757                 }
758
759                 hasHardKeyboards = true;
760
761                 InputDeviceIdentifier identifier = device.getIdentifier();
762                 String keyboardLayoutDescriptor =
763                         inputManager.getCurrentKeyboardLayoutForInputDevice(identifier);
764                 KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
765                         inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null;
766
767                 String summary;
768                 if (keyboardLayout != null) {
769                     summary = keyboardLayout.toString();
770                 } else {
771                     summary = context.getString(R.string.keyboard_layout_default_label);
772                 }
773
774                 indexable = new SearchIndexableRaw(context);
775                 indexable.key = device.getName();
776                 indexable.title = device.getName();
777                 indexable.summaryOn = summary;
778                 indexable.summaryOff = summary;
779                 indexable.screenTitle = screenTitle;
780                 indexables.add(indexable);
781             }
782
783             if (hasHardKeyboards) {
784                 // Hard keyboard category.
785                 indexable = new SearchIndexableRaw(context);
786                 indexable.key = "builtin_keyboard_settings";
787                 indexable.title = context.getString(
788                         R.string.builtin_keyboard_settings_title);
789                 indexable.screenTitle = screenTitle;
790                 indexables.add(indexable);
791             }
792
793             // Voice input
794             indexable = new SearchIndexableRaw(context);
795             indexable.key = "voice_input_settings";
796             indexable.title = context.getString(R.string.voice_input_settings);
797             indexable.screenTitle = screenTitle;
798             indexable.keywords = context.getString(R.string.keywords_voice_input);
799             indexables.add(indexable);
800
801             // Text-to-speech.
802             TtsEngines ttsEngines = new TtsEngines(context);
803             if (!ttsEngines.getEngines().isEmpty()) {
804                 indexable = new SearchIndexableRaw(context);
805                 indexable.key = "tts_settings";
806                 indexable.title = context.getString(R.string.tts_settings_title);
807                 indexable.screenTitle = screenTitle;
808                 indexable.keywords = context.getString(R.string.keywords_text_to_speech_output);
809                 indexables.add(indexable);
810             }
811
812             // Pointer settings.
813             indexable = new SearchIndexableRaw(context);
814             indexable.key = "pointer_settings_category";
815             indexable.title = context.getString(R.string.pointer_settings_category);
816             indexable.screenTitle = screenTitle;
817             indexables.add(indexable);
818
819             indexable = new SearchIndexableRaw(context);
820             indexable.key = "pointer_speed";
821             indexable.title = context.getString(R.string.pointer_speed);
822             indexable.screenTitle = screenTitle;
823             indexables.add(indexable);
824
825             // Game controllers.
826             if (haveInputDeviceWithVibrator()) {
827                 indexable = new SearchIndexableRaw(context);
828                 indexable.key = "vibrate_input_devices";
829                 indexable.title = context.getString(R.string.vibrate_input_devices);
830                 indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary);
831                 indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary);
832                 indexable.screenTitle = screenTitle;
833                 indexables.add(indexable);
834             }
835
836             return indexables;
837         }
838     };
839 }