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