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