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