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