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