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.os.LocaleList;
36import android.provider.Settings;
37import android.provider.Settings.System;
38import android.speech.tts.TtsEngines;
39import android.support.v14.preference.SwitchPreference;
40import android.support.v7.preference.ListPreference;
41import android.support.v7.preference.Preference;
42import android.support.v7.preference.Preference.OnPreferenceClickListener;
43import android.support.v7.preference.PreferenceCategory;
44import android.support.v7.preference.PreferenceManager;
45import android.support.v7.preference.PreferenceScreen;
46import android.text.TextUtils;
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            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
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
313    public void onInputDeviceAdded(int deviceId) {
314        updateInputDevices();
315    }
316
317    @Override
318    public void onInputDeviceChanged(int deviceId) {
319        updateInputDevices();
320    }
321
322    @Override
323    public void onInputDeviceRemoved(int deviceId) {
324        updateInputDevices();
325    }
326
327    @Override
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
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
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
371    private int loadInputMethodSelectorVisibility() {
372        return Settings.Secure.getInt(getContentResolver(),
373                Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
374                mDefaultInputMethodSelectorVisibility);
375    }
376
377    @Override
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
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
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
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
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
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
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
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
522    private void updateInputDevices() {
523        updateHardKeyboards();
524        updateGameControllers();
525    }
526
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
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
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
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
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
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
644        public SettingsObserver(Handler handler, Context context) {
645            super(handler);
646            mContext = context;
647        }
648
649        @Override public void onChange(boolean selfChange) {
650            updateCurrentImeName();
651        }
652
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
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
671        public SummaryProvider(Context context, SummaryLoader summaryLoader) {
672            mContext = context;
673            mSummaryLoader = summaryLoader;
674        }
675
676        @Override
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