KeyboardState.java revision 93445b4821e9e8ecc7dd52f1a5d5316c7eec2654
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.utils.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()},
33 * {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet()}.
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 double tap.
85    private boolean mIsInAlphabetUnshiftedFromShifted;
86    private boolean mIsInDoubleTapShiftKey;
87
88    private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState();
89
90    static final class SavedKeyboardState {
91        public boolean mIsValid;
92        public boolean mIsAlphabetMode;
93        public boolean mIsAlphabetShiftLocked;
94        public int mShiftMode;
95
96        @Override
97        public String toString() {
98            if (!mIsValid) return "INVALID";
99            if (mIsAlphabetMode) {
100                if (mIsAlphabetShiftLocked) return "ALPHABET_SHIFT_LOCKED";
101                return "ALPHABET_" + shiftModeToString(mShiftMode);
102            } else {
103                return "SYMBOLS_" + shiftModeToString(mShiftMode);
104            }
105        }
106    }
107
108    public KeyboardState(final SwitchActions switchActions) {
109        mSwitchActions = switchActions;
110        mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
111    }
112
113    public void onLoadKeyboard() {
114        if (DEBUG_EVENT) {
115            Log.d(TAG, "onLoadKeyboard: " + this);
116        }
117        // Reset alphabet shift state.
118        mAlphabetShiftState.setShiftLocked(false);
119        mPrevMainKeyboardWasShiftLocked = false;
120        mPrevSymbolsKeyboardWasShifted = false;
121        mShiftKeyState.onRelease();
122        mSymbolKeyState.onRelease();
123        onRestoreKeyboardState();
124    }
125
126    private static final int UNSHIFT = 0;
127    private static final int MANUAL_SHIFT = 1;
128    private static final int AUTOMATIC_SHIFT = 2;
129    private static final int SHIFT_LOCK_SHIFTED = 3;
130
131    public void onSaveKeyboardState() {
132        final SavedKeyboardState state = mSavedKeyboardState;
133        state.mIsAlphabetMode = mIsAlphabetMode;
134        if (mIsAlphabetMode) {
135            state.mIsAlphabetShiftLocked = mAlphabetShiftState.isShiftLocked();
136            state.mShiftMode = mAlphabetShiftState.isAutomaticShifted() ? AUTOMATIC_SHIFT
137                    : (mAlphabetShiftState.isShiftedOrShiftLocked() ? MANUAL_SHIFT : UNSHIFT);
138        } else {
139            state.mIsAlphabetShiftLocked = mPrevMainKeyboardWasShiftLocked;
140            state.mShiftMode = mIsSymbolShifted ? MANUAL_SHIFT : UNSHIFT;
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.mShiftMode == MANUAL_SHIFT) {
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.mShiftMode);
170            }
171        } else {
172            mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
173        }
174    }
175
176    private void setShifted(final int shiftMode) {
177        if (DEBUG_ACTION) {
178            Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this);
179        }
180        if (!mIsAlphabetMode) return;
181        final int prevShiftMode;
182        if (mAlphabetShiftState.isAutomaticShifted()) {
183            prevShiftMode = AUTOMATIC_SHIFT;
184        } else if (mAlphabetShiftState.isManualShifted()) {
185            prevShiftMode = MANUAL_SHIFT;
186        } else {
187            prevShiftMode = UNSHIFT;
188        }
189        switch (shiftMode) {
190        case AUTOMATIC_SHIFT:
191            mAlphabetShiftState.setAutomaticShifted();
192            if (shiftMode != prevShiftMode) {
193                mSwitchActions.setAlphabetAutomaticShiftedKeyboard();
194            }
195            break;
196        case MANUAL_SHIFT:
197            mAlphabetShiftState.setShifted(true);
198            if (shiftMode != prevShiftMode) {
199                mSwitchActions.setAlphabetManualShiftedKeyboard();
200            }
201            break;
202        case UNSHIFT:
203            mAlphabetShiftState.setShifted(false);
204            if (shiftMode != prevShiftMode) {
205                mSwitchActions.setAlphabetKeyboard();
206            }
207            break;
208        case SHIFT_LOCK_SHIFTED:
209            mAlphabetShiftState.setShifted(true);
210            mSwitchActions.setAlphabetShiftLockShiftedKeyboard();
211            break;
212        }
213    }
214
215    private void setShiftLocked(final boolean shiftLocked) {
216        if (DEBUG_ACTION) {
217            Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this);
218        }
219        if (!mIsAlphabetMode) return;
220        if (shiftLocked && (!mAlphabetShiftState.isShiftLocked()
221                || mAlphabetShiftState.isShiftLockShifted())) {
222            mSwitchActions.setAlphabetShiftLockedKeyboard();
223        }
224        if (!shiftLocked && mAlphabetShiftState.isShiftLocked()) {
225            mSwitchActions.setAlphabetKeyboard();
226        }
227        mAlphabetShiftState.setShiftLocked(shiftLocked);
228    }
229
230    private void toggleAlphabetAndSymbols() {
231        if (DEBUG_ACTION) {
232            Log.d(TAG, "toggleAlphabetAndSymbols: " + this);
233        }
234        if (mIsAlphabetMode) {
235            mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
236            if (mPrevSymbolsKeyboardWasShifted) {
237                setSymbolsShiftedKeyboard();
238            } else {
239                setSymbolsKeyboard();
240            }
241            mPrevSymbolsKeyboardWasShifted = false;
242        } else {
243            mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
244            setAlphabetKeyboard();
245            if (mPrevMainKeyboardWasShiftLocked) {
246                setShiftLocked(true);
247            }
248            mPrevMainKeyboardWasShiftLocked = false;
249        }
250    }
251
252    // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
253    // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
254    private void resetKeyboardStateToAlphabet() {
255        if (DEBUG_ACTION) {
256            Log.d(TAG, "resetKeyboardStateToAlphabet: " + this);
257        }
258        if (mIsAlphabetMode) return;
259
260        mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted;
261        setAlphabetKeyboard();
262        if (mPrevMainKeyboardWasShiftLocked) {
263            setShiftLocked(true);
264        }
265        mPrevMainKeyboardWasShiftLocked = false;
266    }
267
268    private void toggleShiftInSymbols() {
269        if (mIsSymbolShifted) {
270            setSymbolsKeyboard();
271        } else {
272            setSymbolsShiftedKeyboard();
273        }
274    }
275
276    private void setAlphabetKeyboard() {
277        if (DEBUG_ACTION) {
278            Log.d(TAG, "setAlphabetKeyboard");
279        }
280
281        mSwitchActions.setAlphabetKeyboard();
282        mIsAlphabetMode = true;
283        mIsSymbolShifted = false;
284        mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
285        mSwitchState = SWITCH_STATE_ALPHA;
286        mSwitchActions.requestUpdatingShiftState();
287    }
288
289    private void setSymbolsKeyboard() {
290        if (DEBUG_ACTION) {
291            Log.d(TAG, "setSymbolsKeyboard");
292        }
293        mSwitchActions.setSymbolsKeyboard();
294        mIsAlphabetMode = false;
295        mIsSymbolShifted = false;
296        // Reset alphabet shift state.
297        mAlphabetShiftState.setShiftLocked(false);
298        mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
299    }
300
301    private void setSymbolsShiftedKeyboard() {
302        if (DEBUG_ACTION) {
303            Log.d(TAG, "setSymbolsShiftedKeyboard");
304        }
305        mSwitchActions.setSymbolsShiftedKeyboard();
306        mIsAlphabetMode = false;
307        mIsSymbolShifted = true;
308        // Reset alphabet shift state.
309        mAlphabetShiftState.setShiftLocked(false);
310        mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
311    }
312
313    public void onPressKey(final int code, final boolean isSinglePointer, final int autoCaps) {
314        if (DEBUG_EVENT) {
315            Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
316                   + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this);
317        }
318        if (code != Constants.CODE_SHIFT) {
319            // Because the double tap shift key timer is to detect two consecutive shift key press,
320            // it should be canceled when a non-shift key is pressed.
321            mSwitchActions.cancelDoubleTapShiftKeyTimer();
322        }
323        if (code == Constants.CODE_SHIFT) {
324            onPressShift();
325        } else if (code == Constants.CODE_CAPSLOCK) {
326            // Nothing to do here. See {@link #onReleaseKey(int,boolean)}.
327        } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
328            onPressSymbol();
329        } else {
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(final int code, final boolean withSliding) {
351        if (DEBUG_EVENT) {
352            Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code)
353                    + " sliding=" + withSliding + " " + this);
354        }
355        if (code == Constants.CODE_SHIFT) {
356            onReleaseShift(withSliding);
357        } else if (code == Constants.CODE_CAPSLOCK) {
358            setShiftLocked(!mAlphabetShiftState.isShiftLocked());
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        // If we are recapitalizing, we don't do any of the normal processing, including
441        // importantly the double tap timer.
442        if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
443            return;
444        }
445        if (mIsAlphabetMode) {
446            mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout();
447            if (!mIsInDoubleTapShiftKey) {
448                // This is first tap.
449                mSwitchActions.startDoubleTapShiftKeyTimer();
450            }
451            if (mIsInDoubleTapShiftKey) {
452                if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
453                    // Shift key has been double tapped while in manual shifted or automatic
454                    // shifted state.
455                    setShiftLocked(true);
456                } else {
457                    // Shift key has been double tapped while in normal state. This is the second
458                    // tap to disable shift locked state, so just ignore this.
459                }
460            } else {
461                if (mAlphabetShiftState.isShiftLocked()) {
462                    // Shift key is pressed while shift locked state, we will treat this state as
463                    // shift lock shifted state and mark as if shift key pressed while normal state.
464                    setShifted(SHIFT_LOCK_SHIFTED);
465                    mShiftKeyState.onPress();
466                } else if (mAlphabetShiftState.isAutomaticShifted()) {
467                    // Shift key is pressed while automatic shifted, we have to move to manual
468                    // shifted.
469                    setShifted(MANUAL_SHIFT);
470                    mShiftKeyState.onPress();
471                } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) {
472                    // In manual shifted state, we just record shift key has been pressing while
473                    // shifted state.
474                    mShiftKeyState.onPressOnShifted();
475                } else {
476                    // In base layout, chording or manual shifted mode is started.
477                    setShifted(MANUAL_SHIFT);
478                    mShiftKeyState.onPress();
479                }
480            }
481        } else {
482            // In symbol mode, just toggle symbol and symbol more keyboard.
483            toggleShiftInSymbols();
484            mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
485            mShiftKeyState.onPress();
486        }
487    }
488
489    private void onReleaseShift(final boolean withSliding) {
490        if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
491            // We are recapitalizing. We should match the keyboard state to the recapitalize
492            // state in priority.
493            updateShiftStateForRecapitalize(mRecapitalizeMode);
494        } else if (mIsAlphabetMode) {
495            final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
496            mIsInAlphabetUnshiftedFromShifted = false;
497            if (mIsInDoubleTapShiftKey) {
498                // Double tap shift key has been handled in {@link #onPressShift}, so that just
499                // ignore this release shift key here.
500                mIsInDoubleTapShiftKey = false;
501            } else if (mShiftKeyState.isChording()) {
502                if (mAlphabetShiftState.isShiftLockShifted()) {
503                    // After chording input while shift locked state.
504                    setShiftLocked(true);
505                } else {
506                    // After chording input while normal state.
507                    setShifted(UNSHIFT);
508                }
509                // After chording input, automatic shift state may have been changed depending on
510                // what characters were input.
511                mShiftKeyState.onRelease();
512                mSwitchActions.requestUpdatingShiftState();
513                return;
514            } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) {
515                // In shift locked state, shift has been pressed and slid out to other key.
516                setShiftLocked(true);
517            } else if (mAlphabetShiftState.isManualShifted() && withSliding) {
518                // Shift has been pressed and slid out to other key.
519                mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_SHIFT;
520            } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted()
521                    && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted())
522                    && !withSliding) {
523                // Shift has been long pressed, ignore this release.
524            } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) {
525                // Shift has been pressed without chording while shift locked state.
526                setShiftLocked(false);
527            } else if (mAlphabetShiftState.isShiftedOrShiftLocked()
528                    && mShiftKeyState.isPressingOnShifted() && !withSliding) {
529                // Shift has been pressed without chording while shifted state.
530                setShifted(UNSHIFT);
531                mIsInAlphabetUnshiftedFromShifted = true;
532            } else if (mAlphabetShiftState.isManualShiftedFromAutomaticShifted()
533                    && mShiftKeyState.isPressing() && !withSliding) {
534                // Shift has been pressed without chording while manual shifted transited from
535                // automatic shifted
536                setShifted(UNSHIFT);
537                mIsInAlphabetUnshiftedFromShifted = true;
538            }
539        } else {
540            // In symbol mode, switch back to the previous keyboard mode if the user chords the
541            // shift key and another key, then releases the shift key.
542            if (mShiftKeyState.isChording()) {
543                toggleShiftInSymbols();
544            }
545        }
546        mShiftKeyState.onRelease();
547    }
548
549    public void onFinishSlidingInput() {
550        if (DEBUG_EVENT) {
551            Log.d(TAG, "onFinishSlidingInput: " + this);
552        }
553        // Switch back to the previous keyboard mode if the user cancels sliding input.
554        switch (mSwitchState) {
555        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
556            toggleAlphabetAndSymbols();
557            break;
558        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
559            toggleShiftInSymbols();
560            break;
561        case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT:
562            setAlphabetKeyboard();
563            break;
564        }
565    }
566
567    private static boolean isSpaceCharacter(final int c) {
568        return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
569    }
570
571    public void onCodeInput(final int code, final int autoCaps) {
572        if (DEBUG_EVENT) {
573            Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code)
574                    + " autoCaps=" + autoCaps + " " + this);
575        }
576
577        switch (mSwitchState) {
578        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
579            if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
580                // Detected only the mode change key has been pressed, and then released.
581                if (mIsAlphabetMode) {
582                    mSwitchState = SWITCH_STATE_ALPHA;
583                } else {
584                    mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
585                }
586            }
587            break;
588        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
589            if (code == Constants.CODE_SHIFT) {
590                // Detected only the shift key has been pressed on symbol layout, and then released.
591                mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
592            }
593            break;
594        case SWITCH_STATE_SYMBOL_BEGIN:
595            if (!isSpaceCharacter(code) && (Constants.isLetterCode(code)
596                    || code == Constants.CODE_OUTPUT_TEXT)) {
597                mSwitchState = SWITCH_STATE_SYMBOL;
598            }
599            break;
600        case SWITCH_STATE_SYMBOL:
601            // Switch back to alpha keyboard mode if user types one or more non-space/enter
602            // characters followed by a space/enter.
603            if (isSpaceCharacter(code)) {
604                toggleAlphabetAndSymbols();
605                mPrevSymbolsKeyboardWasShifted = false;
606            }
607            break;
608        }
609
610        // If the code is a letter, update keyboard shift state.
611        if (Constants.isLetterCode(code)) {
612            updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
613        }
614    }
615
616    static String shiftModeToString(final int shiftMode) {
617        switch (shiftMode) {
618        case UNSHIFT: return "UNSHIFT";
619        case MANUAL_SHIFT: return "MANUAL";
620        case AUTOMATIC_SHIFT: return "AUTOMATIC";
621        default: return null;
622        }
623    }
624
625    private static String switchStateToString(final int switchState) {
626        switch (switchState) {
627        case SWITCH_STATE_ALPHA: return "ALPHA";
628        case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN";
629        case SWITCH_STATE_SYMBOL: return "SYMBOL";
630        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL";
631        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE";
632        case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: return "MOMENTARY-ALPHA_SHIFT";
633        default: return null;
634        }
635    }
636
637    @Override
638    public String toString() {
639        return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString()
640                : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS"))
641                + " shift=" + mShiftKeyState
642                + " symbol=" + mSymbolKeyState
643                + " switch=" + switchStateToString(mSwitchState) + "]";
644    }
645}
646