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