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