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