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