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