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