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