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