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