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