SubtypeSwitcher.java revision fefda4e6df5c2f8e2b2730dfe5b88644a1caaa6b
1/* 2 * Copyright (C) 2010 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.inputmethod.latin; 18 19import com.android.inputmethod.keyboard.KeyboardSwitcher; 20import com.android.inputmethod.voice.SettingsUtil; 21import com.android.inputmethod.voice.VoiceIMEConnector; 22import com.android.inputmethod.voice.VoiceInput; 23 24import android.content.Context; 25import android.content.SharedPreferences; 26import android.content.pm.PackageManager; 27import android.content.res.Configuration; 28import android.content.res.Resources; 29import android.graphics.drawable.Drawable; 30import android.os.IBinder; 31import android.text.TextUtils; 32import android.util.Log; 33import android.view.inputmethod.InputMethodInfo; 34import android.view.inputmethod.InputMethodManager; 35import android.view.inputmethod.InputMethodSubtype; 36 37import java.util.ArrayList; 38import java.util.Arrays; 39import java.util.List; 40import java.util.Locale; 41import java.util.Map; 42 43public class SubtypeSwitcher { 44 private static final boolean DBG = LatinImeLogger.sDBG; 45 private static final String TAG = "SubtypeSwitcher"; 46 47 private static final char LOCALE_SEPARATER = '_'; 48 private static final String KEYBOARD_MODE = "keyboard"; 49 private static final String VOICE_MODE = "voice"; 50 private final TextUtils.SimpleStringSplitter mLocaleSplitter = 51 new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER); 52 53 private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); 54 private /* final */ LatinIME mService; 55 private /* final */ SharedPreferences mPrefs; 56 private /* final */ InputMethodManager mImm; 57 private /* final */ Resources mResources; 58 private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod = 59 new ArrayList<InputMethodSubtype>(); 60 private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>(); 61 62 private boolean mConfigUseSpacebarLanguageSwitcher; 63 64 /*-----------------------------------------------------------*/ 65 // Variants which should be changed only by reload functions. 66 private boolean mNeedsToDisplayLanguage; 67 private boolean mIsSystemLanguageSameAsInputLanguage; 68 private InputMethodInfo mShortcutInfo; 69 private InputMethodSubtype mShortcutSubtype; 70 private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod; 71 private Locale mSystemLocale; 72 private Locale mInputLocale; 73 private String mInputLocaleStr; 74 private String mMode; 75 private VoiceInput mVoiceInput; 76 /*-----------------------------------------------------------*/ 77 78 public static SubtypeSwitcher getInstance() { 79 return sInstance; 80 } 81 82 public static void init(LatinIME service, SharedPreferences prefs) { 83 sInstance.mPrefs = prefs; 84 sInstance.resetParams(service); 85 sInstance.updateAllParameters(); 86 87 SubtypeLocale.init(service); 88 } 89 90 private SubtypeSwitcher() { 91 // Intentional empty constructor for singleton. 92 } 93 94 private void resetParams(LatinIME service) { 95 mService = service; 96 mResources = service.getResources(); 97 mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); 98 mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); 99 mEnabledLanguagesOfCurrentInputMethod.clear(); 100 mSystemLocale = null; 101 mInputLocale = null; 102 mInputLocaleStr = null; 103 // Mode is initialized to KEYBOARD_MODE, in case that LatinIME can't obtain currentSubtype 104 mMode = KEYBOARD_MODE; 105 mAllEnabledSubtypesOfCurrentInputMethod = null; 106 // TODO: Voice input should be created here 107 mVoiceInput = null; 108 mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean( 109 R.bool.config_use_spacebar_language_switcher); 110 if (mConfigUseSpacebarLanguageSwitcher) 111 initLanguageSwitcher(service); 112 } 113 114 // Update all parameters stored in SubtypeSwitcher. 115 // Only configuration changed event is allowed to call this because this is heavy. 116 private void updateAllParameters() { 117 mSystemLocale = mResources.getConfiguration().locale; 118 updateSubtype(mImm.getCurrentInputMethodSubtype()); 119 updateParametersOnStartInputView(); 120 } 121 122 // Update parameters which are changed outside LatinIME. This parameters affect UI so they 123 // should be updated every time onStartInputview. 124 public void updateParametersOnStartInputView() { 125 if (mConfigUseSpacebarLanguageSwitcher) { 126 updateForSpacebarLanguageSwitch(); 127 } else { 128 updateEnabledSubtypes(); 129 } 130 updateShortcutIME(); 131 } 132 133 // Reload enabledSubtypes from the framework. 134 private void updateEnabledSubtypes() { 135 boolean foundCurrentSubtypeBecameDisabled = true; 136 mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList( 137 null, true); 138 mEnabledLanguagesOfCurrentInputMethod.clear(); 139 mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); 140 for (InputMethodSubtype ims: mAllEnabledSubtypesOfCurrentInputMethod) { 141 final String locale = ims.getLocale(); 142 final String mode = ims.getMode(); 143 mLocaleSplitter.setString(locale); 144 if (mLocaleSplitter.hasNext()) { 145 mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next()); 146 } 147 if (locale.equals(mInputLocaleStr) && mode.equals(mMode)) { 148 foundCurrentSubtypeBecameDisabled = false; 149 } 150 if (KEYBOARD_MODE.equals(ims.getMode())) { 151 mEnabledKeyboardSubtypesOfCurrentInputMethod.add(ims); 152 } 153 } 154 mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 155 && mIsSystemLanguageSameAsInputLanguage); 156 if (foundCurrentSubtypeBecameDisabled) { 157 if (DBG) { 158 Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + mMode); 159 Log.w(TAG, "Last subtype was disabled. Update to the current one."); 160 } 161 updateSubtype(mImm.getCurrentInputMethodSubtype()); 162 } 163 } 164 165 private void updateShortcutIME() { 166 // TODO: Update an icon for shortcut IME 167 Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = 168 mImm.getShortcutInputMethodsAndSubtypes(); 169 for (InputMethodInfo imi: shortcuts.keySet()) { 170 List<InputMethodSubtype> subtypes = shortcuts.get(imi); 171 // TODO: Returns the first found IMI for now. Should handle all shortcuts as 172 // appropriate. 173 mShortcutInfo = imi; 174 // TODO: Pick up the first found subtype for now. Should handle all subtypes 175 // as appropriate. 176 mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; 177 break; 178 } 179 } 180 181 // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. 182 public void updateSubtype(InputMethodSubtype newSubtype) { 183 final String newLocale; 184 final String newMode; 185 if (newSubtype == null) { 186 // Normally, newSubtype shouldn't be null. But just in case newSubtype was null, 187 // fallback to the default locale and mode. 188 Log.w(TAG, "Couldn't get the current subtype."); 189 newLocale = "en_US"; 190 newMode = KEYBOARD_MODE; 191 } else { 192 newLocale = newSubtype.getLocale(); 193 newMode = newSubtype.getMode(); 194 } 195 if (DBG) { 196 Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode 197 + ", from: " + mInputLocaleStr + ", " + mMode); 198 } 199 boolean languageChanged = false; 200 if (!newLocale.equals(mInputLocaleStr)) { 201 if (mInputLocaleStr != null) { 202 languageChanged = true; 203 } 204 updateInputLocale(newLocale); 205 } 206 boolean modeChanged = false; 207 String oldMode = mMode; 208 if (!newMode.equals(mMode)) { 209 if (mMode != null) { 210 modeChanged = true; 211 } 212 mMode = newMode; 213 } 214 if (isKeyboardMode()) { 215 if (modeChanged) { 216 if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { 217 mVoiceInput.cancel(); 218 } 219 } 220 if (modeChanged || languageChanged) { 221 mService.onRefreshKeyboard(); 222 } 223 } else if (isVoiceMode()) { 224 // If needsToShowWarningDialog is true, voice input need to show warning before 225 // show recognition view. 226 if (languageChanged || modeChanged 227 || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) { 228 if (mVoiceInput != null) { 229 triggerVoiceIME(); 230 } 231 } 232 } else { 233 Log.w(TAG, "Unknown subtype mode: " + mMode); 234 } 235 } 236 237 // Update the current input locale from Locale string. 238 private void updateInputLocale(String inputLocaleStr) { 239 // example: inputLocaleStr = "en_US" "en" "" 240 // "en_US" --> language: en & country: US 241 // "en" --> language: en 242 // "" --> the system locale 243 mLocaleSplitter.setString(inputLocaleStr); 244 if (mLocaleSplitter.hasNext()) { 245 String language = mLocaleSplitter.next(); 246 if (mLocaleSplitter.hasNext()) { 247 mInputLocale = new Locale(language, mLocaleSplitter.next()); 248 } else { 249 mInputLocale = new Locale(language); 250 } 251 mInputLocaleStr = inputLocaleStr; 252 } else { 253 mInputLocale = mSystemLocale; 254 String country = mSystemLocale.getCountry(); 255 mInputLocaleStr = mSystemLocale.getLanguage() 256 + (TextUtils.isEmpty(country) ? "" : "_" + mSystemLocale.getLanguage()); 257 } 258 mIsSystemLanguageSameAsInputLanguage = getSystemLocale().getLanguage().equalsIgnoreCase( 259 getInputLocale().getLanguage()); 260 mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 261 && mIsSystemLanguageSameAsInputLanguage); 262 } 263 264 //////////////////////////// 265 // Shortcut IME functions // 266 //////////////////////////// 267 268 public void switchToShortcutIME() { 269 IBinder token = mService.getWindow().getWindow().getAttributes().token; 270 if (token == null || mShortcutInfo == null) { 271 return; 272 } 273 mImm.setInputMethodAndSubtype(token, mShortcutInfo.getId(), mShortcutSubtype); 274 } 275 276 public Drawable getShortcutIcon() { 277 return getSubtypeIcon(mShortcutInfo, mShortcutSubtype); 278 } 279 280 private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) { 281 final PackageManager pm = mService.getPackageManager(); 282 if (imi != null) { 283 final String imiPackageName = imi.getPackageName(); 284 if (DBG) { 285 Log.d(TAG, "Update icons of IME: " + imiPackageName + "," 286 + subtype.getLocale() + "," + subtype.getMode()); 287 } 288 if (subtype != null) { 289 return pm.getDrawable(imiPackageName, subtype.getIconResId(), 290 imi.getServiceInfo().applicationInfo); 291 } else if (imi.getSubtypeCount() > 0 && imi.getSubtypeAt(0) != null) { 292 return pm.getDrawable(imiPackageName, 293 imi.getSubtypeAt(0).getIconResId(), 294 imi.getServiceInfo().applicationInfo); 295 } else { 296 try { 297 return pm.getApplicationInfo(imiPackageName, 0).loadIcon(pm); 298 } catch (PackageManager.NameNotFoundException e) { 299 Log.w(TAG, "IME can't be found: " + imiPackageName); 300 } 301 } 302 } 303 return null; 304 } 305 306 ////////////////////////////////// 307 // Language Switching functions // 308 ////////////////////////////////// 309 310 public int getEnabledKeyboardLocaleCount() { 311 if (mConfigUseSpacebarLanguageSwitcher) { 312 return mLanguageSwitcher.getLocaleCount(); 313 } else { 314 return mEnabledKeyboardSubtypesOfCurrentInputMethod.size(); 315 } 316 } 317 318 public boolean useSpacebarLanguageSwitcher() { 319 return mConfigUseSpacebarLanguageSwitcher; 320 } 321 322 public boolean needsToDisplayLanguage() { 323 return mNeedsToDisplayLanguage; 324 } 325 326 public Locale getInputLocale() { 327 if (mConfigUseSpacebarLanguageSwitcher) { 328 return mLanguageSwitcher.getInputLocale(); 329 } else { 330 return mInputLocale; 331 } 332 } 333 334 public String getInputLocaleStr() { 335 if (mConfigUseSpacebarLanguageSwitcher) { 336 String inputLanguage = null; 337 inputLanguage = mLanguageSwitcher.getInputLanguage(); 338 // Should return system locale if there is no Language available. 339 if (inputLanguage == null) { 340 inputLanguage = getSystemLocale().getLanguage(); 341 } 342 return inputLanguage; 343 } else { 344 return mInputLocaleStr; 345 } 346 } 347 348 public String[] getEnabledLanguages() { 349 if (mConfigUseSpacebarLanguageSwitcher) { 350 return mLanguageSwitcher.getEnabledLanguages(); 351 } else { 352 return mEnabledLanguagesOfCurrentInputMethod.toArray( 353 new String[mEnabledLanguagesOfCurrentInputMethod.size()]); 354 } 355 } 356 357 public Locale getSystemLocale() { 358 if (mConfigUseSpacebarLanguageSwitcher) { 359 return mLanguageSwitcher.getSystemLocale(); 360 } else { 361 return mSystemLocale; 362 } 363 } 364 365 public boolean isSystemLanguageSameAsInputLanguage() { 366 if (mConfigUseSpacebarLanguageSwitcher) { 367 return getSystemLocale().getLanguage().equalsIgnoreCase( 368 getInputLocaleStr().substring(0, 2)); 369 } else { 370 return mIsSystemLanguageSameAsInputLanguage; 371 } 372 } 373 374 public void onConfigurationChanged(Configuration conf) { 375 final Locale systemLocale = conf.locale; 376 // If system configuration was changed, update all parameters. 377 if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) { 378 if (mConfigUseSpacebarLanguageSwitcher) { 379 // If the system locale changes and is different from the saved 380 // locale (mSystemLocale), then reload the input locale list from the 381 // latin ime settings (shared prefs) and reset the input locale 382 // to the first one. 383 mLanguageSwitcher.loadLocales(mPrefs); 384 mLanguageSwitcher.setSystemLocale(systemLocale); 385 } else { 386 updateAllParameters(); 387 } 388 } 389 } 390 391 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 392 if (mConfigUseSpacebarLanguageSwitcher) { 393 if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) { 394 mLanguageSwitcher.loadLocales(sharedPreferences); 395 } 396 } 397 } 398 399 /** 400 * Change system locale for this application 401 * @param newLocale 402 * @return oldLocale 403 */ 404 public Locale changeSystemLocale(Locale newLocale) { 405 Configuration conf = mResources.getConfiguration(); 406 Locale oldLocale = conf.locale; 407 conf.locale = newLocale; 408 mResources.updateConfiguration(conf, mResources.getDisplayMetrics()); 409 return oldLocale; 410 } 411 412 public boolean isKeyboardMode() { 413 return KEYBOARD_MODE.equals(mMode); 414 } 415 416 417 /////////////////////////// 418 // Voice Input functions // 419 /////////////////////////// 420 421 public boolean setVoiceInput(VoiceInput vi) { 422 if (mVoiceInput == null && vi != null) { 423 mVoiceInput = vi; 424 if (isVoiceMode()) { 425 if (DBG) { 426 Log.d(TAG, "Set and call voice input."); 427 } 428 triggerVoiceIME(); 429 return true; 430 } 431 } 432 return false; 433 } 434 435 public boolean isVoiceMode() { 436 return VOICE_MODE.equals(mMode); 437 } 438 439 private void triggerVoiceIME() { 440 if (!mService.isInputViewShown()) return; 441 VoiceIMEConnector.getInstance().startListening(false, 442 KeyboardSwitcher.getInstance().getInputView().getWindowToken()); 443 } 444 445 ////////////////////////////////////// 446 // Spacebar Language Switch support // 447 ////////////////////////////////////// 448 449 private LanguageSwitcher mLanguageSwitcher; 450 451 public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) { 452 if (returnsNameInThisLocale) { 453 return toTitleCase(SubtypeLocale.getFullDisplayName(locale)); 454 } else { 455 return toTitleCase(locale.getDisplayName()); 456 } 457 } 458 459 public static String getDisplayLanguage(Locale locale) { 460 return toTitleCase(locale.getDisplayLanguage(locale)); 461 } 462 463 public static String getShortDisplayLanguage(Locale locale) { 464 return toTitleCase(locale.getLanguage()); 465 } 466 467 private static String toTitleCase(String s) { 468 if (s.length() == 0) { 469 return s; 470 } 471 return Character.toUpperCase(s.charAt(0)) + s.substring(1); 472 } 473 474 private void updateForSpacebarLanguageSwitch() { 475 // We need to update mNeedsToDisplayLanguage in onStartInputView because 476 // getEnabledKeyboardLocaleCount could have been changed. 477 mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 478 && getSystemLocale().getLanguage().equalsIgnoreCase( 479 getInputLocale().getLanguage())); 480 } 481 482 public String getInputLanguageName() { 483 return getDisplayLanguage(getInputLocale()); 484 } 485 486 public String getNextInputLanguageName() { 487 if (mConfigUseSpacebarLanguageSwitcher) { 488 return getDisplayLanguage(mLanguageSwitcher.getNextInputLocale()); 489 } else { 490 return ""; 491 } 492 } 493 494 public String getPreviousInputLanguageName() { 495 if (mConfigUseSpacebarLanguageSwitcher) { 496 return getDisplayLanguage(mLanguageSwitcher.getPrevInputLocale()); 497 } else { 498 return ""; 499 } 500 } 501 502 // A list of locales which are supported by default for voice input, unless we get a 503 // different list from Gservices. 504 private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = 505 "en " + 506 "en_US " + 507 "en_GB " + 508 "en_AU " + 509 "en_CA " + 510 "en_IE " + 511 "en_IN " + 512 "en_NZ " + 513 "en_SG " + 514 "en_ZA "; 515 516 public boolean isVoiceSupported(String locale) { 517 // Get the current list of supported locales and check the current locale against that 518 // list. We cache this value so as not to check it every time the user starts a voice 519 // input. Because this method is called by onStartInputView, this should mean that as 520 // long as the locale doesn't change while the user is keeping the IME open, the 521 // value should never be stale. 522 String supportedLocalesString = SettingsUtil.getSettingsString( 523 mService.getContentResolver(), 524 SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES, 525 DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES); 526 List<String> voiceInputSupportedLocales = Arrays.asList( 527 supportedLocalesString.split("\\s+")); 528 return voiceInputSupportedLocales.contains(locale); 529 } 530 531 public void loadSettings() { 532 if (mConfigUseSpacebarLanguageSwitcher) { 533 mLanguageSwitcher.loadLocales(mPrefs); 534 } 535 } 536 537 public void toggleLanguage(boolean reset, boolean next) { 538 if (mConfigUseSpacebarLanguageSwitcher) { 539 if (reset) { 540 mLanguageSwitcher.reset(); 541 } else { 542 if (next) { 543 mLanguageSwitcher.next(); 544 } else { 545 mLanguageSwitcher.prev(); 546 } 547 } 548 mLanguageSwitcher.persist(mPrefs); 549 } 550 } 551 552 private void initLanguageSwitcher(LatinIME service) { 553 final Configuration conf = service.getResources().getConfiguration(); 554 mLanguageSwitcher = new LanguageSwitcher(service); 555 mLanguageSwitcher.loadLocales(mPrefs); 556 mLanguageSwitcher.setSystemLocale(conf.locale); 557 } 558} 559