KeyboardState.java revision f057b25cddb3e5e18bc56e8ddaab541044c6ba58
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 17package com.android.inputmethod.keyboard.internal; 18 19import android.text.TextUtils; 20import android.util.Log; 21 22import com.android.inputmethod.keyboard.Keyboard; 23 24/** 25 * Keyboard state machine. 26 * 27 * This class contains all keyboard state transition logic. 28 * 29 * The input events are {@link #onLoadKeyboard(String)}, {@link #onSaveKeyboardState()}, 30 * {@link #onPressKey(int)}, {@link #onReleaseKey(int, boolean)}, 31 * {@link #onCodeInput(int, boolean, boolean)}, {@link #onCancelInput(boolean)}, 32 * {@link #onUpdateShiftState(boolean)}. 33 * 34 * The actions are {@link SwitchActions}'s methods. 35 */ 36public class KeyboardState { 37 private static final String TAG = KeyboardState.class.getSimpleName(); 38 private static final boolean DEBUG_EVENT = false; 39 private static final boolean DEBUG_ACTION = false; 40 41 public interface SwitchActions { 42 public void setAlphabetKeyboard(); 43 public void setAlphabetManualShiftedKeyboard(); 44 public void setAlphabetAutomaticShiftedKeyboard(); 45 public void setAlphabetShiftLockedKeyboard(); 46 public void setAlphabetShiftLockShiftedKeyboard(); 47 public void setSymbolsKeyboard(); 48 public void setSymbolsShiftedKeyboard(); 49 50 /** 51 * Request to call back {@link KeyboardState#onUpdateShiftState(boolean)}. 52 */ 53 public void requestUpdatingShiftState(); 54 } 55 56 private final SwitchActions mSwitchActions; 57 58 private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift"); 59 private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol"); 60 61 // TODO: Merge {@link #mSwitchState}, {@link #mIsAlphabetMode}, {@link #mAlphabetShiftState}, 62 // {@link #mIsSymbolShifted}, {@link #mPrevMainKeyboardWasShiftLocked}, and 63 // {@link #mPrevSymbolsKeyboardWasShifted} into single state variable. 64 private static final int SWITCH_STATE_ALPHA = 0; 65 private static final int SWITCH_STATE_SYMBOL_BEGIN = 1; 66 private static final int SWITCH_STATE_SYMBOL = 2; 67 private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3; 68 private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4; 69 private int mSwitchState = SWITCH_STATE_ALPHA; 70 private String mLayoutSwitchBackSymbols; 71 72 private boolean mIsAlphabetMode; 73 private KeyboardShiftState mAlphabetShiftState = new KeyboardShiftState(); 74 private boolean mIsSymbolShifted; 75 private boolean mPrevMainKeyboardWasShiftLocked; 76 private boolean mPrevSymbolsKeyboardWasShifted; 77 78 private final SavedKeyboardState mSavedKeyboardState = new SavedKeyboardState(); 79 80 static class SavedKeyboardState { 81 public boolean mIsValid; 82 public boolean mIsAlphabetMode; 83 public boolean mIsShiftLocked; 84 public boolean mIsShifted; 85 86 @Override 87 public String toString() { 88 if (!mIsValid) return "INVALID"; 89 if (mIsAlphabetMode) { 90 if (mIsShiftLocked) return "ALPHABET_SHIFT_LOCKED"; 91 return mIsShifted ? "ALPHABET_SHIFTED" : "ALPHABET"; 92 } else { 93 return mIsShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS"; 94 } 95 } 96 } 97 98 public KeyboardState(SwitchActions switchActions) { 99 mSwitchActions = switchActions; 100 } 101 102 public void onLoadKeyboard(String layoutSwitchBackSymbols) { 103 if (DEBUG_EVENT) { 104 Log.d(TAG, "onLoadKeyboard: " + this); 105 } 106 mLayoutSwitchBackSymbols = layoutSwitchBackSymbols; 107 // Reset alphabet shift state. 108 mAlphabetShiftState.setShiftLocked(false); 109 mPrevMainKeyboardWasShiftLocked = false; 110 mPrevSymbolsKeyboardWasShifted = false; 111 mShiftKeyState.onRelease(); 112 mSymbolKeyState.onRelease(); 113 onRestoreKeyboardState(); 114 } 115 116 public void onSaveKeyboardState() { 117 final SavedKeyboardState state = mSavedKeyboardState; 118 state.mIsAlphabetMode = mIsAlphabetMode; 119 if (mIsAlphabetMode) { 120 state.mIsShiftLocked = mAlphabetShiftState.isShiftLocked(); 121 state.mIsShifted = !state.mIsShiftLocked 122 && mAlphabetShiftState.isShiftedOrShiftLocked(); 123 } else { 124 state.mIsShiftLocked = false; 125 state.mIsShifted = mIsSymbolShifted; 126 } 127 state.mIsValid = true; 128 if (DEBUG_EVENT) { 129 Log.d(TAG, "onSaveKeyboardState: saved=" + state + " " + this); 130 } 131 } 132 133 private void onRestoreKeyboardState() { 134 final SavedKeyboardState state = mSavedKeyboardState; 135 if (DEBUG_EVENT) { 136 Log.d(TAG, "onRestoreKeyboardState: saved=" + state + " " + this); 137 } 138 if (!state.mIsValid || state.mIsAlphabetMode) { 139 setAlphabetKeyboard(); 140 } else { 141 if (state.mIsShifted) { 142 setSymbolsShiftedKeyboard(); 143 } else { 144 setSymbolsKeyboard(); 145 } 146 } 147 148 if (!state.mIsValid) return; 149 state.mIsValid = false; 150 151 if (state.mIsAlphabetMode) { 152 setShiftLocked(state.mIsShiftLocked); 153 if (!state.mIsShiftLocked) { 154 setShifted(state.mIsShifted ? MANUAL_SHIFT : UNSHIFT); 155 } 156 } 157 } 158 159 private static final int UNSHIFT = 0; 160 private static final int MANUAL_SHIFT = 1; 161 private static final int AUTOMATIC_SHIFT = 2; 162 private static final int SHIFT_LOCK_SHIFTED = 3; 163 164 private void setShifted(int shiftMode) { 165 if (DEBUG_ACTION) { 166 Log.d(TAG, "setShifted: shiftMode=" + shiftModeToString(shiftMode) + " " + this); 167 } 168 if (!mIsAlphabetMode) return; 169 final int prevShiftMode; 170 if (mAlphabetShiftState.isAutomaticTemporaryUpperCase()) { 171 prevShiftMode = AUTOMATIC_SHIFT; 172 } else if (mAlphabetShiftState.isManualTemporaryUpperCase()) { 173 prevShiftMode = MANUAL_SHIFT; 174 } else { 175 prevShiftMode = UNSHIFT; 176 } 177 switch (shiftMode) { 178 case AUTOMATIC_SHIFT: 179 mAlphabetShiftState.setAutomaticTemporaryUpperCase(); 180 if (shiftMode != prevShiftMode) { 181 mSwitchActions.setAlphabetAutomaticShiftedKeyboard(); 182 } 183 break; 184 case MANUAL_SHIFT: 185 mAlphabetShiftState.setShifted(true); 186 if (shiftMode != prevShiftMode) { 187 mSwitchActions.setAlphabetManualShiftedKeyboard(); 188 } 189 break; 190 case UNSHIFT: 191 mAlphabetShiftState.setShifted(false); 192 if (shiftMode != prevShiftMode) { 193 mSwitchActions.setAlphabetKeyboard(); 194 } 195 break; 196 case SHIFT_LOCK_SHIFTED: 197 mAlphabetShiftState.setShifted(true); 198 mSwitchActions.setAlphabetShiftLockShiftedKeyboard(); 199 break; 200 } 201 } 202 203 private void setShiftLocked(boolean shiftLocked) { 204 if (DEBUG_ACTION) { 205 Log.d(TAG, "setShiftLocked: shiftLocked=" + shiftLocked + " " + this); 206 } 207 if (!mIsAlphabetMode) return; 208 if (shiftLocked && (!mAlphabetShiftState.isShiftLocked() 209 || mAlphabetShiftState.isShiftLockShifted())) { 210 mSwitchActions.setAlphabetShiftLockedKeyboard(); 211 } 212 if (!shiftLocked && mAlphabetShiftState.isShiftLocked()) { 213 mSwitchActions.setAlphabetKeyboard(); 214 } 215 mAlphabetShiftState.setShiftLocked(shiftLocked); 216 } 217 218 private void toggleAlphabetAndSymbols() { 219 if (DEBUG_ACTION) { 220 Log.d(TAG, "toggleAlphabetAndSymbols: " + this); 221 } 222 if (mIsAlphabetMode) { 223 mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked(); 224 if (mPrevSymbolsKeyboardWasShifted) { 225 setSymbolsShiftedKeyboard(); 226 } else { 227 setSymbolsKeyboard(); 228 } 229 mPrevSymbolsKeyboardWasShifted = false; 230 } else { 231 mPrevSymbolsKeyboardWasShifted = mIsSymbolShifted; 232 setAlphabetKeyboard(); 233 if (mPrevMainKeyboardWasShiftLocked) { 234 setShiftLocked(true); 235 } 236 mPrevMainKeyboardWasShiftLocked = false; 237 } 238 } 239 240 private void toggleShiftInSymbols() { 241 if (mIsSymbolShifted) { 242 setSymbolsKeyboard(); 243 } else { 244 setSymbolsShiftedKeyboard(); 245 } 246 } 247 248 private void setAlphabetKeyboard() { 249 if (DEBUG_ACTION) { 250 Log.d(TAG, "setAlphabetKeyboard"); 251 } 252 mSwitchActions.setAlphabetKeyboard(); 253 mIsAlphabetMode = true; 254 mIsSymbolShifted = false; 255 mSwitchState = SWITCH_STATE_ALPHA; 256 mSwitchActions.requestUpdatingShiftState(); 257 } 258 259 // TODO: Make this method private 260 public void setSymbolsKeyboard() { 261 if (DEBUG_ACTION) { 262 Log.d(TAG, "setSymbolsKeyboard"); 263 } 264 mSwitchActions.setSymbolsKeyboard(); 265 mIsAlphabetMode = false; 266 mIsSymbolShifted = false; 267 // Reset alphabet shift state. 268 mAlphabetShiftState.setShiftLocked(false); 269 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 270 } 271 272 private void setSymbolsShiftedKeyboard() { 273 if (DEBUG_ACTION) { 274 Log.d(TAG, "setSymbolsShiftedKeyboard"); 275 } 276 mSwitchActions.setSymbolsShiftedKeyboard(); 277 mIsAlphabetMode = false; 278 mIsSymbolShifted = true; 279 // Reset alphabet shift state. 280 mAlphabetShiftState.setShiftLocked(false); 281 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 282 } 283 284 public void onPressKey(int code) { 285 if (DEBUG_EVENT) { 286 Log.d(TAG, "onPressKey: code=" + Keyboard.printableCode(code) + " " + this); 287 } 288 if (code == Keyboard.CODE_SHIFT) { 289 onPressShift(); 290 } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 291 onPressSymbol(); 292 } else { 293 mShiftKeyState.onOtherKeyPressed(); 294 mSymbolKeyState.onOtherKeyPressed(); 295 } 296 } 297 298 public void onReleaseKey(int code, boolean withSliding) { 299 if (DEBUG_EVENT) { 300 Log.d(TAG, "onReleaseKey: code=" + Keyboard.printableCode(code) 301 + " sliding=" + withSliding + " " + this); 302 } 303 if (code == Keyboard.CODE_SHIFT) { 304 onReleaseShift(withSliding); 305 } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 306 onReleaseSymbol(withSliding); 307 } 308 } 309 310 private void onPressSymbol() { 311 toggleAlphabetAndSymbols(); 312 mSymbolKeyState.onPress(); 313 mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL; 314 } 315 316 private void onReleaseSymbol(boolean withSliding) { 317 if (mSymbolKeyState.isChording()) { 318 // Switch back to the previous keyboard mode if the user chords the mode change key and 319 // another key, then releases the mode change key. 320 toggleAlphabetAndSymbols(); 321 } else if (!withSliding) { 322 // If the mode change key is being released without sliding, we should forget the 323 // previous symbols keyboard shift state and simply switch back to symbols layout 324 // (never symbols shifted) next time the mode gets changed to symbols layout. 325 mPrevSymbolsKeyboardWasShifted = false; 326 } 327 mSymbolKeyState.onRelease(); 328 } 329 330 public void onUpdateShiftState(boolean autoCaps) { 331 if (DEBUG_EVENT) { 332 Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this); 333 } 334 updateAlphabetShiftState(autoCaps); 335 } 336 337 private void updateAlphabetShiftState(boolean autoCaps) { 338 if (!mIsAlphabetMode) return; 339 if (!mAlphabetShiftState.isShiftLocked() && !mShiftKeyState.isIgnoring()) { 340 if (mShiftKeyState.isReleasing() && autoCaps) { 341 // Only when shift key is releasing, automatic temporary upper case will be set. 342 setShifted(AUTOMATIC_SHIFT); 343 } else { 344 setShifted(mShiftKeyState.isChording() ? MANUAL_SHIFT : UNSHIFT); 345 } 346 } 347 } 348 349 private void onPressShift() { 350 if (mIsAlphabetMode) { 351 if (mAlphabetShiftState.isShiftLocked()) { 352 // Shift key is pressed while caps lock state, we will treat this state as shifted 353 // caps lock state and mark as if shift key pressed while normal state. 354 setShifted(SHIFT_LOCK_SHIFTED); 355 mShiftKeyState.onPress(); 356 } else if (mAlphabetShiftState.isAutomaticTemporaryUpperCase()) { 357 // Shift key is pressed while automatic temporary upper case, we have to move to 358 // manual temporary upper case. 359 setShifted(MANUAL_SHIFT); 360 mShiftKeyState.onPress(); 361 } else if (mAlphabetShiftState.isShiftedOrShiftLocked()) { 362 // In manual upper case state, we just record shift key has been pressing while 363 // shifted state. 364 mShiftKeyState.onPressOnShifted(); 365 } else { 366 // In base layout, chording or manual temporary upper case mode is started. 367 setShifted(MANUAL_SHIFT); 368 mShiftKeyState.onPress(); 369 } 370 } else { 371 // In symbol mode, just toggle symbol and symbol more keyboard. 372 toggleShiftInSymbols(); 373 mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; 374 mShiftKeyState.onPress(); 375 } 376 } 377 378 private void onReleaseShift(boolean withSliding) { 379 if (mIsAlphabetMode) { 380 final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked(); 381 if (mShiftKeyState.isChording()) { 382 if (mAlphabetShiftState.isShiftLockShifted()) { 383 // After chording input while caps lock state. 384 setShiftLocked(true); 385 } else { 386 // After chording input while normal state. 387 setShifted(UNSHIFT); 388 } 389 } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) { 390 // In caps lock state, shift has been pressed and slid out to other key. 391 setShiftLocked(true); 392 } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted() 393 && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted()) 394 && !withSliding) { 395 // Shift has been long pressed, ignore this release. 396 } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) { 397 // Shift has been pressed without chording while caps lock state. 398 setShiftLocked(false); 399 } else if (mAlphabetShiftState.isShiftedOrShiftLocked() 400 && mShiftKeyState.isPressingOnShifted() && !withSliding) { 401 // Shift has been pressed without chording while shifted state. 402 setShifted(UNSHIFT); 403 } else if (mAlphabetShiftState.isManualTemporaryUpperCaseFromAuto() 404 && mShiftKeyState.isPressing() && !withSliding) { 405 // Shift has been pressed without chording while manual temporary upper case 406 // transited from automatic temporary upper case. 407 setShifted(UNSHIFT); 408 } 409 } else { 410 // In symbol mode, switch back to the previous keyboard mode if the user chords the 411 // shift key and another key, then releases the shift key. 412 if (mShiftKeyState.isChording()) { 413 toggleShiftInSymbols(); 414 } 415 } 416 mShiftKeyState.onRelease(); 417 } 418 419 public void onCancelInput(boolean isSinglePointer) { 420 if (DEBUG_EVENT) { 421 Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this); 422 } 423 // Switch back to the previous keyboard mode if the user cancels sliding input. 424 if (isSinglePointer) { 425 if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) { 426 toggleAlphabetAndSymbols(); 427 } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) { 428 toggleShiftInSymbols(); 429 } 430 } 431 } 432 433 public boolean isInMomentarySwitchState() { 434 return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL 435 || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE; 436 } 437 438 private static boolean isSpaceCharacter(int c) { 439 return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER; 440 } 441 442 private boolean isLayoutSwitchBackCharacter(int c) { 443 if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false; 444 if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true; 445 return false; 446 } 447 448 public void onCodeInput(int code, boolean isSinglePointer, boolean autoCaps) { 449 if (DEBUG_EVENT) { 450 Log.d(TAG, "onCodeInput: code=" + Keyboard.printableCode(code) 451 + " single=" + isSinglePointer 452 + " autoCaps=" + autoCaps + " " + this); 453 } 454 455 if (mIsAlphabetMode && code == Keyboard.CODE_CAPSLOCK) { 456 if (mAlphabetShiftState.isShiftLocked()) { 457 setShiftLocked(false); 458 // Shift key is long pressed or double tapped while caps lock state, we will 459 // toggle back to normal state. And mark as if shift key is released. 460 mShiftKeyState.onRelease(); 461 } else { 462 setShiftLocked(true); 463 } 464 } 465 466 switch (mSwitchState) { 467 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: 468 if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { 469 // Detected only the mode change key has been pressed, and then released. 470 if (mIsAlphabetMode) { 471 mSwitchState = SWITCH_STATE_ALPHA; 472 } else { 473 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 474 } 475 } else if (isSinglePointer) { 476 // Switch back to the previous keyboard mode if the user pressed the mode change key 477 // and slid to other key, then released the finger. 478 // If the user cancels the sliding input, switching back to the previous keyboard 479 // mode is handled by {@link #onCancelInput}. 480 toggleAlphabetAndSymbols(); 481 } 482 break; 483 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: 484 if (code == Keyboard.CODE_SHIFT) { 485 // Detected only the shift key has been pressed on symbol layout, and then released. 486 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; 487 } else if (isSinglePointer) { 488 // Switch back to the previous keyboard mode if the user pressed the shift key on 489 // symbol mode and slid to other key, then released the finger. 490 toggleShiftInSymbols(); 491 mSwitchState = SWITCH_STATE_SYMBOL; 492 } 493 break; 494 case SWITCH_STATE_SYMBOL_BEGIN: 495 if (!isSpaceCharacter(code) && (Keyboard.isLetterCode(code) 496 || code == Keyboard.CODE_OUTPUT_TEXT)) { 497 mSwitchState = SWITCH_STATE_SYMBOL; 498 } 499 // Switch back to alpha keyboard mode immediately if user types a quote character. 500 if (isLayoutSwitchBackCharacter(code)) { 501 toggleAlphabetAndSymbols(); 502 } 503 break; 504 case SWITCH_STATE_SYMBOL: 505 // Switch back to alpha keyboard mode if user types one or more non-space/enter 506 // characters followed by a space/enter or a quote character. 507 if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) { 508 toggleAlphabetAndSymbols(); 509 } 510 break; 511 } 512 513 // If the code is a letter, update keyboard shift state. 514 if (Keyboard.isLetterCode(code)) { 515 updateAlphabetShiftState(autoCaps); 516 } 517 } 518 519 private static String shiftModeToString(int shiftMode) { 520 switch (shiftMode) { 521 case UNSHIFT: return "UNSHIFT"; 522 case MANUAL_SHIFT: return "MANUAL"; 523 case AUTOMATIC_SHIFT: return "AUTOMATIC"; 524 default: return null; 525 } 526 } 527 528 private static String switchStateToString(int switchState) { 529 switch (switchState) { 530 case SWITCH_STATE_ALPHA: return "ALPHA"; 531 case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN"; 532 case SWITCH_STATE_SYMBOL: return "SYMBOL"; 533 case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL"; 534 case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE"; 535 default: return null; 536 } 537 } 538 539 @Override 540 public String toString() { 541 return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() 542 : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) 543 + " shift=" + mShiftKeyState 544 + " symbol=" + mSymbolKeyState 545 + " switch=" + switchStateToString(mSwitchState) + "]"; 546 } 547} 548