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