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