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