Home | History | Annotate | Download | only in internal
      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