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