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 // Remember caps lock mode and reset alphabet shift state. 331 mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); 332 mAlphabetShiftState.setShiftLocked(false); 333 mSwitchActions.setEmojiKeyboard(); 334 } 335 336 public void onPressKey(final int code, final boolean isSinglePointer, final int autoCaps) { 337 if (DEBUG_EVENT) { 338 Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) 339 + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this); 340 } 341 if (code != Constants.CODE_SHIFT) { 342 // Because the double tap shift key timer is to detect two consecutive shift key press, 343 // it should be canceled when a non-shift key is pressed. 344 mSwitchActions.cancelDoubleTapShiftKeyTimer(); 345 } 346 if (code == Constants.CODE_SHIFT) { 347 onPressShift(); 348 } else if (code == Constants.CODE_CAPSLOCK) { 349 // Nothing to do here. See {@link #onReleaseKey(int,boolean)}. 350 } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { 351 onPressSymbol(); 352 } else { 353 mShiftKeyState.onOtherKeyPressed(); 354 mSymbolKeyState.onOtherKeyPressed(); 355 // It is required to reset the auto caps state when all of the following conditions 356 // are met: 357 // 1) two or more fingers are in action 358 // 2) in alphabet layout 359 // 3) not in all characters caps mode 360 // As for #3, please note that it's required to check even when the auto caps mode is 361 // off because, for example, we may be in the #1 state within the manual temporary 362 // shifted mode. 363 if (!isSinglePointer && mIsAlphabetMode && autoCaps != TextUtils.CAP_MODE_CHARACTERS) { 364 final boolean needsToResetAutoCaps = mAlphabetShiftState.isAutomaticShifted() 365 || (mAlphabetShiftState.isManualShifted() && mShiftKeyState.isReleasing()); 366 if (needsToResetAutoCaps) { 367 mSwitchActions.setAlphabetKeyboard(); 368 } 369 } 370 } 371 } 372 373 public void onReleaseKey(final int code, final boolean withSliding) { 374 if (DEBUG_EVENT) { 375 Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code) 376 + " sliding=" + withSliding + " " + this); 377 } 378 if (code == Constants.CODE_SHIFT) { 379 onReleaseShift(withSliding); 380 } else if (code == Constants.CODE_CAPSLOCK) { 381 setShiftLocked(!mAlphabetShiftState.isShiftLocked()); 382 } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { 383 onReleaseSymbol(withSliding); 384 } 385 } 386 387 private void onPressSymbol() { 388 toggleAlphabetAndSymbols(); 389 mSymbolKeyState.onPress(); 390 mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; 391 } 392 393 private void onReleaseSymbol(final boolean withSliding) { 394 if (mSymbolKeyState.isChording()) { 395 // Switch back to the previous keyboard mode if the user chords the mode change key and 396 // another key, then releases the mode change key. 397 toggleAlphabetAndSymbols(); 398 } else if (!withSliding) { 399 // If the mode change key is being released without sliding, we should forget the 400 // previous symbols keyboard shift state and simply switch back to symbols layout 401 // (never symbols shifted) next time the mode gets changed to symbols layout. 402 mPrevSymbolsKeyboardWasShifted = false; 403 } 404 mSymbolKeyState.onRelease(); 405 } 406 407 public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) { 408 if (DEBUG_EVENT) { 409 Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode=" 410 + recapitalizeMode + " " + this); 411 } 412 mRecapitalizeMode = recapitalizeMode; 413 updateAlphabetShiftState(autoCaps, recapitalizeMode); 414 } 415 416 // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout 417 // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). 418 public void onResetKeyboardStateToAlphabet() { 419 if (DEBUG_EVENT) { 420 Log.d(TAG, "onResetKeyboardStateToAlphabet: " + this); 421 } 422 resetKeyboardStateToAlphabet(); 423 } 424 425 private void updateShiftStateForRecapitalize(final int recapitalizeMode) { 426 switch (recapitalizeMode) { 427 case RecapitalizeStatus.CAPS_MODE_ALL_UPPER: 428 setShifted(SHIFT_LOCK_SHIFTED); 429 break; 430 case RecapitalizeStatus.CAPS_MODE_FIRST_WORD_UPPER: 431 setShifted(AUTOMATIC_SHIFT); 432 break; 433 case RecapitalizeStatus.CAPS_MODE_ALL_LOWER: 434 case RecapitalizeStatus.CAPS_MODE_ORIGINAL_MIXED_CASE: 435 default: 436 setShifted(UNSHIFT); 437 } 438 } 439 440 private void updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode) { 441 if (!mIsAlphabetMode) return; 442 if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) { 443 // We are recapitalizing. Match the keyboard to the current recapitalize state. 444 updateShiftStateForRecapitalize(recapitalizeMode); 445 return; 446 } 447 if (!mShiftKeyState.isReleasing()) { 448 // Ignore update shift state event while the shift key is being pressed (including 449 // chording). 450 return; 451 } 452 if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) { 453 if (mShiftKeyState.isReleasing() && autoCaps != Constants.TextUtils.CAP_MODE_OFF) { 454 // Only when shift key is releasing, automatic temporary upper case will be set. 455 setShifted(AUTOMATIC_SHIFT); 456 } else { 457 setShifted(mShiftKeyState.isChording() ? MANUAL_SHIFT : UNSHIFT); 458 } 459 } 460 } 461 462 private void onPressShift() { 463 // If we are recapitalizing, we don't do any of the normal processing, including 464 // importantly the double tap timer. 465 if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) { 466 return; 467 } 468 if (mIsAlphabetMode) { 469 mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout(); 470 if (!mIsInDoubleTapShiftKey) { 471 // This is first tap. 472 mSwitchActions.startDoubleTapShiftKeyTimer(); 473 } 474 if (mIsInDoubleTapShiftKey) { 475 if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) { 476 // Shift key has been double tapped while in manual shifted or automatic 477 // shifted state. 478 setShiftLocked(true); 479 } else { 480 // Shift key has been double tapped while in normal state. This is the second 481 // tap to disable shift locked state, so just ignore this. 482 } 483 } else { 484 if (mAlphabetShiftState.isShiftLocked()) { 485 // Shift key is pressed while shift locked state, we will treat this state as 486 // shift lock shifted state and mark as if shift key pressed while normal 487 // state. 488 setShifted(SHIFT_LOCK_SHIFTED); 489 mShiftKeyState.onPress(); 490 } else if (mAlphabetShiftState.isAutomaticShifted()) { 491 // Shift key is pressed while automatic shifted, we have to move to manual 492 // shifted. 493 setShifted(MANUAL_SHIFT); 494 mShiftKeyState.onPress(); 495 } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) { 496 // In manual shifted state, we just record shift key has been pressing while 497 // shifted state. 498 mShiftKeyState.onPressOnShifted(); 499 } else { 500 // In base layout, chording or manual shifted mode is started. 501 setShifted(MANUAL_SHIFT); 502 mShiftKeyState.onPress(); 503 } 504 } 505 } else { 506 // In symbol mode, just toggle symbol and symbol more keyboard. 507 toggleShiftInSymbols(); 508 mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; 509 mShiftKeyState.onPress(); 510 } 511 } 512 513 private void onReleaseShift(final boolean withSliding) { 514 if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) { 515 // We are recapitalizing. We should match the keyboard state to the recapitalize 516 // state in priority. 517 updateShiftStateForRecapitalize(mRecapitalizeMode); 518 } else if (mIsAlphabetMode) { 519 final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked(); 520 mIsInAlphabetUnshiftedFromShifted = false; 521 if (mIsInDoubleTapShiftKey) { 522 // Double tap shift key has been handled in {@link #onPressShift}, so that just 523 // ignore this release shift key here. 524 mIsInDoubleTapShiftKey = false; 525 } else if (mShiftKeyState.isChording()) { 526 if (mAlphabetShiftState.isShiftLockShifted()) { 527 // After chording input while shift locked state. 528 setShiftLocked(true); 529 } else { 530 // After chording input while normal state. 531 setShifted(UNSHIFT); 532 } 533 // After chording input, automatic shift state may have been changed depending on 534 // what characters were input. 535 mShiftKeyState.onRelease(); 536 mSwitchActions.requestUpdatingShiftState(); 537 return; 538 } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) { 539 // In shift locked state, shift has been pressed and slid out to other key. 540 setShiftLocked(true); 541 } else if (mAlphabetShiftState.isManualShifted() && withSliding) { 542 // Shift has been pressed and slid out to other key. 543 mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_SHIFT; 544 } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted() 545 && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted()) 546 && !withSliding) { 547 // Shift has been long pressed, ignore this release. 548 } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) { 549 // Shift has been pressed without chording while shift locked state. 550 setShiftLocked(false); 551 } else if (mAlphabetShiftState.isShiftedOrShiftLocked() 552 && mShiftKeyState.isPressingOnShifted() && !withSliding) { 553 // Shift has been pressed without chording while shifted state. 554 setShifted(UNSHIFT); 555 mIsInAlphabetUnshiftedFromShifted = true; 556 } else if (mAlphabetShiftState.isManualShiftedFromAutomaticShifted() 557 && mShiftKeyState.isPressing() && !withSliding) { 558 // Shift has been pressed without chording while manual shifted transited from 559 // automatic shifted 560 setShifted(UNSHIFT); 561 mIsInAlphabetUnshiftedFromShifted = true; 562 } 563 } else { 564 // In symbol mode, switch back to the previous keyboard mode if the user chords the 565 // shift key and another key, then releases the shift key. 566 if (mShiftKeyState.isChording()) { 567 toggleShiftInSymbols(); 568 } 569 } 570 mShiftKeyState.onRelease(); 571 } 572 573 public void onFinishSlidingInput() { 574 if (DEBUG_EVENT) { 575 Log.d(TAG, "onFinishSlidingInput: " + this); 576 } 577 // Switch back to the previous keyboard mode if the user cancels sliding input. 578 switch (mSwitchState) { 579 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: 580 toggleAlphabetAndSymbols(); 581 break; 582 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: 583 toggleShiftInSymbols(); 584 break; 585 case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: 586 setAlphabetKeyboard(); 587 break; 588 } 589 } 590 591 private static boolean isSpaceOrEnter(final int c) { 592 return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; 593 } 594 595 public void onCodeInput(final int code, final int autoCaps) { 596 if (DEBUG_EVENT) { 597 Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code) 598 + " autoCaps=" + autoCaps + " " + this); 599 } 600 601 switch (mSwitchState) { 602 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: 603 if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { 604 // Detected only the mode change key has been pressed, and then released. 605 if (mIsAlphabetMode) { 606 mSwitchState = SWITCH_STATE_ALPHA; 607 } else { 608 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 609 } 610 } 611 break; 612 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: 613 if (code == Constants.CODE_SHIFT) { 614 // Detected only the shift key has been pressed on symbol layout, and then 615 // released. 616 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 617 } 618 break; 619 case SWITCH_STATE_SYMBOL_BEGIN: 620 if (mIsEmojiMode) { 621 // When in the Emoji keyboard, we don't want to switch back to the main layout even 622 // after the user hits an emoji letter followed by an enter or a space. 623 break; 624 } 625 if (!isSpaceOrEnter(code) && (Constants.isLetterCode(code) 626 || code == Constants.CODE_OUTPUT_TEXT)) { 627 mSwitchState = SWITCH_STATE_SYMBOL; 628 } 629 break; 630 case SWITCH_STATE_SYMBOL: 631 // Switch back to alpha keyboard mode if user types one or more non-space/enter 632 // characters followed by a space/enter. 633 if (isSpaceOrEnter(code)) { 634 toggleAlphabetAndSymbols(); 635 mPrevSymbolsKeyboardWasShifted = false; 636 } 637 break; 638 } 639 640 // If the code is a letter, update keyboard shift state. 641 if (Constants.isLetterCode(code)) { 642 updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE); 643 } else if (code == Constants.CODE_EMOJI) { 644 setEmojiKeyboard(); 645 } 646 } 647 648 static String shiftModeToString(final int shiftMode) { 649 switch (shiftMode) { 650 case UNSHIFT: return "UNSHIFT"; 651 case MANUAL_SHIFT: return "MANUAL"; 652 case AUTOMATIC_SHIFT: return "AUTOMATIC"; 653 default: return null; 654 } 655 } 656 657 private static String switchStateToString(final int switchState) { 658 switch (switchState) { 659 case SWITCH_STATE_ALPHA: return "ALPHA"; 660 case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN"; 661 case SWITCH_STATE_SYMBOL: return "SYMBOL"; 662 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL"; 663 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE"; 664 case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: return "MOMENTARY-ALPHA_SHIFT"; 665 default: return null; 666 } 667 } 668 669 @Override 670 public String toString() { 671 return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() 672 : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) 673 + " shift=" + mShiftKeyState 674 + " symbol=" + mSymbolKeyState 675 + " switch=" + switchStateToString(mSwitchState) + "]"; 676 } 677 } 678