1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.keyboard.internal; 18 19 import android.text.TextUtils; 20 import android.util.Log; 21 22 import com.android.inputmethod.latin.Constants; 23 import com.android.inputmethod.latin.RecapitalizeStatus; 24 25 /** 26 * Keyboard state machine. 27 * 28 * This class contains all keyboard state transition logic. 29 * 30 * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()}, 31 * {@link #onPressKey(int,boolean,int)}, {@link #onReleaseKey(int,boolean)}, 32 * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()}, {@link #onCancelInput()}, 33 * {@link #onUpdateShiftState(int,int)}, {@link #onLongPressTimeout(int)}. 34 * 35 * The actions are {@link SwitchActions}'s methods. 36 */ 37 public final class KeyboardState { 38 private static final String TAG = KeyboardState.class.getSimpleName(); 39 private static final boolean DEBUG_EVENT = false; 40 private static final boolean DEBUG_ACTION = false; 41 42 public interface SwitchActions { setAlphabetKeyboard()43 public void setAlphabetKeyboard(); setAlphabetManualShiftedKeyboard()44 public void setAlphabetManualShiftedKeyboard(); setAlphabetAutomaticShiftedKeyboard()45 public void setAlphabetAutomaticShiftedKeyboard(); setAlphabetShiftLockedKeyboard()46 public void setAlphabetShiftLockedKeyboard(); setAlphabetShiftLockShiftedKeyboard()47 public void setAlphabetShiftLockShiftedKeyboard(); setSymbolsKeyboard()48 public void setSymbolsKeyboard(); setSymbolsShiftedKeyboard()49 public void setSymbolsShiftedKeyboard(); 50 51 /** 52 * Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}. 53 */ requestUpdatingShiftState()54 public void requestUpdatingShiftState(); 55 startDoubleTapTimer()56 public void startDoubleTapTimer(); isInDoubleTapTimeout()57 public boolean isInDoubleTapTimeout(); cancelDoubleTapTimer()58 public void cancelDoubleTapTimer(); startLongPressTimer(int code)59 public void startLongPressTimer(int code); cancelLongPressTimer()60 public void cancelLongPressTimer(); hapticAndAudioFeedback(int code)61 public void hapticAndAudioFeedback(int code); 62 } 63 64 private final SwitchActions mSwitchActions; 65 66 private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift"); 67 private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol"); 68 69 // TODO: Merge {@link #mSwitchState}, {@link #mIsAlphabetMode}, {@link #mAlphabetShiftState}, 70 // {@link #mIsSymbolShifted}, {@link #mPrevMainKeyboardWasShiftLocked}, and 71 // {@link #mPrevSymbolsKeyboardWasShifted} into single state variable. 72 private static final int SWITCH_STATE_ALPHA = 0; 73 private static final int SWITCH_STATE_SYMBOL_BEGIN = 1; 74 private static final int SWITCH_STATE_SYMBOL = 2; 75 private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3; 76 private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4; 77 private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 5; 78 private int mSwitchState = SWITCH_STATE_ALPHA; 79 80 private boolean mIsAlphabetMode; 81 private AlphabetShiftState mAlphabetShiftState = new AlphabetShiftState(); 82 private boolean mIsSymbolShifted; 83 private boolean mPrevMainKeyboardWasShiftLocked; 84 private boolean mPrevSymbolsKeyboardWasShifted; 85 private int mRecapitalizeMode; 86 87 // For handling long press. 88 private boolean mLongPressShiftLockFired; 89 90 // For handling double tap. 91 private boolean mIsInAlphabetUnshiftedFromShifted; 92 private boolean mIsInDoubleTapShiftKey; 93 94 private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState(); 95 96 static final class SavedKeyboardState { 97 public boolean mIsValid; 98 public boolean mIsAlphabetMode; 99 public boolean mIsAlphabetShiftLocked; 100 public int mShiftMode; 101 102 @Override toString()103 public String toString() { 104 if (!mIsValid) return "INVALID"; 105 if (mIsAlphabetMode) { 106 if (mIsAlphabetShiftLocked) return "ALPHABET_SHIFT_LOCKED"; 107 return "ALPHABET_" + shiftModeToString(mShiftMode); 108 } else { 109 return "SYMBOLS_" + shiftModeToString(mShiftMode); 110 } 111 } 112 } 113 KeyboardState(final SwitchActions switchActions)114 public KeyboardState(final SwitchActions switchActions) { 115 mSwitchActions = switchActions; 116 mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; 117 } 118 onLoadKeyboard()119 public void onLoadKeyboard() { 120 if (DEBUG_EVENT) { 121 Log.d(TAG, "onLoadKeyboard: " + this); 122 } 123 // Reset alphabet shift state. 124 mAlphabetShiftState.setShiftLocked(false); 125 mPrevMainKeyboardWasShiftLocked = false; 126 mPrevSymbolsKeyboardWasShifted = false; 127 mShiftKeyState.onRelease(); 128 mSymbolKeyState.onRelease(); 129 onRestoreKeyboardState(); 130 } 131 132 private static final int UNSHIFT = 0; 133 private static final int MANUAL_SHIFT = 1; 134 private static final int AUTOMATIC_SHIFT = 2; 135 private static final int SHIFT_LOCK_SHIFTED = 3; 136 onSaveKeyboardState()137 public void onSaveKeyboardState() { 138 final SavedKeyboardState state = mSavedKeyboardState; 139 state.mIsAlphabetMode = mIsAlphabetMode; 140 if (mIsAlphabetMode) { 141 state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked(); 142 state.mShiftMode = mAlphabetShiftState.isAutomaticShifted() ? AUTOMATIC_SHIFT 143 : (mAlphabetShiftState.isShiftedOrShiftLocked() ? MANUAL_SHIFT : UNSHIFT); 144 } else { 145 state.mIsAlphabetShiftLocked = mPrevMainKeyboardWasShiftLocked; 146 state.mShiftMode = mIsSymbolShifted ? MANUAL_SHIFT : UNSHIFT; 147 } 148 state.mIsValid = true; 149 if (DEBUG_EVENT) { 150 Log.d(TAG, "onSaveKeyboardState: saved=" + state + " " + this); 151 } 152 } 153 onRestoreKeyboardState()154 private void onRestoreKeyboardState() { 155 final SavedKeyboardState state = mSavedKeyboardState; 156 if (DEBUG_EVENT) { 157 Log.d(TAG, "onRestoreKeyboardState: saved=" + state + " " + this); 158 } 159 if (!state.mIsValid || state.mIsAlphabetMode) { 160 setAlphabetKeyboard(); 161 } else { 162 if (state.mShiftMode == MANUAL_SHIFT) { 163 setSymbolsShiftedKeyboard(); 164 } else { 165 setSymbolsKeyboard(); 166 } 167 } 168 169 if (!state.mIsValid) return; 170 state.mIsValid = false; 171 172 if (state.mIsAlphabetMode) { 173 setShiftLocked(state.mIsAlphabetShiftLocked); 174 if (!state.mIsAlphabetShiftLocked) { 175 setShifted(state.mShiftMode); 176 } 177 } else { 178 mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked; 179 } 180 } 181 setShifted(final int shiftMode)182 private void setShifted(final int shiftMode) { 183 if (DEBUG_ACTION) { 184 Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this); 185 } 186 if (!mIsAlphabetMode) return; 187 final int prevShiftMode; 188 if (mAlphabetShiftState.isAutomaticShifted()) { 189 prevShiftMode = AUTOMATIC_SHIFT; 190 } else if (mAlphabetShiftState.isManualShifted()) { 191 prevShiftMode = MANUAL_SHIFT; 192 } else { 193 prevShiftMode = UNSHIFT; 194 } 195 switch (shiftMode) { 196 case AUTOMATIC_SHIFT: 197 mAlphabetShiftState.setAutomaticShifted(); 198 if (shiftMode != prevShiftMode) { 199 mSwitchActions.setAlphabetAutomaticShiftedKeyboard(); 200 } 201 break; 202 case MANUAL_SHIFT: 203 mAlphabetShiftState.setShifted(true); 204 if (shiftMode != prevShiftMode) { 205 mSwitchActions.setAlphabetManualShiftedKeyboard(); 206 } 207 break; 208 case UNSHIFT: 209 mAlphabetShiftState.setShifted(false); 210 if (shiftMode != prevShiftMode) { 211 mSwitchActions.setAlphabetKeyboard(); 212 } 213 break; 214 case SHIFT_LOCK_SHIFTED: 215 mAlphabetShiftState.setShifted(true); 216 mSwitchActions.setAlphabetShiftLockShiftedKeyboard(); 217 break; 218 } 219 } 220 setShiftLocked(final boolean shiftLocked)221 private void setShiftLocked(final boolean shiftLocked) { 222 if (DEBUG_ACTION) { 223 Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this); 224 } 225 if (!mIsAlphabetMode) return; 226 if (shiftLocked && (!mAlphabetShiftState.isShiftLocked() 227 || mAlphabetShiftState.isShiftLockShifted())) { 228 mSwitchActions.setAlphabetShiftLockedKeyboard(); 229 } 230 if (!shiftLocked && mAlphabetShiftState.isShiftLocked()) { 231 mSwitchActions.setAlphabetKeyboard(); 232 } 233 mAlphabetShiftState.setShiftLocked(shiftLocked); 234 } 235 toggleAlphabetAndSymbols()236 private void toggleAlphabetAndSymbols() { 237 if (DEBUG_ACTION) { 238 Log.d(TAG, "toggleAlphabetAndSymbols: " + this); 239 } 240 if (mIsAlphabetMode) { 241 mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); 242 if (mPrevSymbolsKeyboardWasShifted) { 243 setSymbolsShiftedKeyboard(); 244 } else { 245 setSymbolsKeyboard(); 246 } 247 mPrevSymbolsKeyboardWasShifted = false; 248 } else { 249 mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; 250 setAlphabetKeyboard(); 251 if (mPrevMainKeyboardWasShiftLocked) { 252 setShiftLocked(true); 253 } 254 mPrevMainKeyboardWasShiftLocked = false; 255 } 256 } 257 258 // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout 259 // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). resetKeyboardStateToAlphabet()260 private void resetKeyboardStateToAlphabet() { 261 if (DEBUG_ACTION) { 262 Log.d(TAG, "resetKeyboardStateToAlphabet: " + this); 263 } 264 if (mIsAlphabetMode) return; 265 266 mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; 267 setAlphabetKeyboard(); 268 if (mPrevMainKeyboardWasShiftLocked) { 269 setShiftLocked(true); 270 } 271 mPrevMainKeyboardWasShiftLocked = false; 272 } 273 toggleShiftInSymbols()274 private void toggleShiftInSymbols() { 275 if (mIsSymbolShifted) { 276 setSymbolsKeyboard(); 277 } else { 278 setSymbolsShiftedKeyboard(); 279 } 280 } 281 setAlphabetKeyboard()282 private void setAlphabetKeyboard() { 283 if (DEBUG_ACTION) { 284 Log.d(TAG, "setAlphabetKeyboard"); 285 } 286 287 mSwitchActions.setAlphabetKeyboard(); 288 mIsAlphabetMode = true; 289 mIsSymbolShifted = false; 290 mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE; 291 mSwitchState = SWITCH_STATE_ALPHA; 292 mSwitchActions.requestUpdatingShiftState(); 293 } 294 setSymbolsKeyboard()295 private void setSymbolsKeyboard() { 296 if (DEBUG_ACTION) { 297 Log.d(TAG, "setSymbolsKeyboard"); 298 } 299 mSwitchActions.setSymbolsKeyboard(); 300 mIsAlphabetMode = false; 301 mIsSymbolShifted = false; 302 // Reset alphabet shift state. 303 mAlphabetShiftState.setShiftLocked(false); 304 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 305 } 306 setSymbolsShiftedKeyboard()307 private void setSymbolsShiftedKeyboard() { 308 if (DEBUG_ACTION) { 309 Log.d(TAG, "setSymbolsShiftedKeyboard"); 310 } 311 mSwitchActions.setSymbolsShiftedKeyboard(); 312 mIsAlphabetMode = false; 313 mIsSymbolShifted = true; 314 // Reset alphabet shift state. 315 mAlphabetShiftState.setShiftLocked(false); 316 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 317 } 318 onPressKey(final int code, final boolean isSinglePointer, final int autoCaps)319 public void onPressKey(final int code, final boolean isSinglePointer, final int autoCaps) { 320 if (DEBUG_EVENT) { 321 Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) 322 + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this); 323 } 324 if (code == Constants.CODE_SHIFT) { 325 onPressShift(); 326 } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { 327 onPressSymbol(); 328 } else { 329 mSwitchActions.cancelDoubleTapTimer(); 330 mSwitchActions.cancelLongPressTimer(); 331 mLongPressShiftLockFired = false; 332 mShiftKeyState.onOtherKeyPressed(); 333 mSymbolKeyState.onOtherKeyPressed(); 334 // It is required to reset the auto caps state when all of the following conditions 335 // are met: 336 // 1) two or more fingers are in action 337 // 2) in alphabet layout 338 // 3) not in all characters caps mode 339 // As for #3, please note that it's required to check even when the auto caps mode is 340 // off because, for example, we may be in the #1 state within the manual temporary 341 // shifted mode. 342 if (!isSinglePointer && mIsAlphabetMode && autoCaps != TextUtils.CAP_MODE_CHARACTERS) { 343 final boolean needsToResetAutoCaps = mAlphabetShiftState.isAutomaticShifted() 344 || (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing()); 345 if (needsToResetAutoCaps) { 346 mSwitchActions.setAlphabetKeyboard(); 347 } 348 } 349 } 350 } 351 onReleaseKey(final int code, final boolean withSliding)352 public void onReleaseKey(final int code, final boolean withSliding) { 353 if (DEBUG_EVENT) { 354 Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code) 355 + " sliding=" + withSliding + " " + this); 356 } 357 if (code == Constants.CODE_SHIFT) { 358 onReleaseShift(withSliding); 359 } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { 360 onReleaseSymbol(withSliding); 361 } 362 } 363 onPressSymbol()364 private void onPressSymbol() { 365 toggleAlphabetAndSymbols(); 366 mSymbolKeyState.onPress(); 367 mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; 368 } 369 onReleaseSymbol(final boolean withSliding)370 private void onReleaseSymbol(final boolean withSliding) { 371 if (mSymbolKeyState.isChording()) { 372 // Switch back to the previous keyboard mode if the user chords the mode change key and 373 // another key, then releases the mode change key. 374 toggleAlphabetAndSymbols(); 375 } else if (!withSliding) { 376 // If the mode change key is being released without sliding, we should forget the 377 // previous symbols keyboard shift state and simply switch back to symbols layout 378 // (never symbols shifted) next time the mode gets changed to symbols layout. 379 mPrevSymbolsKeyboardWasShifted = false; 380 } 381 mSymbolKeyState.onRelease(); 382 } 383 onLongPressTimeout(final int code)384 public void onLongPressTimeout(final int code) { 385 if (DEBUG_EVENT) { 386 Log.d(TAG, "onLongPressTimeout: code=" + Constants.printableCode(code) + " " + this); 387 } 388 if (mIsAlphabetMode && code == Constants.CODE_SHIFT) { 389 mLongPressShiftLockFired = true; 390 mSwitchActions.hapticAndAudioFeedback(code); 391 } 392 } 393 onUpdateShiftState(final int autoCaps, final int recapitalizeMode)394 public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) { 395 if (DEBUG_EVENT) { 396 Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode=" 397 + recapitalizeMode + " " + this); 398 } 399 mRecapitalizeMode = recapitalizeMode; 400 updateAlphabetShiftState(autoCaps, recapitalizeMode); 401 } 402 403 // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout 404 // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). onResetKeyboardStateToAlphabet()405 public void onResetKeyboardStateToAlphabet() { 406 if (DEBUG_EVENT) { 407 Log.d(TAG, "onResetKeyboardStateToAlphabet: " + this); 408 } 409 resetKeyboardStateToAlphabet(); 410 } 411 updateShiftStateForRecapitalize(final int recapitalizeMode)412 private void updateShiftStateForRecapitalize(final int recapitalizeMode) { 413 switch (recapitalizeMode) { 414 case RecapitalizeStatus.CAPS_MODE_ALL_UPPER: 415 setShifted(SHIFT_LOCK_SHIFTED); 416 break; 417 case RecapitalizeStatus.CAPS_MODE_FIRST_WORD_UPPER: 418 setShifted(AUTOMATIC_SHIFT); 419 break; 420 case RecapitalizeStatus.CAPS_MODE_ALL_LOWER: 421 case RecapitalizeStatus.CAPS_MODE_ORIGINAL_MIXED_CASE: 422 default: 423 setShifted(UNSHIFT); 424 } 425 } 426 updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode)427 private void updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode) { 428 if (!mIsAlphabetMode) return; 429 if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) { 430 // We are recapitalizing. Match the keyboard to the current recapitalize state. 431 updateShiftStateForRecapitalize(recapitalizeMode); 432 return; 433 } 434 if (!mShiftKeyState.isReleasing()) { 435 // Ignore update shift state event while the shift key is being pressed (including 436 // chording). 437 return; 438 } 439 if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) { 440 if (mShiftKeyState.isReleasing() && autoCaps != Constants.TextUtils.CAP_MODE_OFF) { 441 // Only when shift key is releasing, automatic temporary upper case will be set. 442 setShifted(AUTOMATIC_SHIFT); 443 } else { 444 setShifted(mShiftKeyState.isChording() ? MANUAL_SHIFT : UNSHIFT); 445 } 446 } 447 } 448 onPressShift()449 private void onPressShift() { 450 mLongPressShiftLockFired = false; 451 // If we are recapitalizing, we don't do any of the normal processing, including 452 // importantly the double tap timer. 453 if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) return; 454 if (mIsAlphabetMode) { 455 mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapTimeout(); 456 if (!mIsInDoubleTapShiftKey) { 457 // This is first tap. 458 mSwitchActions.startDoubleTapTimer(); 459 } 460 if (mIsInDoubleTapShiftKey) { 461 if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) { 462 // Shift key has been double tapped while in manual shifted or automatic 463 // shifted state. 464 setShiftLocked(true); 465 } else { 466 // Shift key has been double tapped while in normal state. This is the second 467 // tap to disable shift locked state, so just ignore this. 468 } 469 } else { 470 if (mAlphabetShiftState.isShiftLocked()) { 471 // Shift key is pressed while shift locked state, we will treat this state as 472 // shift lock shifted state and mark as if shift key pressed while normal state. 473 setShifted(SHIFT_LOCK_SHIFTED); 474 mShiftKeyState.onPress(); 475 } else if (mAlphabetShiftState.isAutomaticShifted()) { 476 // Shift key is pressed while automatic shifted, we have to move to manual 477 // shifted. 478 setShifted(MANUAL_SHIFT); 479 mShiftKeyState.onPress(); 480 } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) { 481 // In manual shifted state, we just record shift key has been pressing while 482 // shifted state. 483 mShiftKeyState.onPressOnShifted(); 484 } else { 485 // In base layout, chording or manual shifted mode is started. 486 setShifted(MANUAL_SHIFT); 487 mShiftKeyState.onPress(); 488 } 489 mSwitchActions.startLongPressTimer(Constants.CODE_SHIFT); 490 } 491 } else { 492 // In symbol mode, just toggle symbol and symbol more keyboard. 493 toggleShiftInSymbols(); 494 mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; 495 mShiftKeyState.onPress(); 496 } 497 } 498 onReleaseShift(final boolean withSliding)499 private void onReleaseShift(final boolean withSliding) { 500 if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) { 501 // We are recapitalizing. We should match the keyboard state to the recapitalize 502 // state in priority. 503 updateShiftStateForRecapitalize(mRecapitalizeMode); 504 } else if (mIsAlphabetMode) { 505 final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked(); 506 mIsInAlphabetUnshiftedFromShifted = false; 507 if (mIsInDoubleTapShiftKey) { 508 // Double tap shift key has been handled in {@link #onPressShift}, so that just 509 // ignore this release shift key here. 510 mIsInDoubleTapShiftKey = false; 511 } else if (mLongPressShiftLockFired) { 512 setShiftLocked(!mAlphabetShiftState.isShiftLocked()); 513 } else if (mShiftKeyState.isChording()) { 514 if (mAlphabetShiftState.isShiftLockShifted()) { 515 // After chording input while shift locked state. 516 setShiftLocked(true); 517 } else { 518 // After chording input while normal state. 519 setShifted(UNSHIFT); 520 } 521 // After chording input, automatic shift state may have been changed depending on 522 // what characters were input. 523 mShiftKeyState.onRelease(); 524 mSwitchActions.requestUpdatingShiftState(); 525 return; 526 } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) { 527 // In shift locked state, shift has been pressed and slid out to other key. 528 setShiftLocked(true); 529 } else if (mAlphabetShiftState.isManualShifted() && withSliding) { 530 // Shift has been pressed and slid out to other key. 531 mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_SHIFT; 532 } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted() 533 && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted()) 534 && !withSliding) { 535 // Shift has been long pressed, ignore this release. 536 } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) { 537 // Shift has been pressed without chording while shift locked state. 538 setShiftLocked(false); 539 } else if (mAlphabetShiftState.isShiftedOrShiftLocked() 540 && mShiftKeyState.isPressingOnShifted() && !withSliding) { 541 // Shift has been pressed without chording while shifted state. 542 setShifted(UNSHIFT); 543 mIsInAlphabetUnshiftedFromShifted = true; 544 } else if (mAlphabetShiftState.isManualShiftedFromAutomaticShifted() 545 && mShiftKeyState.isPressing() && !withSliding) { 546 // Shift has been pressed without chording while manual shifted transited from 547 // automatic shifted 548 setShifted(UNSHIFT); 549 mIsInAlphabetUnshiftedFromShifted = true; 550 } 551 } else { 552 // In symbol mode, switch back to the previous keyboard mode if the user chords the 553 // shift key and another key, then releases the shift key. 554 if (mShiftKeyState.isChording()) { 555 toggleShiftInSymbols(); 556 } 557 } 558 mShiftKeyState.onRelease(); 559 } 560 onFinishSlidingInput()561 public void onFinishSlidingInput() { 562 if (DEBUG_EVENT) { 563 Log.d(TAG, "onFinishSlidingInput: " + this); 564 } 565 // Switch back to the previous keyboard mode if the user cancels sliding input. 566 switch (mSwitchState) { 567 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: 568 toggleAlphabetAndSymbols(); 569 break; 570 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: 571 toggleShiftInSymbols(); 572 break; 573 case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: 574 setAlphabetKeyboard(); 575 break; 576 } 577 } 578 isInMomentarySwitchState()579 public boolean isInMomentarySwitchState() { 580 return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL 581 || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; 582 } 583 isSpaceCharacter(final int c)584 private static boolean isSpaceCharacter(final int c) { 585 return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; 586 } 587 onCodeInput(final int code, final int autoCaps)588 public void onCodeInput(final int code, final int autoCaps) { 589 if (DEBUG_EVENT) { 590 Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code) 591 + " autoCaps=" + autoCaps + " " + this); 592 } 593 594 switch (mSwitchState) { 595 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: 596 if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { 597 // Detected only the mode change key has been pressed, and then released. 598 if (mIsAlphabetMode) { 599 mSwitchState = SWITCH_STATE_ALPHA; 600 } else { 601 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 602 } 603 } 604 break; 605 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: 606 if (code == Constants.CODE_SHIFT) { 607 // Detected only the shift key has been pressed on symbol layout, and then released. 608 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 609 } 610 break; 611 case SWITCH_STATE_SYMBOL_BEGIN: 612 if (!isSpaceCharacter(code) && (Constants.isLetterCode(code) 613 || code == Constants.CODE_OUTPUT_TEXT)) { 614 mSwitchState = SWITCH_STATE_SYMBOL; 615 } 616 break; 617 case SWITCH_STATE_SYMBOL: 618 // Switch back to alpha keyboard mode if user types one or more non-space/enter 619 // characters followed by a space/enter. 620 if (isSpaceCharacter(code)) { 621 toggleAlphabetAndSymbols(); 622 mPrevSymbolsKeyboardWasShifted = false; 623 } 624 break; 625 } 626 627 // If the code is a letter, update keyboard shift state. 628 if (Constants.isLetterCode(code)) { 629 updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); 630 } 631 } 632 shiftModeToString(final int shiftMode)633 static String shiftModeToString(final int shiftMode) { 634 switch (shiftMode) { 635 case UNSHIFT: return "UNSHIFT"; 636 case MANUAL_SHIFT: return "MANUAL"; 637 case AUTOMATIC_SHIFT: return "AUTOMATIC"; 638 default: return null; 639 } 640 } 641 switchStateToString(final int switchState)642 private static String switchStateToString(final int switchState) { 643 switch (switchState) { 644 case SWITCH_STATE_ALPHA: return "ALPHA"; 645 case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN"; 646 case SWITCH_STATE_SYMBOL: return "SYMBOL"; 647 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL"; 648 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE"; 649 case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: return "MOMENTARY-ALPHA_SHIFT"; 650 default: return null; 651 } 652 } 653 654 @Override toString()655 public String toString() { 656 return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() 657 : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) 658 + " shift=" + mShiftKeyState 659 + " symbol=" + mSymbolKeyState 660 + " switch=" + switchStateToString(mSwitchState) + "]"; 661 } 662 } 663