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