KeyboardState.java revision 5d2556b93286f5f1d7d829b586b84a8b7ae55743
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
17package com.android.inputmethod.keyboard.internal;
18
19import android.text.TextUtils;
20import android.util.Log;
21
22import com.android.inputmethod.keyboard.Keyboard;
23import 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 */
37public 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