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