• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle icicle)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 
updateInputMethodSelectorSummary(int value)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 
updateUserDictionaryPreference(Preference userDictionaryPreference)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
onResume()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
onPause()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
onInputDeviceAdded(int deviceId)301     public void onInputDeviceAdded(int deviceId) {
302         updateInputDevices();
303     }
304 
305     @Override
onInputDeviceChanged(int deviceId)306     public void onInputDeviceChanged(int deviceId) {
307         updateInputDevices();
308     }
309 
310     @Override
onInputDeviceRemoved(int deviceId)311     public void onInputDeviceRemoved(int deviceId) {
312         updateInputDevices();
313     }
314 
315     @Override
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)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 
getLocaleName(Context context)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 
saveInputMethodSelectorVisibility(String value)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 
loadInputMethodSelectorVisibility()365     private int loadInputMethodSelectorVisibility() {
366         return Settings.Secure.getInt(getContentResolver(),
367                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
368                 mDefaultInputMethodSelectorVisibility);
369     }
370 
371     @Override
onPreferenceChange(Preference preference, Object value)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 
updateInputMethodPreferenceViews()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
onSaveInputMethodPreference(final InputMethodPreference pref)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 
saveEnabledSubtypesOf(final InputMethodInfo imi)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 
restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi)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 
loadPreviouslyEnabledSubtypeIdsMap()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 
savePreviouslyEnabledSubtypeIdsMap( final HashMap<String, HashSet<String>> subtypesMap)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 
updateCurrentImeName()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 
updateInputDevices()512     private void updateInputDevices() {
513         updateHardKeyboards();
514         updateGameControllers();
515     }
516 
updateHardKeyboards()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 
showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier)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
onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier)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
onActivityResult(int requestCode, int resultCode, Intent data)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 
updateGameControllers()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 
haveInputDeviceWithVibrator()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 
SettingsObserver(Handler handler, Context context)627         public SettingsObserver(Handler handler, Context context) {
628             super(handler);
629             mContext = context;
630         }
631 
onChange(boolean selfChange)632         @Override public void onChange(boolean selfChange) {
633             updateCurrentImeName();
634         }
635 
resume()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 
pause()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