KeyboardSwitcher.java revision 0a054dd1075d679385434ecef38a3d88c7551d7a
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.keyboard; 18 19import android.content.Context; 20import android.content.SharedPreferences; 21import android.content.res.Resources; 22import android.util.Log; 23import android.view.ContextThemeWrapper; 24import android.view.InflateException; 25import android.view.LayoutInflater; 26import android.view.View; 27import android.view.inputmethod.EditorInfo; 28 29import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 30import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; 31import com.android.inputmethod.keyboard.internal.ModifierKeyState; 32import com.android.inputmethod.keyboard.internal.ShiftKeyState; 33import com.android.inputmethod.latin.LatinIME; 34import com.android.inputmethod.latin.LatinImeLogger; 35import com.android.inputmethod.latin.R; 36import com.android.inputmethod.latin.Settings; 37import com.android.inputmethod.latin.SubtypeSwitcher; 38import com.android.inputmethod.latin.Utils; 39 40import java.lang.ref.SoftReference; 41import java.util.HashMap; 42import java.util.Locale; 43 44public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener { 45 private static final String TAG = KeyboardSwitcher.class.getSimpleName(); 46 private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG; 47 public static final boolean DEBUG_STATE = false; 48 49 public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902"; 50 private static final int[] KEYBOARD_THEMES = { 51 R.style.KeyboardTheme, 52 R.style.KeyboardTheme_HighContrast, 53 R.style.KeyboardTheme_Stone, 54 R.style.KeyboardTheme_Stone_Bold, 55 R.style.KeyboardTheme_Gingerbread, 56 R.style.KeyboardTheme_IceCreamSandwich, 57 }; 58 59 private SubtypeSwitcher mSubtypeSwitcher; 60 private SharedPreferences mPrefs; 61 62 private View mCurrentInputView; 63 private LatinKeyboardView mKeyboardView; 64 private LatinIME mInputMethodService; 65 private String mPackageName; 66 private Resources mResources; 67 68 // TODO: Combine these key state objects with auto mode switch state. 69 private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift"); 70 private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol"); 71 72 private KeyboardId mMainKeyboardId; 73 private KeyboardId mSymbolsKeyboardId; 74 private KeyboardId mSymbolsShiftedKeyboardId; 75 76 private KeyboardId mCurrentId; 77 private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache = 78 new HashMap<KeyboardId, SoftReference<LatinKeyboard>>(); 79 80 /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of 81 * what user actually typed. */ 82 private boolean mIsAutoCorrectionActive; 83 84 // TODO: Encapsulate these state handling to separate class and combine with ShiftKeyState 85 // and ModifierKeyState. 86 private static final int SWITCH_STATE_ALPHA = 0; 87 private static final int SWITCH_STATE_SYMBOL_BEGIN = 1; 88 private static final int SWITCH_STATE_SYMBOL = 2; 89 // The following states are used only on the distinct multi-touch panel devices. 90 private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3; 91 private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4; 92 private static final int SWITCH_STATE_CHORDING_ALPHA = 5; 93 private static final int SWITCH_STATE_CHORDING_SYMBOL = 6; 94 private int mSwitchState = SWITCH_STATE_ALPHA; 95 96 private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto; 97 private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW = 98 R.string.settings_key_mode_always_show; 99 // NOTE: No need to have SETTINGS_KEY_MODE_ALWAYS_HIDE here because it's not being referred to 100 // in the source code now. 101 // Default is SETTINGS_KEY_MODE_AUTO. 102 private static final int DEFAULT_SETTINGS_KEY_MODE = SETTINGS_KEY_MODE_AUTO; 103 104 private int mThemeIndex = -1; 105 private Context mThemeContext; 106 private int mWindowWidth; 107 108 private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); 109 110 public static KeyboardSwitcher getInstance() { 111 return sInstance; 112 } 113 114 private KeyboardSwitcher() { 115 // Intentional empty constructor for singleton. 116 } 117 118 public static void init(LatinIME ims, SharedPreferences prefs) { 119 sInstance.mInputMethodService = ims; 120 sInstance.mPackageName = ims.getPackageName(); 121 sInstance.mResources = ims.getResources(); 122 sInstance.mPrefs = prefs; 123 sInstance.mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 124 sInstance.setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs)); 125 prefs.registerOnSharedPreferenceChangeListener(sInstance); 126 } 127 128 private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) { 129 final String defaultThemeId = context.getString(R.string.config_default_keyboard_theme_id); 130 final String themeId = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultThemeId); 131 try { 132 final int themeIndex = Integer.valueOf(themeId); 133 if (themeIndex >= 0 && themeIndex < KEYBOARD_THEMES.length) 134 return themeIndex; 135 } catch (NumberFormatException e) { 136 // Format error, keyboard theme is default to 0. 137 } 138 Log.w(TAG, "Illegal keyboard theme in preference: " + themeId + ", default to 0"); 139 return 0; 140 } 141 142 private void setContextThemeWrapper(Context context, int themeIndex) { 143 if (mThemeIndex != themeIndex) { 144 mThemeIndex = themeIndex; 145 mThemeContext = new ContextThemeWrapper(context, KEYBOARD_THEMES[themeIndex]); 146 mKeyboardCache.clear(); 147 } 148 } 149 150 public void loadKeyboard(EditorInfo attribute, Settings.Values settings) { 151 mSwitchState = SWITCH_STATE_ALPHA; 152 try { 153 final Locale locale = mSubtypeSwitcher.getInputLocale(); 154 final int orientation = mResources.getConfiguration().orientation; 155 final int width = (mWindowWidth != 0) ? mWindowWidth 156 : mResources.getDisplayMetrics().widthPixels; 157 final boolean voiceKeyEnabled = settings.isVoiceKeyEnabled(attribute); 158 final boolean voiceKeyOnMain = settings.isVoiceKeyOnMain(); 159 mMainKeyboardId = getKeyboardId(attribute, locale, orientation, width, 160 false, false, voiceKeyEnabled, voiceKeyOnMain); 161 mSymbolsKeyboardId = getKeyboardId(attribute, locale, orientation, width, 162 true, false, voiceKeyEnabled, voiceKeyOnMain); 163 mSymbolsShiftedKeyboardId = getKeyboardId(attribute, locale, orientation, width, 164 true, true, voiceKeyEnabled, voiceKeyOnMain); 165 setKeyboard(getKeyboard(mMainKeyboardId)); 166 } catch (RuntimeException e) { 167 Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e); 168 LatinImeLogger.logOnException(mMainKeyboardId.toString(), e); 169 } 170 } 171 172 public void onHideWindow() { 173 mWindowWidth = 0; 174 mIsAutoCorrectionActive = false; 175 } 176 177 @SuppressWarnings("unused") 178 public void onSizeChanged(int w, int h, int oldw, int oldh) { 179 // TODO: This hack should be removed when display metric returns a proper width. 180 // Until then, the behavior of KeyboardSwitcher is suboptimal on a device that has a 181 // vertical system navigation bar in landscape screen orientation, for instance. 182 final int width = mInputMethodService.getWindow().getWindow().getDecorView().getWidth(); 183 // If the window width hasn't fixed yet or keyboard doesn't exist, nothing to do with. 184 if (width == 0 || mCurrentId == null) 185 return; 186 // The window width is fixed. 187 mWindowWidth = width; 188 // Reload keyboard with new width. 189 final int orientation = mInputMethodService.getResources().getConfiguration().orientation; 190 final KeyboardId newId = mCurrentId.cloneWithNewGeometry(orientation, width); 191 setKeyboard(getKeyboard(newId)); 192 } 193 194 private void setKeyboard(final Keyboard newKeyboard) { 195 final Keyboard oldKeyboard = mKeyboardView.getKeyboard(); 196 mKeyboardView.setKeyboard(newKeyboard); 197 mCurrentId = newKeyboard.mId; 198 mKeyboardView.setKeyPreviewPopupEnabled( 199 Settings.Values.isKeyPreviewPopupEnabled(mPrefs, mResources), 200 Settings.Values.getKeyPreviewPopupDismissDelay(mPrefs, mResources)); 201 final boolean localeChanged = (oldKeyboard == null) 202 || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); 203 mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); 204 } 205 206 private LatinKeyboard getKeyboard(KeyboardId id) { 207 final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id); 208 LatinKeyboard keyboard = (ref == null) ? null : ref.get(); 209 if (keyboard == null) { 210 final Locale savedLocale = Utils.setSystemLocale( 211 mResources, mSubtypeSwitcher.getInputLocale()); 212 213 keyboard = new LatinKeyboard(mThemeContext, id, id.mWidth); 214 mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard)); 215 if (DEBUG_CACHE) 216 Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": " 217 + ((ref == null) ? "LOAD" : "GCed") + " id=" + id); 218 219 Utils.setSystemLocale(mResources, savedLocale); 220 } else if (DEBUG_CACHE) { 221 Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT id=" + id); 222 } 223 224 keyboard.onAutoCorrectionStateChanged(mIsAutoCorrectionActive); 225 keyboard.setShifted(false); 226 // If the cached keyboard had been switched to another keyboard while the language was 227 // displayed on its spacebar, it might have had arbitrary text fade factor. In such case, 228 // we should reset the text fade factor. It is also applicable to shortcut key. 229 keyboard.setSpacebarTextFadeFactor(0.0f, null); 230 keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null); 231 return keyboard; 232 } 233 234 private KeyboardId getKeyboardId(EditorInfo attribute, Locale locale, final int orientation, 235 final int width, final boolean isSymbols, final boolean isShift, 236 final boolean voiceKeyEnabled, final boolean voiceKeyOnMain) { 237 final int mode = Utils.getKeyboardMode(attribute); 238 final int xmlId; 239 switch (mode) { 240 case KeyboardId.MODE_PHONE: 241 xmlId = (isSymbols && isShift) ? R.xml.kbd_phone_shift : R.xml.kbd_phone; 242 break; 243 case KeyboardId.MODE_NUMBER: 244 xmlId = R.xml.kbd_number; 245 break; 246 default: 247 if (isSymbols) { 248 xmlId = isShift ? R.xml.kbd_symbols_shift : R.xml.kbd_symbols; 249 } else { 250 xmlId = R.xml.kbd_qwerty; 251 } 252 break; 253 } 254 255 final boolean noSettingsKey = Utils.inPrivateImeOptions( 256 mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, attribute); 257 final boolean hasSettingsKey = getSettingsKeyMode(mPrefs, mResources) && !noSettingsKey; 258 final int f2KeyMode = getF2KeyMode(mPrefs, mResources, mPackageName, attribute); 259 final boolean hasVoiceKey = voiceKeyEnabled && (isSymbols != voiceKeyOnMain); 260 261 return new KeyboardId( 262 mResources.getResourceEntryName(xmlId), xmlId, locale, 263 orientation, width, mode, attribute, hasSettingsKey, f2KeyMode, noSettingsKey, 264 voiceKeyEnabled, hasVoiceKey); 265 } 266 267 public int getKeyboardMode() { 268 return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT; 269 } 270 271 public boolean isAlphabetMode() { 272 return mCurrentId != null && mCurrentId.isAlphabetKeyboard(); 273 } 274 275 public boolean isInputViewShown() { 276 return mCurrentInputView != null && mCurrentInputView.isShown(); 277 } 278 279 public boolean isKeyboardAvailable() { 280 if (mKeyboardView != null) 281 return mKeyboardView.getKeyboard() != null; 282 return false; 283 } 284 285 public LatinKeyboard getLatinKeyboard() { 286 if (mKeyboardView != null) { 287 final Keyboard keyboard = mKeyboardView.getKeyboard(); 288 if (keyboard instanceof LatinKeyboard) 289 return (LatinKeyboard)keyboard; 290 } 291 return null; 292 } 293 294 public boolean isShiftedOrShiftLocked() { 295 LatinKeyboard latinKeyboard = getLatinKeyboard(); 296 if (latinKeyboard != null) 297 return latinKeyboard.isShiftedOrShiftLocked(); 298 return false; 299 } 300 301 public boolean isShiftLocked() { 302 LatinKeyboard latinKeyboard = getLatinKeyboard(); 303 if (latinKeyboard != null) 304 return latinKeyboard.isShiftLocked(); 305 return false; 306 } 307 308 public boolean isAutomaticTemporaryUpperCase() { 309 LatinKeyboard latinKeyboard = getLatinKeyboard(); 310 if (latinKeyboard != null) 311 return latinKeyboard.isAutomaticTemporaryUpperCase(); 312 return false; 313 } 314 315 public boolean isManualTemporaryUpperCase() { 316 LatinKeyboard latinKeyboard = getLatinKeyboard(); 317 if (latinKeyboard != null) 318 return latinKeyboard.isManualTemporaryUpperCase(); 319 return false; 320 } 321 322 private boolean isManualTemporaryUpperCaseFromAuto() { 323 LatinKeyboard latinKeyboard = getLatinKeyboard(); 324 if (latinKeyboard != null) 325 return latinKeyboard.isManualTemporaryUpperCaseFromAuto(); 326 return false; 327 } 328 329 private void setManualTemporaryUpperCase(boolean shifted) { 330 LatinKeyboard latinKeyboard = getLatinKeyboard(); 331 if (latinKeyboard != null) { 332 // On non-distinct multi touch panel device, we should also turn off the shift locked 333 // state when shift key is pressed to go to normal mode. 334 // On the other hand, on distinct multi touch panel device, turning off the shift locked 335 // state with shift key pressing is handled by onReleaseShift(). 336 if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) { 337 latinKeyboard.setShiftLocked(false); 338 } 339 if (latinKeyboard.setShifted(shifted)) { 340 mKeyboardView.invalidateAllKeys(); 341 } 342 } 343 } 344 345 private void setShiftLocked(boolean shiftLocked) { 346 LatinKeyboard latinKeyboard = getLatinKeyboard(); 347 if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) { 348 mKeyboardView.invalidateAllKeys(); 349 } 350 } 351 352 /** 353 * Toggle keyboard shift state triggered by user touch event. 354 */ 355 public void toggleShift() { 356 mInputMethodService.mHandler.cancelUpdateShiftState(); 357 if (DEBUG_STATE) 358 Log.d(TAG, "toggleShift:" 359 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 360 + " shiftKeyState=" + mShiftKeyState); 361 if (isAlphabetMode()) { 362 setManualTemporaryUpperCase(!isShiftedOrShiftLocked()); 363 } else { 364 toggleShiftInSymbol(); 365 } 366 } 367 368 public void toggleCapsLock() { 369 mInputMethodService.mHandler.cancelUpdateShiftState(); 370 if (DEBUG_STATE) 371 Log.d(TAG, "toggleCapsLock:" 372 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 373 + " shiftKeyState=" + mShiftKeyState); 374 if (isAlphabetMode()) { 375 if (isShiftLocked()) { 376 // Shift key is long pressed while caps lock state, we will toggle back to normal 377 // state. And mark as if shift key is released. 378 setShiftLocked(false); 379 mShiftKeyState.onRelease(); 380 } else { 381 setShiftLocked(true); 382 } 383 } 384 } 385 386 private void setAutomaticTemporaryUpperCase() { 387 LatinKeyboard latinKeyboard = getLatinKeyboard(); 388 if (latinKeyboard != null) { 389 latinKeyboard.setAutomaticTemporaryUpperCase(); 390 mKeyboardView.invalidateAllKeys(); 391 } 392 } 393 394 /** 395 * Update keyboard shift state triggered by connected EditText status change. 396 */ 397 public void updateShiftState() { 398 final ShiftKeyState shiftKeyState = mShiftKeyState; 399 if (DEBUG_STATE) 400 Log.d(TAG, "updateShiftState:" 401 + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState() 402 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 403 + " shiftKeyState=" + shiftKeyState); 404 if (isAlphabetMode()) { 405 if (!isShiftLocked() && !shiftKeyState.isIgnoring()) { 406 if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) { 407 // Only when shift key is releasing, automatic temporary upper case will be set. 408 setAutomaticTemporaryUpperCase(); 409 } else { 410 setManualTemporaryUpperCase(shiftKeyState.isMomentary()); 411 } 412 } 413 } else { 414 // In symbol keyboard mode, we should clear shift key state because only alphabet 415 // keyboard has shift key. 416 shiftKeyState.onRelease(); 417 } 418 } 419 420 public void changeKeyboardMode() { 421 if (DEBUG_STATE) 422 Log.d(TAG, "changeKeyboardMode:" 423 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 424 + " shiftKeyState=" + mShiftKeyState); 425 toggleKeyboardMode(); 426 if (isShiftLocked() && isAlphabetMode()) 427 setShiftLocked(true); 428 updateShiftState(); 429 } 430 431 public void onPressShift(boolean withSliding) { 432 if (!isKeyboardAvailable()) 433 return; 434 ShiftKeyState shiftKeyState = mShiftKeyState; 435 if (DEBUG_STATE) 436 Log.d(TAG, "onPressShift:" 437 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 438 + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding); 439 if (isAlphabetMode()) { 440 if (isShiftLocked()) { 441 // Shift key is pressed while caps lock state, we will treat this state as shifted 442 // caps lock state and mark as if shift key pressed while normal state. 443 shiftKeyState.onPress(); 444 setManualTemporaryUpperCase(true); 445 } else if (isAutomaticTemporaryUpperCase()) { 446 // Shift key is pressed while automatic temporary upper case, we have to move to 447 // manual temporary upper case. 448 shiftKeyState.onPress(); 449 setManualTemporaryUpperCase(true); 450 } else if (isShiftedOrShiftLocked()) { 451 // In manual upper case state, we just record shift key has been pressing while 452 // shifted state. 453 shiftKeyState.onPressOnShifted(); 454 } else { 455 // In base layout, chording or manual temporary upper case mode is started. 456 shiftKeyState.onPress(); 457 toggleShift(); 458 } 459 } else { 460 // In symbol mode, just toggle symbol and symbol more keyboard. 461 shiftKeyState.onPress(); 462 toggleShift(); 463 mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; 464 } 465 } 466 467 public void onReleaseShift(boolean withSliding) { 468 if (!isKeyboardAvailable()) 469 return; 470 ShiftKeyState shiftKeyState = mShiftKeyState; 471 if (DEBUG_STATE) 472 Log.d(TAG, "onReleaseShift:" 473 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 474 + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding); 475 if (isAlphabetMode()) { 476 if (shiftKeyState.isMomentary()) { 477 // After chording input while normal state. 478 toggleShift(); 479 } else if (isShiftLocked() && !shiftKeyState.isIgnoring() && !withSliding) { 480 // Shift has been pressed without chording while caps lock state. 481 toggleCapsLock(); 482 // To be able to turn off caps lock by "double tap" on shift key, we should ignore 483 // the second tap of the "double tap" from now for a while because we just have 484 // already turned off caps lock above. 485 mKeyboardView.startIgnoringDoubleTap(); 486 } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted() 487 && !withSliding) { 488 // Shift has been pressed without chording while shifted state. 489 toggleShift(); 490 } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing() 491 && !withSliding) { 492 // Shift has been pressed without chording while manual temporary upper case 493 // transited from automatic temporary upper case. 494 toggleShift(); 495 } 496 } else { 497 // In symbol mode, snap back to the previous keyboard mode if the user chords the shift 498 // key and another key, then releases the shift key. 499 if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) { 500 toggleShift(); 501 } 502 } 503 shiftKeyState.onRelease(); 504 } 505 506 public void onPressSymbol() { 507 if (DEBUG_STATE) 508 Log.d(TAG, "onPressSymbol:" 509 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 510 + " symbolKeyState=" + mSymbolKeyState); 511 changeKeyboardMode(); 512 mSymbolKeyState.onPress(); 513 mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; 514 } 515 516 public void onReleaseSymbol() { 517 if (DEBUG_STATE) 518 Log.d(TAG, "onReleaseSymbol:" 519 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 520 + " symbolKeyState=" + mSymbolKeyState); 521 // Snap back to the previous keyboard mode if the user chords the mode change key and 522 // another key, then releases the mode change key. 523 if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) { 524 changeKeyboardMode(); 525 } 526 mSymbolKeyState.onRelease(); 527 } 528 529 public void onOtherKeyPressed() { 530 if (DEBUG_STATE) 531 Log.d(TAG, "onOtherKeyPressed:" 532 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 533 + " shiftKeyState=" + mShiftKeyState 534 + " symbolKeyState=" + mSymbolKeyState); 535 mShiftKeyState.onOtherKeyPressed(); 536 mSymbolKeyState.onOtherKeyPressed(); 537 } 538 539 public void onCancelInput() { 540 // Snap back to the previous keyboard mode if the user cancels sliding input. 541 if (getPointerCount() == 1) { 542 if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) { 543 changeKeyboardMode(); 544 } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) { 545 toggleShift(); 546 } 547 } 548 } 549 550 private void toggleShiftInSymbol() { 551 if (isAlphabetMode()) 552 return; 553 final LatinKeyboard keyboard; 554 if (mCurrentId.equals(mSymbolsKeyboardId) 555 || !mCurrentId.equals(mSymbolsShiftedKeyboardId)) { 556 keyboard = getKeyboard(mSymbolsShiftedKeyboardId); 557 // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a. 558 // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked() 559 // that takes care of the current keyboard having such ALT key or not. 560 keyboard.setShiftLocked(keyboard.hasShiftLockKey()); 561 } else { 562 keyboard = getKeyboard(mSymbolsKeyboardId); 563 // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the 564 // indicator, we need to call setShiftLocked(false). 565 keyboard.setShiftLocked(false); 566 } 567 setKeyboard(keyboard); 568 } 569 570 public boolean isInMomentarySwitchState() { 571 return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL 572 || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; 573 } 574 575 public boolean isVibrateAndSoundFeedbackRequired() { 576 return mKeyboardView == null || !mKeyboardView.isInSlidingKeyInput(); 577 } 578 579 private int getPointerCount() { 580 return mKeyboardView == null ? 0 : mKeyboardView.getPointerCount(); 581 } 582 583 private void toggleKeyboardMode() { 584 if (mCurrentId.equals(mMainKeyboardId)) { 585 setKeyboard(getKeyboard(mSymbolsKeyboardId)); 586 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 587 } else { 588 setKeyboard(getKeyboard(mMainKeyboardId)); 589 mSwitchState = SWITCH_STATE_ALPHA; 590 } 591 } 592 593 public boolean hasDistinctMultitouch() { 594 return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); 595 } 596 597 private static boolean isSpaceCharacter(int c) { 598 return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER; 599 } 600 601 private static boolean isQuoteCharacter(int c) { 602 // Apostrophe, quotation mark. 603 if (c == Keyboard.CODE_SINGLE_QUOTE || c == Keyboard.CODE_DOUBLE_QUOTE) 604 return true; 605 // \u2018: Left single quotation mark 606 // \u2019: Right single quotation mark 607 // \u201a: Single low-9 quotation mark 608 // \u201b: Single high-reversed-9 quotation mark 609 // \u201c: Left double quotation mark 610 // \u201d: Right double quotation mark 611 // \u201e: Double low-9 quotation mark 612 // \u201f: Double high-reversed-9 quotation mark 613 if (c >= '\u2018' && c <= '\u201f') 614 return true; 615 // \u00ab: Left-pointing double angle quotation mark 616 // \u00bb: Right-pointing double angle quotation mark 617 if (c == '\u00ab' || c == '\u00bb') 618 return true; 619 return false; 620 } 621 622 /** 623 * Updates state machine to figure out when to automatically snap back to the previous mode. 624 */ 625 public void onKey(int code) { 626 if (DEBUG_STATE) 627 Log.d(TAG, "onKey: code=" + code + " switchState=" + mSwitchState 628 + " pointers=" + getPointerCount()); 629 switch (mSwitchState) { 630 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: 631 // Only distinct multi touch devices can be in this state. 632 // On non-distinct multi touch devices, mode change key is handled by 633 // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and 634 // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts 635 // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from 636 // {@link #SWITCH_STATE_MOMENTARY}. 637 if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 638 // Detected only the mode change key has been pressed, and then released. 639 if (mCurrentId.equals(mMainKeyboardId)) { 640 mSwitchState = SWITCH_STATE_ALPHA; 641 } else { 642 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 643 } 644 } else if (getPointerCount() == 1) { 645 // Snap back to the previous keyboard mode if the user pressed the mode change key 646 // and slid to other key, then released the finger. 647 // If the user cancels the sliding input, snapping back to the previous keyboard 648 // mode is handled by {@link #onCancelInput}. 649 changeKeyboardMode(); 650 } else { 651 // Chording input is being started. The keyboard mode will be snapped back to the 652 // previous mode in {@link onReleaseSymbol} when the mode change key is released. 653 mSwitchState = SWITCH_STATE_CHORDING_ALPHA; 654 } 655 break; 656 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: 657 if (code == Keyboard.CODE_SHIFT) { 658 // Detected only the shift key has been pressed on symbol layout, and then released. 659 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 660 } else if (getPointerCount() == 1) { 661 // Snap back to the previous keyboard mode if the user pressed the shift key on 662 // symbol mode and slid to other key, then released the finger. 663 toggleShift(); 664 mSwitchState = SWITCH_STATE_SYMBOL; 665 } else { 666 // Chording input is being started. The keyboard mode will be snapped back to the 667 // previous mode in {@link onReleaseShift} when the shift key is released. 668 mSwitchState = SWITCH_STATE_CHORDING_SYMBOL; 669 } 670 break; 671 case SWITCH_STATE_SYMBOL_BEGIN: 672 if (!isSpaceCharacter(code) && code >= 0) { 673 mSwitchState = SWITCH_STATE_SYMBOL; 674 } 675 // Snap back to alpha keyboard mode immediately if user types a quote character. 676 if (isQuoteCharacter(code)) { 677 changeKeyboardMode(); 678 } 679 break; 680 case SWITCH_STATE_SYMBOL: 681 case SWITCH_STATE_CHORDING_SYMBOL: 682 // Snap back to alpha keyboard mode if user types one or more non-space/enter 683 // characters followed by a space/enter or a quote character. 684 if (isSpaceCharacter(code) || isQuoteCharacter(code)) { 685 changeKeyboardMode(); 686 } 687 break; 688 } 689 } 690 691 public LatinKeyboardView getKeyboardView() { 692 return mKeyboardView; 693 } 694 695 public View onCreateInputView() { 696 return createInputView(mThemeIndex, true); 697 } 698 699 private View createInputView(final int newThemeIndex, final boolean forceRecreate) { 700 if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate) 701 return mCurrentInputView; 702 703 if (mKeyboardView != null) { 704 mKeyboardView.closing(); 705 } 706 707 final int oldThemeIndex = mThemeIndex; 708 Utils.GCUtils.getInstance().reset(); 709 boolean tryGC = true; 710 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 711 try { 712 setContextThemeWrapper(mInputMethodService, newThemeIndex); 713 mCurrentInputView = LayoutInflater.from(mThemeContext).inflate( 714 R.layout.input_view, null); 715 tryGC = false; 716 } catch (OutOfMemoryError e) { 717 Log.w(TAG, "load keyboard failed: " + e); 718 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 719 oldThemeIndex + "," + newThemeIndex, e); 720 } catch (InflateException e) { 721 Log.w(TAG, "load keyboard failed: " + e); 722 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 723 oldThemeIndex + "," + newThemeIndex, e); 724 } 725 } 726 727 mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 728 mKeyboardView.setKeyboardActionListener(mInputMethodService); 729 730 // This always needs to be set since the accessibility state can 731 // potentially change without the input view being re-created. 732 AccessibleKeyboardViewProxy.setView(mKeyboardView); 733 734 return mCurrentInputView; 735 } 736 737 private void postSetInputView(final View newInputView) { 738 mInputMethodService.mHandler.post(new Runnable() { 739 @Override 740 public void run() { 741 if (newInputView != null) { 742 mInputMethodService.setInputView(newInputView); 743 } 744 mInputMethodService.updateInputViewShown(); 745 } 746 }); 747 } 748 749 @Override 750 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 751 if (PREF_KEYBOARD_LAYOUT.equals(key)) { 752 final int layoutId = getKeyboardThemeIndex(mInputMethodService, sharedPreferences); 753 postSetInputView(createInputView(layoutId, false)); 754 } else if (Settings.PREF_SETTINGS_KEY.equals(key)) { 755 postSetInputView(createInputView(mThemeIndex, true)); 756 } 757 } 758 759 public void onAutoCorrectionStateChanged(boolean isAutoCorrection) { 760 if (mIsAutoCorrectionActive != isAutoCorrection) { 761 mIsAutoCorrectionActive = isAutoCorrection; 762 final LatinKeyboard keyboard = getLatinKeyboard(); 763 if (keyboard != null && keyboard.needsAutoCorrectionSpacebarLed()) { 764 final Key invalidatedKey = keyboard.onAutoCorrectionStateChanged(isAutoCorrection); 765 final LatinKeyboardView keyboardView = getKeyboardView(); 766 if (keyboardView != null) 767 keyboardView.invalidateKey(invalidatedKey); 768 } 769 } 770 } 771 772 private static boolean getSettingsKeyMode(SharedPreferences prefs, Resources res) { 773 final boolean showSettingsKeyOption = res.getBoolean( 774 R.bool.config_enable_show_settings_key_option); 775 if (showSettingsKeyOption) { 776 final String settingsKeyMode = prefs.getString(Settings.PREF_SETTINGS_KEY, 777 res.getString(DEFAULT_SETTINGS_KEY_MODE)); 778 // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or 779 // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system 780 if (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW)) 781 || (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_AUTO)) 782 && Utils.hasMultipleEnabledIMEsOrSubtypes( 783 (InputMethodManagerCompatWrapper.getInstance())))) { 784 return true; 785 } 786 return false; 787 } 788 // If the show settings key option is disabled, we always try showing the settings key. 789 return true; 790 } 791 792 private static int getF2KeyMode(SharedPreferences prefs, Resources res, String packageName, 793 EditorInfo attribute) { 794 final boolean clobberSettingsKey = Utils.inPrivateImeOptions( 795 packageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, attribute); 796 final String settingsKeyMode = prefs.getString(Settings.PREF_SETTINGS_KEY, 797 res.getString(DEFAULT_SETTINGS_KEY_MODE)); 798 if (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_AUTO))) { 799 return clobberSettingsKey ? KeyboardId.F2KEY_MODE_SHORTCUT_IME 800 : KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS; 801 } else if (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))) { 802 return clobberSettingsKey ? KeyboardId.F2KEY_MODE_NONE : KeyboardId.F2KEY_MODE_SETTINGS; 803 } else { // SETTINGS_KEY_MODE_ALWAYS_HIDE 804 return KeyboardId.F2KEY_MODE_SHORTCUT_IME; 805 } 806 } 807} 808