• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.inputmethod;
18 
19 import com.android.settings.R;
20 import com.android.settings.Settings.KeyboardLayoutPickerActivity;
21 import com.android.settings.Settings.SpellCheckersSettingsActivity;
22 import com.android.settings.SettingsPreferenceFragment;
23 import com.android.settings.Utils;
24 import com.android.settings.VoiceInputOutputSettings;
25 
26 import android.app.Activity;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.content.res.Configuration;
32 import android.content.res.Resources;
33 import android.database.ContentObserver;
34 import android.hardware.input.InputManager;
35 import android.hardware.input.KeyboardLayout;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.preference.CheckBoxPreference;
39 import android.preference.ListPreference;
40 import android.preference.Preference;
41 import android.preference.PreferenceCategory;
42 import android.preference.PreferenceScreen;
43 import android.provider.Settings;
44 import android.provider.Settings.System;
45 import android.text.TextUtils;
46 import android.view.InputDevice;
47 import android.view.inputmethod.InputMethodInfo;
48 import android.view.inputmethod.InputMethodManager;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.List;
53 import java.util.TreeSet;
54 
55 public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
56         implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener,
57         KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener {
58 
59     private static final String KEY_PHONE_LANGUAGE = "phone_language";
60     private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
61     private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
62     private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
63     // false: on ICS or later
64     private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
65 
66     private static final String[] sSystemSettingNames = {
67         System.TEXT_AUTO_REPLACE, System.TEXT_AUTO_CAPS, System.TEXT_AUTO_PUNCTUATE,
68     };
69 
70     private static final String[] sHardKeyboardKeys = {
71         "auto_replace", "auto_caps", "auto_punctuate",
72     };
73 
74     private int mDefaultInputMethodSelectorVisibility = 0;
75     private ListPreference mShowInputMethodSelectorPref;
76     private PreferenceCategory mKeyboardSettingsCategory;
77     private PreferenceCategory mHardKeyboardCategory;
78     private PreferenceCategory mGameControllerCategory;
79     private Preference mLanguagePref;
80     private final ArrayList<InputMethodPreference> mInputMethodPreferenceList =
81             new ArrayList<InputMethodPreference>();
82     private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList =
83             new ArrayList<PreferenceScreen>();
84     private InputManager mIm;
85     private InputMethodManager mImm;
86     private List<InputMethodInfo> mImis;
87     private boolean mIsOnlyImeSettings;
88     private Handler mHandler;
89     @SuppressWarnings("unused")
90     private SettingsObserver mSettingsObserver;
91     private Intent mIntentWaitingForResult;
92 
93     @Override
onCreate(Bundle icicle)94     public void onCreate(Bundle icicle) {
95         super.onCreate(icicle);
96 
97         addPreferencesFromResource(R.xml.language_settings);
98 
99         try {
100             mDefaultInputMethodSelectorVisibility = Integer.valueOf(
101                     getString(R.string.input_method_selector_visibility_default_value));
102         } catch (NumberFormatException e) {
103         }
104 
105         if (getActivity().getAssets().getLocales().length == 1) {
106             // No "Select language" pref if there's only one system locale available.
107             getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
108         } else {
109             mLanguagePref = findPreference(KEY_PHONE_LANGUAGE);
110         }
111         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
112             mShowInputMethodSelectorPref = (ListPreference)findPreference(
113                     KEY_INPUT_METHOD_SELECTOR);
114             mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
115             // TODO: Update current input method name on summary
116             updateInputMethodSelectorSummary(loadInputMethodSelectorVisibility());
117         }
118 
119         new VoiceInputOutputSettings(this).onCreate();
120 
121         // Get references to dynamically constructed categories.
122         mHardKeyboardCategory = (PreferenceCategory)findPreference("hard_keyboard");
123         mKeyboardSettingsCategory = (PreferenceCategory)findPreference(
124                 "keyboard_settings_category");
125         mGameControllerCategory = (PreferenceCategory)findPreference(
126                 "game_controller_settings_category");
127 
128         // Filter out irrelevant features if invoked from IME settings button.
129         mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
130                 getActivity().getIntent().getAction());
131         getActivity().getIntent().setAction(null);
132         if (mIsOnlyImeSettings) {
133             getPreferenceScreen().removeAll();
134             getPreferenceScreen().addPreference(mHardKeyboardCategory);
135             if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
136                 getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
137             }
138             getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
139         }
140 
141         // Build IME preference category.
142         mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
143         mImis = mImm.getInputMethodList();
144 
145         mKeyboardSettingsCategory.removeAll();
146         if (!mIsOnlyImeSettings) {
147             final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null);
148             currentIme.setKey(KEY_CURRENT_INPUT_METHOD);
149             currentIme.setTitle(getResources().getString(R.string.current_input_method));
150             mKeyboardSettingsCategory.addPreference(currentIme);
151         }
152 
153         mInputMethodPreferenceList.clear();
154         final int N = (mImis == null ? 0 : mImis.size());
155         for (int i = 0; i < N; ++i) {
156             final InputMethodInfo imi = mImis.get(i);
157             final InputMethodPreference pref = getInputMethodPreference(imi, N);
158             mInputMethodPreferenceList.add(pref);
159         }
160 
161         if (!mInputMethodPreferenceList.isEmpty()) {
162             Collections.sort(mInputMethodPreferenceList);
163             for (int i = 0; i < N; ++i) {
164                 mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i));
165             }
166         }
167 
168         // Build hard keyboard and game controller preference categories.
169         mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE);
170         updateInputDevices();
171 
172         // Spell Checker
173         final Intent intent = new Intent(Intent.ACTION_MAIN);
174         intent.setClass(getActivity(), SpellCheckersSettingsActivity.class);
175         final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference(
176                 "spellcheckers_settings"));
177         if (scp != null) {
178             scp.setFragmentIntent(this, intent);
179         }
180 
181         mHandler = new Handler();
182         mSettingsObserver = new SettingsObserver(mHandler, getActivity());
183     }
184 
updateInputMethodSelectorSummary(int value)185     private void updateInputMethodSelectorSummary(int value) {
186         String[] inputMethodSelectorTitles = getResources().getStringArray(
187                 R.array.input_method_selector_titles);
188         if (inputMethodSelectorTitles.length > value) {
189             mShowInputMethodSelectorPref.setSummary(inputMethodSelectorTitles[value]);
190             mShowInputMethodSelectorPref.setValue(String.valueOf(value));
191         }
192     }
193 
updateUserDictionaryPreference(Preference userDictionaryPreference)194     private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
195         final Activity activity = getActivity();
196         final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity);
197         if (null == localeList) {
198             // The locale list is null if and only if the user dictionary service is
199             // not present or disabled. In this case we need to remove the preference.
200             getPreferenceScreen().removePreference(userDictionaryPreference);
201         } else if (localeList.size() <= 1) {
202             final Intent intent =
203                     new Intent(UserDictionaryList.USER_DICTIONARY_SETTINGS_INTENT_ACTION);
204             userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title);
205             userDictionaryPreference.setIntent(intent);
206             userDictionaryPreference.setFragment(
207                     com.android.settings.UserDictionarySettings.class.getName());
208             // If the size of localeList is 0, we don't set the locale parameter in the
209             // extras. This will be interpreted by the UserDictionarySettings class as
210             // meaning "the current locale".
211             // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet()
212             // the locale list always has at least one element, since it always includes the current
213             // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet().
214             if (localeList.size() == 1) {
215                 final String locale = (String)localeList.toArray()[0];
216                 userDictionaryPreference.getExtras().putString("locale", locale);
217             }
218         } else {
219             userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title);
220             userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
221         }
222     }
223 
224     @Override
onResume()225     public void onResume() {
226         super.onResume();
227 
228         mIm.registerInputDeviceListener(this, null);
229 
230         if (!mIsOnlyImeSettings) {
231             if (mLanguagePref != null) {
232                 Configuration conf = getResources().getConfiguration();
233                 String language = conf.locale.getLanguage();
234                 String localeString;
235                 // TODO: This is not an accurate way to display the locale, as it is
236                 // just working around the fact that we support limited dialects
237                 // and want to pretend that the language is valid for all locales.
238                 // We need a way to support languages that aren't tied to a particular
239                 // locale instead of hiding the locale qualifier.
240                 if (hasOnlyOneLanguageInstance(language,
241                         Resources.getSystem().getAssets().getLocales())) {
242                     localeString = conf.locale.getDisplayLanguage(conf.locale);
243                 } else {
244                     localeString = conf.locale.getDisplayName(conf.locale);
245                 }
246                 if (localeString.length() > 1) {
247                     localeString = Character.toUpperCase(localeString.charAt(0))
248                             + localeString.substring(1);
249                     mLanguagePref.setSummary(localeString);
250                 }
251             }
252 
253             updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
254             if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
255                 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
256             }
257         }
258 
259         // Hard keyboard
260         if (!mHardKeyboardPreferenceList.isEmpty()) {
261             for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
262                 CheckBoxPreference chkPref = (CheckBoxPreference)
263                         mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]);
264                 chkPref.setChecked(
265                         System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0);
266             }
267         }
268 
269         updateInputDevices();
270 
271         // IME
272         InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
273                 this, getContentResolver(), mImis, null);
274         updateActiveInputMethodsSummary();
275     }
276 
277     @Override
onPause()278     public void onPause() {
279         super.onPause();
280 
281         mIm.unregisterInputDeviceListener(this);
282 
283         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
284             mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null);
285         }
286         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
287                 this, getContentResolver(), mImis, !mHardKeyboardPreferenceList.isEmpty());
288     }
289 
290     @Override
onInputDeviceAdded(int deviceId)291     public void onInputDeviceAdded(int deviceId) {
292         updateInputDevices();
293     }
294 
295     @Override
onInputDeviceChanged(int deviceId)296     public void onInputDeviceChanged(int deviceId) {
297         updateInputDevices();
298     }
299 
300     @Override
onInputDeviceRemoved(int deviceId)301     public void onInputDeviceRemoved(int deviceId) {
302         updateInputDevices();
303     }
304 
305     @Override
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)306     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
307         // Input Method stuff
308         if (Utils.isMonkeyRunning()) {
309             return false;
310         }
311         if (preference instanceof PreferenceScreen) {
312             if (preference.getFragment() != null) {
313                 // Fragment will be handled correctly by the super class.
314             } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) {
315                 final InputMethodManager imm = (InputMethodManager)
316                         getSystemService(Context.INPUT_METHOD_SERVICE);
317                 imm.showInputMethodPicker();
318             }
319         } else if (preference instanceof CheckBoxPreference) {
320             final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
321             if (!mHardKeyboardPreferenceList.isEmpty()) {
322                 for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
323                     if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
324                         System.putInt(getContentResolver(), sSystemSettingNames[i],
325                                 chkPref.isChecked() ? 1 : 0);
326                         return true;
327                     }
328                 }
329             }
330             if (chkPref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
331                 System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
332                         chkPref.isChecked() ? 1 : 0);
333                 return true;
334             }
335         }
336         return super.onPreferenceTreeClick(preferenceScreen, preference);
337     }
338 
hasOnlyOneLanguageInstance(String languageCode, String[] locales)339     private boolean hasOnlyOneLanguageInstance(String languageCode, String[] locales) {
340         int count = 0;
341         for (String localeCode : locales) {
342             if (localeCode.length() > 2
343                     && localeCode.startsWith(languageCode)) {
344                 count++;
345                 if (count > 1) {
346                     return false;
347                 }
348             }
349         }
350         return count == 1;
351     }
352 
saveInputMethodSelectorVisibility(String value)353     private void saveInputMethodSelectorVisibility(String value) {
354         try {
355             int intValue = Integer.valueOf(value);
356             Settings.Secure.putInt(getContentResolver(),
357                     Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue);
358             updateInputMethodSelectorSummary(intValue);
359         } catch(NumberFormatException e) {
360         }
361     }
362 
loadInputMethodSelectorVisibility()363     private int loadInputMethodSelectorVisibility() {
364         return Settings.Secure.getInt(getContentResolver(),
365                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
366                 mDefaultInputMethodSelectorVisibility);
367     }
368 
369     @Override
onPreferenceChange(Preference preference, Object value)370     public boolean onPreferenceChange(Preference preference, Object value) {
371         if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
372             if (preference == mShowInputMethodSelectorPref) {
373                 if (value instanceof String) {
374                     saveInputMethodSelectorVisibility((String)value);
375                 }
376             }
377         }
378         return false;
379     }
380 
updateActiveInputMethodsSummary()381     private void updateActiveInputMethodsSummary() {
382         for (Preference pref : mInputMethodPreferenceList) {
383             if (pref instanceof InputMethodPreference) {
384                 ((InputMethodPreference)pref).updateSummary();
385             }
386         }
387         updateCurrentImeName();
388     }
389 
updateCurrentImeName()390     private void updateCurrentImeName() {
391         final Context context = getActivity();
392         if (context == null || mImm == null) return;
393         final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD);
394         if (curPref != null) {
395             final CharSequence curIme = InputMethodAndSubtypeUtil.getCurrentInputMethodName(
396                     context, getContentResolver(), mImm, mImis, getPackageManager());
397             if (!TextUtils.isEmpty(curIme)) {
398                 synchronized(this) {
399                     curPref.setSummary(curIme);
400                 }
401             }
402         }
403     }
404 
getInputMethodPreference(InputMethodInfo imi, int imiSize)405     private InputMethodPreference getInputMethodPreference(InputMethodInfo imi, int imiSize) {
406         final PackageManager pm = getPackageManager();
407         final CharSequence label = imi.loadLabel(pm);
408         // IME settings
409         final Intent intent;
410         final String settingsActivity = imi.getSettingsActivity();
411         if (!TextUtils.isEmpty(settingsActivity)) {
412             intent = new Intent(Intent.ACTION_MAIN);
413             intent.setClassName(imi.getPackageName(), settingsActivity);
414         } else {
415             intent = null;
416         }
417 
418         // Add a check box for enabling/disabling IME
419         InputMethodPreference pref = new InputMethodPreference(this, intent, mImm, imi, imiSize);
420         pref.setKey(imi.getId());
421         pref.setTitle(label);
422         return pref;
423     }
424 
updateInputDevices()425     private void updateInputDevices() {
426         updateHardKeyboards();
427         updateGameControllers();
428     }
429 
updateHardKeyboards()430     private void updateHardKeyboards() {
431         mHardKeyboardPreferenceList.clear();
432         if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) {
433             final int[] devices = InputDevice.getDeviceIds();
434             for (int i = 0; i < devices.length; i++) {
435                 InputDevice device = InputDevice.getDevice(devices[i]);
436                 if (device != null
437                         && !device.isVirtual()
438                         && device.isFullKeyboard()) {
439                     final String inputDeviceDescriptor = device.getDescriptor();
440                     final String keyboardLayoutDescriptor =
441                             mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor);
442                     final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
443                             mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
444 
445                     final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
446                     pref.setTitle(device.getName());
447                     if (keyboardLayout != null) {
448                         pref.setSummary(keyboardLayout.toString());
449                     } else {
450                         pref.setSummary(R.string.keyboard_layout_default_label);
451                     }
452                     pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
453                         @Override
454                         public boolean onPreferenceClick(Preference preference) {
455                             showKeyboardLayoutDialog(inputDeviceDescriptor);
456                             return true;
457                         }
458                     });
459                     mHardKeyboardPreferenceList.add(pref);
460                 }
461             }
462         }
463 
464         if (!mHardKeyboardPreferenceList.isEmpty()) {
465             for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) {
466                 final Preference pref = mHardKeyboardCategory.getPreference(i);
467                 if (pref.getOrder() < 1000) {
468                     mHardKeyboardCategory.removePreference(pref);
469                 }
470             }
471 
472             Collections.sort(mHardKeyboardPreferenceList);
473             final int count = mHardKeyboardPreferenceList.size();
474             for (int i = 0; i < count; i++) {
475                 final Preference pref = mHardKeyboardPreferenceList.get(i);
476                 pref.setOrder(i);
477                 mHardKeyboardCategory.addPreference(pref);
478             }
479 
480             getPreferenceScreen().addPreference(mHardKeyboardCategory);
481         } else {
482             getPreferenceScreen().removePreference(mHardKeyboardCategory);
483         }
484     }
485 
showKeyboardLayoutDialog(String inputDeviceDescriptor)486     private void showKeyboardLayoutDialog(String inputDeviceDescriptor) {
487         KeyboardLayoutDialogFragment fragment =
488                 new KeyboardLayoutDialogFragment(inputDeviceDescriptor);
489         fragment.setTargetFragment(this, 0);
490         fragment.show(getActivity().getFragmentManager(), "keyboardLayout");
491     }
492 
493     @Override
onSetupKeyboardLayouts(String inputDeviceDescriptor)494     public void onSetupKeyboardLayouts(String inputDeviceDescriptor) {
495         final Intent intent = new Intent(Intent.ACTION_MAIN);
496         intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class);
497         intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_DESCRIPTOR,
498                 inputDeviceDescriptor);
499         mIntentWaitingForResult = intent;
500         startActivityForResult(intent, 0);
501     }
502 
503     @Override
onActivityResult(int requestCode, int resultCode, Intent data)504     public void onActivityResult(int requestCode, int resultCode, Intent data) {
505         super.onActivityResult(requestCode, resultCode, data);
506 
507         if (mIntentWaitingForResult != null) {
508             String inputDeviceDescriptor = mIntentWaitingForResult.getStringExtra(
509                     KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_DESCRIPTOR);
510             mIntentWaitingForResult = null;
511             showKeyboardLayoutDialog(inputDeviceDescriptor);
512         }
513     }
514 
updateGameControllers()515     private void updateGameControllers() {
516         if (haveInputDeviceWithVibrator()) {
517             getPreferenceScreen().addPreference(mGameControllerCategory);
518 
519             CheckBoxPreference chkPref = (CheckBoxPreference)
520                     mGameControllerCategory.findPreference("vibrate_input_devices");
521             chkPref.setChecked(System.getInt(getContentResolver(),
522                     Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0);
523         } else {
524             getPreferenceScreen().removePreference(mGameControllerCategory);
525         }
526     }
527 
haveInputDeviceWithVibrator()528     private boolean haveInputDeviceWithVibrator() {
529         final int[] devices = InputDevice.getDeviceIds();
530         for (int i = 0; i < devices.length; i++) {
531             InputDevice device = InputDevice.getDevice(devices[i]);
532             if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) {
533                 return true;
534             }
535         }
536         return false;
537     }
538 
539     private class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler, Context context)540         public SettingsObserver(Handler handler, Context context) {
541             super(handler);
542             final ContentResolver cr = context.getContentResolver();
543             cr.registerContentObserver(
544                     Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
545             cr.registerContentObserver(Settings.Secure.getUriFor(
546                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
547         }
548 
onChange(boolean selfChange)549         @Override public void onChange(boolean selfChange) {
550             updateCurrentImeName();
551         }
552     }
553 }
554