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 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 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 291 public void onInputDeviceAdded(int deviceId) { 292 updateInputDevices(); 293 } 294 295 @Override 296 public void onInputDeviceChanged(int deviceId) { 297 updateInputDevices(); 298 } 299 300 @Override 301 public void onInputDeviceRemoved(int deviceId) { 302 updateInputDevices(); 303 } 304 305 @Override 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 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 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 363 private int loadInputMethodSelectorVisibility() { 364 return Settings.Secure.getInt(getContentResolver(), 365 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, 366 mDefaultInputMethodSelectorVisibility); 367 } 368 369 @Override 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 381 private void updateActiveInputMethodsSummary() { 382 for (Preference pref : mInputMethodPreferenceList) { 383 if (pref instanceof InputMethodPreference) { 384 ((InputMethodPreference)pref).updateSummary(); 385 } 386 } 387 updateCurrentImeName(); 388 } 389 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 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 425 private void updateInputDevices() { 426 updateHardKeyboards(); 427 updateGameControllers(); 428 } 429 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 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 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 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 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 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 { 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 549 @Override public void onChange(boolean selfChange) { 550 updateCurrentImeName(); 551 } 552 } 553 } 554