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