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