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