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