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