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