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