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 
17 package com.android.inputmethod.keyboard.internal;
18 
19 import android.text.TextUtils;
20 import android.util.Log;
21 
22 import com.android.inputmethod.latin.Constants;
23 import 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  */
37 public 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 {
setAlphabetKeyboard()43         public void setAlphabetKeyboard();
setAlphabetManualShiftedKeyboard()44         public void setAlphabetManualShiftedKeyboard();
setAlphabetAutomaticShiftedKeyboard()45         public void setAlphabetAutomaticShiftedKeyboard();
setAlphabetShiftLockedKeyboard()46         public void setAlphabetShiftLockedKeyboard();
setAlphabetShiftLockShiftedKeyboard()47         public void setAlphabetShiftLockShiftedKeyboard();
setEmojiKeyboard()48         public void setEmojiKeyboard();
setSymbolsKeyboard()49         public void setSymbolsKeyboard();
setSymbolsShiftedKeyboard()50         public void setSymbolsShiftedKeyboard();
51 
52         /**
53          * Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}.
54          */
requestUpdatingShiftState(final int currentAutoCapsState, final int currentRecapitalizeState)55         public void requestUpdatingShiftState(final int currentAutoCapsState,
56                 final int currentRecapitalizeState);
57 
startDoubleTapShiftKeyTimer()58         public void startDoubleTapShiftKeyTimer();
isInDoubleTapShiftKeyTimeout()59         public boolean isInDoubleTapShiftKeyTimeout();
cancelDoubleTapShiftKeyTimer()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
toString()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 
KeyboardState(final SwitchActions switchActions)116     public KeyboardState(final SwitchActions switchActions) {
117         mSwitchActions = switchActions;
118         mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
119     }
120 
onLoadKeyboard(final int currentAutoCapsState, final int currentRecapitalizeState)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 
onSaveKeyboardState()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 
onRestoreKeyboardState(final int currentAutoCapsState, final int currentRecapitalizeState)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 
setShifted(final int shiftMode)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 
setShiftLocked(final boolean shiftLocked)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 
toggleAlphabetAndSymbols(final int currentAutoCapsState, final int currentRecapitalizeState)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().
resetKeyboardStateToAlphabet(final int currentAutoCapsState, final int currentRecapitalizeState)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 
toggleShiftInSymbols()283     private void toggleShiftInSymbols() {
284         if (mIsSymbolShifted) {
285             setSymbolsKeyboard();
286         } else {
287             setSymbolsShiftedKeyboard();
288         }
289     }
290 
setAlphabetKeyboard(final int currentAutoCapsState, final int currentRecapitalizeState)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 
setSymbolsKeyboard()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 
setSymbolsShiftedKeyboard()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 
setEmojiKeyboard()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 
onPressKey(final int code, final boolean isSinglePointer, final int currentAutoCapsState, final int currentRecapitalizeState)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 
onReleaseKey(final int code, final boolean withSliding, final int currentAutoCapsState, final int currentRecapitalizeState)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 
onPressSymbol(final int currentAutoCapsState, final int currentRecapitalizeState)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 
onReleaseSymbol(final boolean withSliding, final int currentAutoCapsState, final int currentRecapitalizeState)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 
onUpdateShiftState(final int autoCaps, final int recapitalizeMode)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().
onResetKeyboardStateToAlphabet(final int currentAutoCapsState, final int currentRecapitalizeState)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 
updateShiftStateForRecapitalize(final int recapitalizeMode)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 
updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode)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 
onPressShift()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 
onReleaseShift(final boolean withSliding, final int currentAutoCapsState, final int currentRecapitalizeState)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 
onFinishSlidingInput(final int currentAutoCapsState, final int currentRecapitalizeState)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 
isSpaceOrEnter(final int c)609     private static boolean isSpaceOrEnter(final int c) {
610         return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
611     }
612 
onCodeInput(final int code, final int currentAutoCapsState, final int currentRecapitalizeState)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 
shiftModeToString(final int shiftMode)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 
switchStateToString(final int switchState)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
toString()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