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