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