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