KeyboardSwitcher.java revision 4199e29a7d796191d3e13ef07e6e80e91834fe7a
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.EditorInfo; 32import android.view.inputmethod.InputMethodManager; 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.isShortcutAvailable(), 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, true); 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, true); 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() && !shifted && latinKeyboard.isShiftLocked()) { 348 latinKeyboard.setShiftLocked(false); 349 } 350 if (latinKeyboard.setShifted(shifted)) { 351 mInputView.invalidateAllKeys(); 352 } 353 } 354 } 355 356 private void setShiftLocked(boolean shiftLocked) { 357 LatinKeyboard latinKeyboard = getLatinKeyboard(); 358 if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) { 359 mInputView.invalidateAllKeys(); 360 } 361 } 362 363 /** 364 * Toggle keyboard shift state triggered by user touch event. 365 */ 366 public void toggleShift() { 367 mInputMethodService.mHandler.cancelUpdateShiftState(); 368 if (DEBUG_STATE) 369 Log.d(TAG, "toggleShift:" 370 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 371 + " shiftKeyState=" + mShiftKeyState); 372 if (isAlphabetMode()) { 373 setManualTemporaryUpperCase(!isShiftedOrShiftLocked()); 374 } else { 375 toggleShiftInSymbol(); 376 } 377 } 378 379 public void toggleCapsLock() { 380 mInputMethodService.mHandler.cancelUpdateShiftState(); 381 if (DEBUG_STATE) 382 Log.d(TAG, "toggleCapsLock:" 383 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 384 + " shiftKeyState=" + mShiftKeyState); 385 if (isAlphabetMode()) { 386 if (isShiftLocked()) { 387 // Shift key is long pressed while caps lock state, we will toggle back to normal 388 // state. And mark as if shift key is released. 389 setShiftLocked(false); 390 mShiftKeyState.onRelease(); 391 } else { 392 setShiftLocked(true); 393 } 394 } 395 } 396 397 private void setAutomaticTemporaryUpperCase() { 398 LatinKeyboard latinKeyboard = getLatinKeyboard(); 399 if (latinKeyboard != null) { 400 latinKeyboard.setAutomaticTemporaryUpperCase(); 401 mInputView.invalidateAllKeys(); 402 } 403 } 404 405 /** 406 * Update keyboard shift state triggered by connected EditText status change. 407 */ 408 public void updateShiftState() { 409 final ShiftKeyState shiftKeyState = mShiftKeyState; 410 if (DEBUG_STATE) 411 Log.d(TAG, "updateShiftState:" 412 + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState() 413 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 414 + " shiftKeyState=" + shiftKeyState); 415 if (isAlphabetMode()) { 416 if (!isShiftLocked() && !shiftKeyState.isIgnoring()) { 417 if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) { 418 // Only when shift key is releasing, automatic temporary upper case will be set. 419 setAutomaticTemporaryUpperCase(); 420 } else { 421 setManualTemporaryUpperCase(shiftKeyState.isMomentary()); 422 } 423 } 424 } else { 425 // In symbol keyboard mode, we should clear shift key state because only alphabet 426 // keyboard has shift key. 427 shiftKeyState.onRelease(); 428 } 429 } 430 431 public void changeKeyboardMode() { 432 if (DEBUG_STATE) 433 Log.d(TAG, "changeKeyboardMode:" 434 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 435 + " shiftKeyState=" + mShiftKeyState); 436 toggleKeyboardMode(); 437 if (isShiftLocked() && isAlphabetMode()) 438 setShiftLocked(true); 439 updateShiftState(); 440 } 441 442 public void onPressShift() { 443 if (!isKeyboardAvailable()) 444 return; 445 ShiftKeyState shiftKeyState = mShiftKeyState; 446 if (DEBUG_STATE) 447 Log.d(TAG, "onPressShift:" 448 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 449 + " shiftKeyState=" + shiftKeyState); 450 if (isAlphabetMode()) { 451 if (isShiftLocked()) { 452 // Shift key is pressed while caps lock state, we will treat this state as shifted 453 // caps lock state and mark as if shift key pressed while normal state. 454 shiftKeyState.onPress(); 455 setManualTemporaryUpperCase(true); 456 } else if (isAutomaticTemporaryUpperCase()) { 457 // Shift key is pressed while automatic temporary upper case, we have to move to 458 // manual temporary upper case. 459 shiftKeyState.onPress(); 460 setManualTemporaryUpperCase(true); 461 } else if (isShiftedOrShiftLocked()) { 462 // In manual upper case state, we just record shift key has been pressing while 463 // shifted state. 464 shiftKeyState.onPressOnShifted(); 465 } else { 466 // In base layout, chording or manual temporary upper case mode is started. 467 shiftKeyState.onPress(); 468 toggleShift(); 469 } 470 } else { 471 // In symbol mode, just toggle symbol and symbol more keyboard. 472 shiftKeyState.onPress(); 473 toggleShift(); 474 } 475 } 476 477 public void onReleaseShift() { 478 if (!isKeyboardAvailable()) 479 return; 480 ShiftKeyState shiftKeyState = mShiftKeyState; 481 if (DEBUG_STATE) 482 Log.d(TAG, "onReleaseShift:" 483 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 484 + " shiftKeyState=" + shiftKeyState); 485 if (isAlphabetMode()) { 486 if (shiftKeyState.isMomentary()) { 487 // After chording input while normal state. 488 toggleShift(); 489 } else if (isShiftLocked() && !shiftKeyState.isIgnoring()) { 490 // Shift has been pressed without chording while caps lock state. 491 toggleCapsLock(); 492 } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()) { 493 // Shift has been pressed without chording while shifted state. 494 toggleShift(); 495 } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()) { 496 // Shift has been pressed without chording while manual temporary upper case 497 // transited from automatic temporary upper case. 498 toggleShift(); 499 } 500 } 501 shiftKeyState.onRelease(); 502 } 503 504 public void onPressSymbol() { 505 if (DEBUG_STATE) 506 Log.d(TAG, "onPressSymbol:" 507 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 508 + " symbolKeyState=" + mSymbolKeyState); 509 changeKeyboardMode(); 510 mSymbolKeyState.onPress(); 511 mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_MOMENTARY; 512 } 513 514 public void onReleaseSymbol() { 515 if (DEBUG_STATE) 516 Log.d(TAG, "onReleaseSymbol:" 517 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 518 + " symbolKeyState=" + mSymbolKeyState); 519 // Snap back to the previous keyboard mode if the user chords the mode change key and 520 // other key, then released the mode change key. 521 if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_CHORDING) 522 changeKeyboardMode(); 523 mSymbolKeyState.onRelease(); 524 } 525 526 public void onOtherKeyPressed() { 527 if (DEBUG_STATE) 528 Log.d(TAG, "onOtherKeyPressed:" 529 + " keyboard=" + getLatinKeyboard().getKeyboardShiftState() 530 + " shiftKeyState=" + mShiftKeyState 531 + " symbolKeyState=" + mSymbolKeyState); 532 mShiftKeyState.onOtherKeyPressed(); 533 mSymbolKeyState.onOtherKeyPressed(); 534 } 535 536 public void onCancelInput() { 537 // Snap back to the previous keyboard mode if the user cancels sliding input. 538 if (mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY && getPointerCount() == 1) 539 changeKeyboardMode(); 540 } 541 542 private void toggleShiftInSymbol() { 543 if (isAlphabetMode()) 544 return; 545 final LatinKeyboard keyboard; 546 if (mCurrentId.equals(mSymbolsId) || !mCurrentId.equals(mSymbolsShiftedId)) { 547 mCurrentId = mSymbolsShiftedId; 548 keyboard = getKeyboard(mCurrentId); 549 // Symbol shifted keyboard has an ALT key that has a caps lock style indicator. To 550 // enable the indicator, we need to call enableShiftLock() and setShiftLocked(true). 551 // Thus we can keep the ALT key's Key.on value true while LatinKey.onRelease() is 552 // called. 553 keyboard.setShiftLocked(true); 554 } else { 555 mCurrentId = mSymbolsId; 556 keyboard = getKeyboard(mCurrentId); 557 // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the 558 // indicator, we need to call enableShiftLock() and setShiftLocked(false). 559 keyboard.setShifted(false); 560 } 561 setKeyboard(keyboard); 562 } 563 564 public boolean isInMomentaryAutoModeSwitchState() { 565 return mAutoModeSwitchState == AUTO_MODE_SWITCH_STATE_MOMENTARY; 566 } 567 568 public boolean isVibrateAndSoundFeedbackRequired() { 569 return mInputView == null || !mInputView.isInSlidingKeyInput(); 570 } 571 572 private int getPointerCount() { 573 return mInputView == null ? 0 : mInputView.getPointerCount(); 574 } 575 576 private void toggleKeyboardMode() { 577 loadKeyboardInternal(mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary, !mIsSymbols); 578 if (mIsSymbols) { 579 mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN; 580 } else { 581 mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; 582 } 583 } 584 585 public boolean hasDistinctMultitouch() { 586 return mInputView != null && mInputView.hasDistinctMultitouch(); 587 } 588 589 /** 590 * Updates state machine to figure out when to automatically snap back to the previous mode. 591 */ 592 public void onKey(int key) { 593 if (DEBUG_STATE) 594 Log.d(TAG, "onKey: code=" + key + " autoModeSwitchState=" + mAutoModeSwitchState 595 + " pointers=" + getPointerCount()); 596 switch (mAutoModeSwitchState) { 597 case AUTO_MODE_SWITCH_STATE_MOMENTARY: 598 // Only distinct multi touch devices can be in this state. 599 // On non-distinct multi touch devices, mode change key is handled by 600 // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and 601 // {@link LatinIME#onRelease}. So, on such devices, {@link #mAutoModeSwitchState} starts 602 // from {@link #AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN}, or 603 // {@link #AUTO_MODE_SWITCH_STATE_ALPHA}, not from 604 // {@link #AUTO_MODE_SWITCH_STATE_MOMENTARY}. 605 if (key == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 606 // Detected only the mode change key has been pressed, and then released. 607 if (mIsSymbols) { 608 mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN; 609 } else { 610 mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; 611 } 612 } else if (getPointerCount() == 1) { 613 // Snap back to the previous keyboard mode if the user pressed the mode change key 614 // and slid to other key, then released the finger. 615 // If the user cancels the sliding input, snapping back to the previous keyboard 616 // mode is handled by {@link #onCancelInput}. 617 changeKeyboardMode(); 618 } else { 619 // Chording input is being started. The keyboard mode will be snapped back to the 620 // previous mode in {@link onReleaseSymbol} when the mode change key is released. 621 mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_CHORDING; 622 } 623 break; 624 case AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN: 625 if (key != Keyboard.CODE_SPACE && key != Keyboard.CODE_ENTER && key >= 0) { 626 mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL; 627 } 628 break; 629 case AUTO_MODE_SWITCH_STATE_SYMBOL: 630 // Snap back to alpha keyboard mode if user types one or more non-space/enter 631 // characters followed by a space/enter. 632 if (key == Keyboard.CODE_ENTER || key == Keyboard.CODE_SPACE) { 633 changeKeyboardMode(); 634 } 635 break; 636 } 637 } 638 639 public LatinKeyboardView getInputView() { 640 return mInputView; 641 } 642 643 public LatinKeyboardView onCreateInputView() { 644 createInputViewInternal(mLayoutId, true); 645 return mInputView; 646 } 647 648 private void createInputViewInternal(int newLayout, boolean forceReset) { 649 int layoutId = newLayout; 650 if (mLayoutId != layoutId || mInputView == null || forceReset) { 651 if (mInputView != null) { 652 mInputView.closing(); 653 } 654 if (KEYBOARD_THEMES.length <= layoutId) { 655 layoutId = Integer.valueOf(sConfigDefaultKeyboardThemeId); 656 } 657 658 Utils.GCUtils.getInstance().reset(); 659 boolean tryGC = true; 660 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 661 try { 662 mInputView = (LatinKeyboardView) mInputMethodService.getLayoutInflater( 663 ).inflate(KEYBOARD_THEMES[layoutId], null); 664 tryGC = false; 665 } catch (OutOfMemoryError e) { 666 Log.w(TAG, "load keyboard failed: " + e); 667 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 668 mLayoutId + "," + layoutId, e); 669 } catch (InflateException e) { 670 Log.w(TAG, "load keyboard failed: " + e); 671 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 672 mLayoutId + "," + layoutId, e); 673 } 674 } 675 mInputView.setOnKeyboardActionListener(mInputMethodService); 676 mLayoutId = layoutId; 677 } 678 } 679 680 private void postSetInputView() { 681 mInputMethodService.mHandler.post(new Runnable() { 682 @Override 683 public void run() { 684 if (mInputView != null) { 685 mInputMethodService.setInputView(mInputView); 686 } 687 mInputMethodService.updateInputViewShown(); 688 } 689 }); 690 } 691 692 @Override 693 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 694 if (PREF_KEYBOARD_LAYOUT.equals(key)) { 695 final int layoutId = Integer.valueOf( 696 sharedPreferences.getString(key, sConfigDefaultKeyboardThemeId)); 697 createInputViewInternal(layoutId, false); 698 postSetInputView(); 699 } else if (Settings.PREF_SETTINGS_KEY.equals(key)) { 700 mSettingsKeyEnabledInSettings = getSettingsKeyMode(sharedPreferences, 701 mInputMethodService); 702 createInputViewInternal(mLayoutId, true); 703 postSetInputView(); 704 } 705 } 706 707 private int getColorScheme() { 708 return (mInputView != null) 709 ? mInputView.getColorScheme() : KeyboardView.COLOR_SCHEME_WHITE; 710 } 711 712 public void onAutoCorrectionStateChanged(boolean isAutoCorrection) { 713 if (isAutoCorrection != mIsAutoCorrectionActive) { 714 LatinKeyboardView keyboardView = getInputView(); 715 mIsAutoCorrectionActive = isAutoCorrection; 716 keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard()) 717 .onAutoCorrectionStateChanged(isAutoCorrection)); 718 } 719 } 720 721 private static boolean getSettingsKeyMode(SharedPreferences prefs, Context context) { 722 Resources resources = context.getResources(); 723 final boolean showSettingsKeyOption = resources.getBoolean( 724 R.bool.config_enable_show_settings_key_option); 725 if (showSettingsKeyOption) { 726 final String settingsKeyMode = prefs.getString(Settings.PREF_SETTINGS_KEY, 727 resources.getString(DEFAULT_SETTINGS_KEY_MODE)); 728 // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or 729 // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system 730 if (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW)) 731 || (settingsKeyMode.equals(resources.getString(SETTINGS_KEY_MODE_AUTO)) 732 && Utils.hasMultipleEnabledIMEsOrSubtypes( 733 ((InputMethodManager) context.getSystemService( 734 Context.INPUT_METHOD_SERVICE))))) { 735 return true; 736 } 737 return false; 738 } 739 // If the show settings key option is disabled, we always try showing the settings key. 740 return true; 741 } 742} 743