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