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 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 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 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 225 public void onResume() { 226 super.onResume(); 227 228 mSettingsObserver.resume(); 229 mIm.registerInputDeviceListener(this, null); 230 231 if (!mIsOnlyImeSettings) { 232 if (mLanguagePref != null) { 233 Configuration conf = getResources().getConfiguration(); 234 String language = conf.locale.getLanguage(); 235 String localeString; 236 // TODO: This is not an accurate way to display the locale, as it is 237 // just working around the fact that we support limited dialects 238 // and want to pretend that the language is valid for all locales. 239 // We need a way to support languages that aren't tied to a particular 240 // locale instead of hiding the locale qualifier. 241 if (language.equals("zz")) { 242 String country = conf.locale.getCountry(); 243 if (country.equals("ZZ")) { 244 localeString = "[Developer] Accented English (zz_ZZ)"; 245 } else if (country.equals("ZY")) { 246 localeString = "[Developer] Fake Bi-Directional (zz_ZY)"; 247 } else { 248 localeString = ""; 249 } 250 } else if (hasOnlyOneLanguageInstance(language, 251 Resources.getSystem().getAssets().getLocales())) { 252 localeString = conf.locale.getDisplayLanguage(conf.locale); 253 } else { 254 localeString = conf.locale.getDisplayName(conf.locale); 255 } 256 if (localeString.length() > 1) { 257 localeString = Character.toUpperCase(localeString.charAt(0)) 258 + localeString.substring(1); 259 mLanguagePref.setSummary(localeString); 260 } 261 } 262 263 updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS)); 264 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 265 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this); 266 } 267 } 268 269 // Hard keyboard 270 if (!mHardKeyboardPreferenceList.isEmpty()) { 271 for (int i = 0; i < sHardKeyboardKeys.length; ++i) { 272 CheckBoxPreference chkPref = (CheckBoxPreference) 273 mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]); 274 chkPref.setChecked( 275 System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0); 276 } 277 } 278 279 updateInputDevices(); 280 281 // IME 282 InputMethodAndSubtypeUtil.loadInputMethodSubtypeList( 283 this, getContentResolver(), mImis, null); 284 updateActiveInputMethodsSummary(); 285 } 286 287 @Override 288 public void onPause() { 289 super.onPause(); 290 291 mIm.unregisterInputDeviceListener(this); 292 mSettingsObserver.pause(); 293 294 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 295 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null); 296 } 297 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList( 298 this, getContentResolver(), mImis, !mHardKeyboardPreferenceList.isEmpty()); 299 } 300 301 @Override 302 public void onInputDeviceAdded(int deviceId) { 303 updateInputDevices(); 304 } 305 306 @Override 307 public void onInputDeviceChanged(int deviceId) { 308 updateInputDevices(); 309 } 310 311 @Override 312 public void onInputDeviceRemoved(int deviceId) { 313 updateInputDevices(); 314 } 315 316 @Override 317 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 318 // Input Method stuff 319 if (Utils.isMonkeyRunning()) { 320 return false; 321 } 322 if (preference instanceof PreferenceScreen) { 323 if (preference.getFragment() != null) { 324 // Fragment will be handled correctly by the super class. 325 } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) { 326 final InputMethodManager imm = (InputMethodManager) 327 getSystemService(Context.INPUT_METHOD_SERVICE); 328 imm.showInputMethodPicker(); 329 } 330 } else if (preference instanceof CheckBoxPreference) { 331 final CheckBoxPreference chkPref = (CheckBoxPreference) preference; 332 if (!mHardKeyboardPreferenceList.isEmpty()) { 333 for (int i = 0; i < sHardKeyboardKeys.length; ++i) { 334 if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) { 335 System.putInt(getContentResolver(), sSystemSettingNames[i], 336 chkPref.isChecked() ? 1 : 0); 337 return true; 338 } 339 } 340 } 341 if (chkPref == mGameControllerCategory.findPreference("vibrate_input_devices")) { 342 System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES, 343 chkPref.isChecked() ? 1 : 0); 344 return true; 345 } 346 } 347 return super.onPreferenceTreeClick(preferenceScreen, preference); 348 } 349 350 private boolean hasOnlyOneLanguageInstance(String languageCode, String[] locales) { 351 int count = 0; 352 for (String localeCode : locales) { 353 if (localeCode.length() > 2 354 && localeCode.startsWith(languageCode)) { 355 count++; 356 if (count > 1) { 357 return false; 358 } 359 } 360 } 361 return count == 1; 362 } 363 364 private void saveInputMethodSelectorVisibility(String value) { 365 try { 366 int intValue = Integer.valueOf(value); 367 Settings.Secure.putInt(getContentResolver(), 368 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue); 369 updateInputMethodSelectorSummary(intValue); 370 } catch(NumberFormatException e) { 371 } 372 } 373 374 private int loadInputMethodSelectorVisibility() { 375 return Settings.Secure.getInt(getContentResolver(), 376 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, 377 mDefaultInputMethodSelectorVisibility); 378 } 379 380 @Override 381 public boolean onPreferenceChange(Preference preference, Object value) { 382 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 383 if (preference == mShowInputMethodSelectorPref) { 384 if (value instanceof String) { 385 saveInputMethodSelectorVisibility((String)value); 386 } 387 } 388 } 389 return false; 390 } 391 392 private void updateActiveInputMethodsSummary() { 393 for (Preference pref : mInputMethodPreferenceList) { 394 if (pref instanceof InputMethodPreference) { 395 ((InputMethodPreference)pref).updateSummary(); 396 } 397 } 398 updateCurrentImeName(); 399 } 400 401 private void updateCurrentImeName() { 402 final Context context = getActivity(); 403 if (context == null || mImm == null) return; 404 final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD); 405 if (curPref != null) { 406 final CharSequence curIme = InputMethodAndSubtypeUtil.getCurrentInputMethodName( 407 context, getContentResolver(), mImm, mImis, getPackageManager()); 408 if (!TextUtils.isEmpty(curIme)) { 409 synchronized(this) { 410 curPref.setSummary(curIme); 411 } 412 } 413 } 414 } 415 416 private InputMethodPreference getInputMethodPreference(InputMethodInfo imi, int imiSize) { 417 final PackageManager pm = getPackageManager(); 418 final CharSequence label = imi.loadLabel(pm); 419 // IME settings 420 final Intent intent; 421 final String settingsActivity = imi.getSettingsActivity(); 422 if (!TextUtils.isEmpty(settingsActivity)) { 423 intent = new Intent(Intent.ACTION_MAIN); 424 intent.setClassName(imi.getPackageName(), settingsActivity); 425 } else { 426 intent = null; 427 } 428 429 // Add a check box for enabling/disabling IME 430 InputMethodPreference pref = new InputMethodPreference(this, intent, mImm, imi, imiSize); 431 pref.setKey(imi.getId()); 432 pref.setTitle(label); 433 return pref; 434 } 435 436 private void updateInputDevices() { 437 updateHardKeyboards(); 438 updateGameControllers(); 439 } 440 441 private void updateHardKeyboards() { 442 mHardKeyboardPreferenceList.clear(); 443 if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) { 444 final int[] devices = InputDevice.getDeviceIds(); 445 for (int i = 0; i < devices.length; i++) { 446 InputDevice device = InputDevice.getDevice(devices[i]); 447 if (device != null 448 && !device.isVirtual() 449 && device.isFullKeyboard()) { 450 final String inputDeviceDescriptor = device.getDescriptor(); 451 final String keyboardLayoutDescriptor = 452 mIm.getCurrentKeyboardLayoutForInputDevice(inputDeviceDescriptor); 453 final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ? 454 mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null; 455 456 final PreferenceScreen pref = new PreferenceScreen(getActivity(), null); 457 pref.setTitle(device.getName()); 458 if (keyboardLayout != null) { 459 pref.setSummary(keyboardLayout.toString()); 460 } else { 461 pref.setSummary(R.string.keyboard_layout_default_label); 462 } 463 pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { 464 @Override 465 public boolean onPreferenceClick(Preference preference) { 466 showKeyboardLayoutDialog(inputDeviceDescriptor); 467 return true; 468 } 469 }); 470 mHardKeyboardPreferenceList.add(pref); 471 } 472 } 473 } 474 475 if (!mHardKeyboardPreferenceList.isEmpty()) { 476 for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) { 477 final Preference pref = mHardKeyboardCategory.getPreference(i); 478 if (pref.getOrder() < 1000) { 479 mHardKeyboardCategory.removePreference(pref); 480 } 481 } 482 483 Collections.sort(mHardKeyboardPreferenceList); 484 final int count = mHardKeyboardPreferenceList.size(); 485 for (int i = 0; i < count; i++) { 486 final Preference pref = mHardKeyboardPreferenceList.get(i); 487 pref.setOrder(i); 488 mHardKeyboardCategory.addPreference(pref); 489 } 490 491 getPreferenceScreen().addPreference(mHardKeyboardCategory); 492 } else { 493 getPreferenceScreen().removePreference(mHardKeyboardCategory); 494 } 495 } 496 497 private void showKeyboardLayoutDialog(String inputDeviceDescriptor) { 498 KeyboardLayoutDialogFragment fragment = 499 new KeyboardLayoutDialogFragment(inputDeviceDescriptor); 500 fragment.setTargetFragment(this, 0); 501 fragment.show(getActivity().getFragmentManager(), "keyboardLayout"); 502 } 503 504 @Override 505 public void onSetupKeyboardLayouts(String inputDeviceDescriptor) { 506 final Intent intent = new Intent(Intent.ACTION_MAIN); 507 intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class); 508 intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_DESCRIPTOR, 509 inputDeviceDescriptor); 510 mIntentWaitingForResult = intent; 511 startActivityForResult(intent, 0); 512 } 513 514 @Override 515 public void onActivityResult(int requestCode, int resultCode, Intent data) { 516 super.onActivityResult(requestCode, resultCode, data); 517 518 if (mIntentWaitingForResult != null) { 519 String inputDeviceDescriptor = mIntentWaitingForResult.getStringExtra( 520 KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_DESCRIPTOR); 521 mIntentWaitingForResult = null; 522 showKeyboardLayoutDialog(inputDeviceDescriptor); 523 } 524 } 525 526 private void updateGameControllers() { 527 if (haveInputDeviceWithVibrator()) { 528 getPreferenceScreen().addPreference(mGameControllerCategory); 529 530 CheckBoxPreference chkPref = (CheckBoxPreference) 531 mGameControllerCategory.findPreference("vibrate_input_devices"); 532 chkPref.setChecked(System.getInt(getContentResolver(), 533 Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0); 534 } else { 535 getPreferenceScreen().removePreference(mGameControllerCategory); 536 } 537 } 538 539 private boolean haveInputDeviceWithVibrator() { 540 final int[] devices = InputDevice.getDeviceIds(); 541 for (int i = 0; i < devices.length; i++) { 542 InputDevice device = InputDevice.getDevice(devices[i]); 543 if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) { 544 return true; 545 } 546 } 547 return false; 548 } 549 550 private class SettingsObserver extends ContentObserver { 551 private Context mContext; 552 553 public SettingsObserver(Handler handler, Context context) { 554 super(handler); 555 mContext = context; 556 } 557 558 @Override public void onChange(boolean selfChange) { 559 updateCurrentImeName(); 560 } 561 562 public void resume() { 563 final ContentResolver cr = mContext.getContentResolver(); 564 cr.registerContentObserver( 565 Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this); 566 cr.registerContentObserver(Settings.Secure.getUriFor( 567 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); 568 } 569 570 public void pause() { 571 mContext.getContentResolver().unregisterContentObserver(this); 572 } 573 } 574 } 575