PointerTracker.java revision 8632bff2d5a8e1160989008dea6eff4b94b065dd
1/* 2 * Copyright (C) 2010 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; 18 19import com.android.inputmethod.keyboard.KeyboardView.UIHandler; 20import com.android.inputmethod.latin.LatinImeLogger; 21import com.android.inputmethod.latin.R; 22import com.android.inputmethod.latin.SubtypeSwitcher; 23 24import android.content.res.Resources; 25import android.util.Log; 26import android.view.MotionEvent; 27 28import java.util.Arrays; 29import java.util.List; 30 31public class PointerTracker { 32 private static final String TAG = PointerTracker.class.getSimpleName(); 33 private static final boolean ENABLE_ASSERTION = false; 34 private static final boolean DEBUG_EVENT = false; 35 private static final boolean DEBUG_MOVE_EVENT = false; 36 private static final boolean DEBUG_LISTENER = false; 37 private static boolean DEBUG_MODE = LatinImeLogger.sDBG; 38 39 public interface UIProxy { 40 public void invalidateKey(Key key); 41 public void showKeyPreview(int keyIndex, PointerTracker tracker); 42 public void dismissKeyPreview(PointerTracker tracker); 43 public boolean hasDistinctMultitouch(); 44 } 45 46 public final int mPointerId; 47 48 // Timing constants 49 private final int mDelayBeforeKeyRepeatStart; 50 private final int mLongPressKeyTimeout; 51 private final int mLongPressShiftKeyTimeout; 52 53 private final KeyboardView mKeyboardView; 54 private final UIProxy mProxy; 55 private final UIHandler mHandler; 56 private final KeyDetector mKeyDetector; 57 private KeyboardActionListener mListener = EMPTY_LISTENER; 58 private final KeyboardSwitcher mKeyboardSwitcher; 59 private final boolean mHasDistinctMultitouch; 60 private final boolean mConfigSlidingKeyInputEnabled; 61 62 private final int mTouchNoiseThresholdMillis; 63 private final int mTouchNoiseThresholdDistanceSquared; 64 65 private Keyboard mKeyboard; 66 private List<Key> mKeys; 67 private int mKeyHysteresisDistanceSquared = -1; 68 private int mKeyQuarterWidthSquared; 69 70 private final PointerTrackerKeyState mKeyState; 71 72 // true if keyboard layout has been changed. 73 private boolean mKeyboardLayoutHasBeenChanged; 74 75 // true if event is already translated to a key action (long press or mini-keyboard) 76 private boolean mKeyAlreadyProcessed; 77 78 // true if this pointer is repeatable key 79 private boolean mIsRepeatableKey; 80 81 // true if this pointer is in sliding key input 82 private boolean mIsInSlidingKeyInput; 83 84 // true if sliding key is allowed. 85 private boolean mIsAllowedSlidingKeyInput; 86 87 // ignore modifier key if true 88 private boolean mIgnoreModifierKey; 89 90 // TODO: Remove these hacking variables 91 // true if this pointer is in sliding language switch 92 private boolean mIsInSlidingLanguageSwitch; 93 private int mSpaceKeyIndex; 94 private final SubtypeSwitcher mSubtypeSwitcher; 95 96 // Empty {@link KeyboardActionListener} 97 private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() { 98 @Override 99 public void onPress(int primaryCode, boolean withSliding) {} 100 @Override 101 public void onRelease(int primaryCode, boolean withSliding) {} 102 @Override 103 public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {} 104 @Override 105 public void onTextInput(CharSequence text) {} 106 @Override 107 public void onCancelInput() {} 108 @Override 109 public void onSwipeDown() {} 110 }; 111 112 public PointerTracker(int id, KeyboardView keyboardView, UIHandler handler, 113 KeyDetector keyDetector, UIProxy proxy) { 114 if (proxy == null || handler == null || keyDetector == null) 115 throw new NullPointerException(); 116 mPointerId = id; 117 mKeyboardView = keyboardView; 118 mProxy = proxy; 119 mHandler = handler; 120 mKeyDetector = keyDetector; 121 mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 122 mKeyState = new PointerTrackerKeyState(keyDetector); 123 mHasDistinctMultitouch = proxy.hasDistinctMultitouch(); 124 final Resources res = mKeyboardView.getResources(); 125 mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled); 126 mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start); 127 mLongPressKeyTimeout = res.getInteger(R.integer.config_long_press_key_timeout); 128 mLongPressShiftKeyTimeout = res.getInteger(R.integer.config_long_press_shift_key_timeout); 129 mTouchNoiseThresholdMillis = res.getInteger(R.integer.config_touch_noise_threshold_millis); 130 final float touchNoiseThresholdDistance = res.getDimension( 131 R.dimen.config_touch_noise_threshold_distance); 132 mTouchNoiseThresholdDistanceSquared = (int)( 133 touchNoiseThresholdDistance * touchNoiseThresholdDistance); 134 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 135 } 136 137 public void setOnKeyboardActionListener(KeyboardActionListener listener) { 138 mListener = listener; 139 } 140 141 // Returns true if keyboard has been changed by this callback. 142 private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) { 143 final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); 144 if (DEBUG_LISTENER) 145 Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding 146 + " ignoreModifier=" + ignoreModifierKey); 147 if (ignoreModifierKey) 148 return false; 149 if (key.mEnabled) { 150 mListener.onPress(key.mCode, withSliding); 151 final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; 152 mKeyboardLayoutHasBeenChanged = false; 153 return keyboardLayoutHasBeenChanged; 154 } 155 return false; 156 } 157 158 // Note that we need primaryCode argument because the keyboard may in shifted state and the 159 // primaryCode is different from {@link Key#mCode}. 160 private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) { 161 final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); 162 if (DEBUG_LISTENER) 163 Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode) 164 + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y 165 + " ignoreModifier=" + ignoreModifierKey); 166 if (ignoreModifierKey) 167 return; 168 if (key.mEnabled) 169 mListener.onCodeInput(primaryCode, keyCodes, x, y); 170 } 171 172 private void callListenerOnTextInput(Key key) { 173 if (DEBUG_LISTENER) 174 Log.d(TAG, "onTextInput: text=" + key.mOutputText); 175 if (key.mEnabled) 176 mListener.onTextInput(key.mOutputText); 177 } 178 179 // Note that we need primaryCode argument because the keyboard may in shifted state and the 180 // primaryCode is different from {@link Key#mCode}. 181 private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) { 182 final boolean ignoreModifierKey = mIgnoreModifierKey && isModifierCode(key.mCode); 183 if (DEBUG_LISTENER) 184 Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode) + " sliding=" 185 + withSliding + " ignoreModifier=" + ignoreModifierKey); 186 if (ignoreModifierKey) 187 return; 188 if (key.mEnabled) 189 mListener.onRelease(primaryCode, withSliding); 190 } 191 192 private void callListenerOnCancelInput() { 193 if (DEBUG_LISTENER) 194 Log.d(TAG, "onCancelInput"); 195 mListener.onCancelInput(); 196 } 197 198 public void setKeyboard(Keyboard keyboard, float keyHysteresisDistance) { 199 if (keyboard == null || keyHysteresisDistance < 0) 200 throw new IllegalArgumentException(); 201 mKeyboard = keyboard; 202 mKeys = keyboard.getKeys(); 203 mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance); 204 final int keyQuarterWidth = keyboard.getKeyWidth() / 4; 205 mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; 206 // Mark that keyboard layout has been changed. 207 mKeyboardLayoutHasBeenChanged = true; 208 } 209 210 public boolean isInSlidingKeyInput() { 211 return mIsInSlidingKeyInput; 212 } 213 214 private boolean isValidKeyIndex(int keyIndex) { 215 return keyIndex >= 0 && keyIndex < mKeys.size(); 216 } 217 218 public Key getKey(int keyIndex) { 219 return isValidKeyIndex(keyIndex) ? mKeys.get(keyIndex) : null; 220 } 221 222 private static boolean isModifierCode(int primaryCode) { 223 return primaryCode == Keyboard.CODE_SHIFT 224 || primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL; 225 } 226 227 private boolean isModifierInternal(int keyIndex) { 228 final Key key = getKey(keyIndex); 229 return key == null ? false : isModifierCode(key.mCode); 230 } 231 232 public boolean isModifier() { 233 return isModifierInternal(mKeyState.getKeyIndex()); 234 } 235 236 private boolean isOnModifierKey(int x, int y) { 237 return isModifierInternal(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); 238 } 239 240 public boolean isOnShiftKey(int x, int y) { 241 final Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null)); 242 return key != null && key.mCode == Keyboard.CODE_SHIFT; 243 } 244 245 public int getKeyIndexOn(int x, int y) { 246 return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); 247 } 248 249 public boolean isSpaceKey(int keyIndex) { 250 Key key = getKey(keyIndex); 251 return key != null && key.mCode == Keyboard.CODE_SPACE; 252 } 253 254 public void setReleasedKeyGraphics() { 255 setReleasedKeyGraphics(mKeyState.getKeyIndex()); 256 } 257 258 private void setReleasedKeyGraphics(int keyIndex) { 259 final Key key = getKey(keyIndex); 260 if (key != null) { 261 key.onReleased(); 262 mProxy.invalidateKey(key); 263 } 264 } 265 266 private void setPressedKeyGraphics(int keyIndex) { 267 final Key key = getKey(keyIndex); 268 if (key != null && key.mEnabled) { 269 key.onPressed(); 270 mProxy.invalidateKey(key); 271 } 272 } 273 274 private void checkAssertion(PointerTrackerQueue queue) { 275 if (mHasDistinctMultitouch && queue == null) 276 throw new RuntimeException( 277 "PointerTrackerQueue must be passed on distinct multi touch device"); 278 if (!mHasDistinctMultitouch && queue != null) 279 throw new RuntimeException( 280 "PointerTrackerQueue must be null on non-distinct multi touch device"); 281 } 282 283 public void onTouchEvent(int action, int x, int y, long eventTime, PointerTrackerQueue queue) { 284 switch (action) { 285 case MotionEvent.ACTION_MOVE: 286 onMoveEvent(x, y, eventTime, queue); 287 break; 288 case MotionEvent.ACTION_DOWN: 289 case MotionEvent.ACTION_POINTER_DOWN: 290 onDownEvent(x, y, eventTime, queue); 291 break; 292 case MotionEvent.ACTION_UP: 293 case MotionEvent.ACTION_POINTER_UP: 294 onUpEvent(x, y, eventTime, queue); 295 break; 296 case MotionEvent.ACTION_CANCEL: 297 onCancelEvent(x, y, eventTime, queue); 298 break; 299 } 300 } 301 302 public void onDownEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { 303 if (ENABLE_ASSERTION) checkAssertion(queue); 304 if (DEBUG_EVENT) 305 printTouchEvent("onDownEvent:", x, y, eventTime); 306 307 // Naive up-to-down noise filter. 308 final long deltaT = eventTime - mKeyState.getUpTime(); 309 if (deltaT < mTouchNoiseThresholdMillis) { 310 final int dx = x - mKeyState.getLastX(); 311 final int dy = y - mKeyState.getLastY(); 312 final int distanceSquared = (dx * dx + dy * dy); 313 if (distanceSquared < mTouchNoiseThresholdDistanceSquared) { 314 if (DEBUG_MODE) 315 Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT 316 + " distance=" + distanceSquared); 317 mKeyAlreadyProcessed = true; 318 return; 319 } 320 } 321 322 if (queue != null) { 323 if (isOnModifierKey(x, y)) { 324 // Before processing a down event of modifier key, all pointers already being 325 // tracked should be released. 326 queue.releaseAllPointers(eventTime); 327 } 328 queue.add(this); 329 } 330 onDownEventInternal(x, y, eventTime); 331 } 332 333 private void onDownEventInternal(int x, int y, long eventTime) { 334 int keyIndex = mKeyState.onDownKey(x, y, eventTime); 335 // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding 336 // from modifier key, or 3) this pointer is on mini-keyboard. 337 mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex) 338 || mKeyDetector instanceof MiniKeyboardKeyDetector; 339 mKeyboardLayoutHasBeenChanged = false; 340 mKeyAlreadyProcessed = false; 341 mIsRepeatableKey = false; 342 mIsInSlidingKeyInput = false; 343 mIsInSlidingLanguageSwitch = false; 344 mIgnoreModifierKey = false; 345 if (isValidKeyIndex(keyIndex)) { 346 // This onPress call may have changed keyboard layout. Those cases are detected at 347 // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new 348 // keyboard layout. 349 if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false)) 350 keyIndex = mKeyState.onDownKey(x, y, eventTime); 351 352 startRepeatKey(keyIndex); 353 startLongPressTimer(keyIndex); 354 showKeyPreview(keyIndex); 355 setPressedKeyGraphics(keyIndex); 356 } 357 } 358 359 private void startSlidingKeyInput(Key key) { 360 if (!mIsInSlidingKeyInput) 361 mIgnoreModifierKey = isModifierCode(key.mCode); 362 mIsInSlidingKeyInput = true; 363 } 364 365 public void onMoveEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { 366 if (ENABLE_ASSERTION) checkAssertion(queue); 367 if (DEBUG_MOVE_EVENT) 368 printTouchEvent("onMoveEvent:", x, y, eventTime); 369 if (mKeyAlreadyProcessed) 370 return; 371 final PointerTrackerKeyState keyState = mKeyState; 372 373 // TODO: Remove this hacking code 374 if (mIsInSlidingLanguageSwitch) { 375 ((LatinKeyboard)mKeyboard).updateSpacebarPreviewIcon(x - keyState.getKeyX()); 376 showKeyPreview(mSpaceKeyIndex); 377 return; 378 } 379 final int lastX = keyState.getLastX(); 380 final int lastY = keyState.getLastY(); 381 final int oldKeyIndex = keyState.getKeyIndex(); 382 final Key oldKey = getKey(oldKeyIndex); 383 int keyIndex = keyState.onMoveKey(x, y); 384 if (isValidKeyIndex(keyIndex)) { 385 if (oldKey == null) { 386 // The pointer has been slid in to the new key, but the finger was not on any keys. 387 // In this case, we must call onPress() to notify that the new key is being pressed. 388 // This onPress call may have changed keyboard layout. Those cases are detected at 389 // {@link #setKeyboard}. In those cases, we should update keyIndex according to the 390 // new keyboard layout. 391 if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) 392 keyIndex = keyState.onMoveKey(x, y); 393 keyState.onMoveToNewKey(keyIndex, x, y); 394 startLongPressTimer(keyIndex); 395 showKeyPreview(keyIndex); 396 setPressedKeyGraphics(keyIndex); 397 } else if (isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) { 398 // The pointer has been slid in to the new key from the previous key, we must call 399 // onRelease() first to notify that the previous key has been released, then call 400 // onPress() to notify that the new key is being pressed. 401 setReleasedKeyGraphics(oldKeyIndex); 402 callListenerOnRelease(oldKey, oldKey.mCode, true); 403 startSlidingKeyInput(oldKey); 404 mHandler.cancelKeyTimers(); 405 startRepeatKey(keyIndex); 406 if (mIsAllowedSlidingKeyInput) { 407 // This onPress call may have changed keyboard layout. Those cases are detected 408 // at {@link #setKeyboard}. In those cases, we should update keyIndex according 409 // to the new keyboard layout. 410 if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true)) 411 keyIndex = keyState.onMoveKey(x, y); 412 keyState.onMoveToNewKey(keyIndex, x, y); 413 startLongPressTimer(keyIndex); 414 setPressedKeyGraphics(keyIndex); 415 showKeyPreview(keyIndex); 416 } else { 417 // HACK: On some devices, quick successive touches may be translated to sudden 418 // move by touch panel firmware. This hack detects the case and translates the 419 // move event to successive up and down events. 420 final int dx = x - lastX; 421 final int dy = y - lastY; 422 final int lastMoveSquared = dx * dx + dy * dy; 423 if (lastMoveSquared >= mKeyQuarterWidthSquared) { 424 if (DEBUG_MODE) 425 Log.w(TAG, String.format("onMoveEvent: sudden move is translated to " 426 + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y)); 427 onUpEventInternal(lastX, lastY, eventTime, true); 428 onDownEventInternal(x, y, eventTime); 429 } else { 430 mKeyAlreadyProcessed = true; 431 dismissKeyPreview(); 432 setReleasedKeyGraphics(oldKeyIndex); 433 } 434 } 435 } 436 // TODO: Remove this hack code 437 else if (isSpaceKey(keyIndex) && !mIsInSlidingLanguageSwitch 438 && mKeyboard instanceof LatinKeyboard) { 439 final LatinKeyboard keyboard = ((LatinKeyboard)mKeyboard); 440 if (mSubtypeSwitcher.useSpacebarLanguageSwitcher() 441 && mSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) { 442 final int diff = x - keyState.getKeyX(); 443 if (keyboard.shouldTriggerSpacebarSlidingLanguageSwitch(diff)) { 444 // Detect start sliding language switch. 445 mIsInSlidingLanguageSwitch = true; 446 mSpaceKeyIndex = keyIndex; 447 keyboard.updateSpacebarPreviewIcon(diff); 448 // Display spacebar slide language switcher. 449 showKeyPreview(keyIndex); 450 if (queue != null) 451 queue.releaseAllPointersExcept(this, eventTime, true); 452 } 453 } 454 } 455 } else { 456 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) { 457 // The pointer has been slid out from the previous key, we must call onRelease() to 458 // notify that the previous key has been released. 459 setReleasedKeyGraphics(oldKeyIndex); 460 callListenerOnRelease(oldKey, oldKey.mCode, true); 461 startSlidingKeyInput(oldKey); 462 mHandler.cancelLongPressTimers(); 463 if (mIsAllowedSlidingKeyInput) { 464 keyState.onMoveToNewKey(keyIndex, x, y); 465 } else { 466 mKeyAlreadyProcessed = true; 467 dismissKeyPreview(); 468 } 469 } 470 } 471 } 472 473 public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { 474 if (ENABLE_ASSERTION) checkAssertion(queue); 475 if (DEBUG_EVENT) 476 printTouchEvent("onUpEvent :", x, y, eventTime); 477 478 if (queue != null) { 479 if (isModifier()) { 480 // Before processing an up event of modifier key, all pointers already being 481 // tracked should be released. 482 queue.releaseAllPointersExcept(this, eventTime, true); 483 } else { 484 queue.releaseAllPointersOlderThan(this, eventTime); 485 } 486 queue.remove(this); 487 } 488 onUpEventInternal(x, y, eventTime, true); 489 } 490 491 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 492 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 493 // "virtual" up event. 494 public void onPhantomUpEvent(int x, int y, long eventTime, boolean updateReleasedKeyGraphics) { 495 if (DEBUG_EVENT) 496 printTouchEvent("onPhntEvent:", x, y, eventTime); 497 onUpEventInternal(x, y, eventTime, updateReleasedKeyGraphics); 498 mKeyAlreadyProcessed = true; 499 } 500 501 private void onUpEventInternal(int x, int y, long eventTime, 502 boolean updateReleasedKeyGraphics) { 503 mHandler.cancelKeyTimers(); 504 mHandler.cancelShowKeyPreview(this); 505 mIsInSlidingKeyInput = false; 506 final PointerTrackerKeyState keyState = mKeyState; 507 final int keyX, keyY; 508 if (isMajorEnoughMoveToBeOnNewKey(x, y, keyState.onMoveKey(x, y))) { 509 keyX = x; 510 keyY = y; 511 } else { 512 // Use previous fixed key coordinates. 513 keyX = keyState.getKeyX(); 514 keyY = keyState.getKeyY(); 515 } 516 final int keyIndex = keyState.onUpKey(keyX, keyY, eventTime); 517 dismissKeyPreview(); 518 if (updateReleasedKeyGraphics) 519 setReleasedKeyGraphics(keyIndex); 520 if (mKeyAlreadyProcessed) 521 return; 522 // TODO: Remove this hacking code 523 if (mIsInSlidingLanguageSwitch) { 524 setReleasedKeyGraphics(mSpaceKeyIndex); 525 final int languageDir = ((LatinKeyboard)mKeyboard).getLanguageChangeDirection(); 526 if (languageDir != 0) { 527 final int code = (languageDir == 1) 528 ? LatinKeyboard.CODE_NEXT_LANGUAGE : LatinKeyboard.CODE_PREV_LANGUAGE; 529 // This will change keyboard layout. 530 mListener.onCodeInput(code, new int[] {code}, keyX, keyY); 531 } 532 mIsInSlidingLanguageSwitch = false; 533 ((LatinKeyboard)mKeyboard).setSpacebarSlidingLanguageSwitchDiff(0); 534 return; 535 } 536 if (!mIsRepeatableKey) { 537 detectAndSendKey(keyIndex, keyX, keyY); 538 } 539 } 540 541 public void onLongPressed(PointerTrackerQueue queue) { 542 mKeyAlreadyProcessed = true; 543 if (queue != null) 544 queue.remove(this); 545 } 546 547 public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) { 548 if (ENABLE_ASSERTION) checkAssertion(queue); 549 if (DEBUG_EVENT) 550 printTouchEvent("onCancelEvt:", x, y, eventTime); 551 552 if (queue != null) { 553 queue.releaseAllPointersExcept(this, eventTime, true); 554 queue.remove(this); 555 } 556 onCancelEventInternal(); 557 } 558 559 private void onCancelEventInternal() { 560 mHandler.cancelKeyTimers(); 561 mHandler.cancelShowKeyPreview(this); 562 dismissKeyPreview(); 563 setReleasedKeyGraphics(mKeyState.getKeyIndex()); 564 mIsInSlidingKeyInput = false; 565 } 566 567 private void startRepeatKey(int keyIndex) { 568 final Key key = getKey(keyIndex); 569 if (key != null && key.mRepeatable) { 570 dismissKeyPreview(); 571 onRepeatKey(keyIndex); 572 mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this); 573 mIsRepeatableKey = true; 574 } else { 575 mIsRepeatableKey = false; 576 } 577 } 578 579 public void onRepeatKey(int keyIndex) { 580 Key key = getKey(keyIndex); 581 if (key != null) { 582 detectAndSendKey(keyIndex, key.mX, key.mY); 583 } 584 } 585 586 public int getLastX() { 587 return mKeyState.getLastX(); 588 } 589 590 public int getLastY() { 591 return mKeyState.getLastY(); 592 } 593 594 public long getDownTime() { 595 return mKeyState.getDownTime(); 596 } 597 598 private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, int newKey) { 599 if (mKeys == null || mKeyHysteresisDistanceSquared < 0) 600 throw new IllegalStateException("keyboard and/or hysteresis not set"); 601 int curKey = mKeyState.getKeyIndex(); 602 if (newKey == curKey) { 603 return false; 604 } else if (isValidKeyIndex(curKey)) { 605 return mKeys.get(curKey).squaredDistanceToEdge(x, y) >= mKeyHysteresisDistanceSquared; 606 } else { 607 return true; 608 } 609 } 610 611 // The modifier key, such as shift key, should not show its key preview. 612 private boolean isKeyPreviewNotRequired(int keyIndex) { 613 final Key key = getKey(keyIndex); 614 if (!key.mEnabled) 615 return true; 616 // Such as spacebar sliding language switch. 617 if (mKeyboard.needSpacebarPreview(keyIndex)) 618 return false; 619 final int code = key.mCode; 620 return isModifierCode(code) || code == Keyboard.CODE_DELETE 621 || code == Keyboard.CODE_ENTER || code == Keyboard.CODE_SPACE; 622 } 623 624 private void showKeyPreview(int keyIndex) { 625 if (isKeyPreviewNotRequired(keyIndex)) 626 return; 627 mProxy.showKeyPreview(keyIndex, this); 628 } 629 630 private void dismissKeyPreview() { 631 mProxy.dismissKeyPreview(this); 632 } 633 634 private void startLongPressTimer(int keyIndex) { 635 Key key = getKey(keyIndex); 636 if (!key.mEnabled) 637 return; 638 if (key.mCode == Keyboard.CODE_SHIFT) { 639 mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this); 640 } else if (key.mManualTemporaryUpperCaseCode != Keyboard.CODE_DUMMY 641 && mKeyboard.isManualTemporaryUpperCase()) { 642 // We need not start long press timer on the key which has manual temporary upper case 643 // code defined and the keyboard is in manual temporary upper case mode. 644 return; 645 } else if (mKeyboardSwitcher.isInMomentarySwitchState()) { 646 // We use longer timeout for sliding finger input started from the symbols mode key. 647 mHandler.startLongPressTimer(mLongPressKeyTimeout * 3, keyIndex, this); 648 } else { 649 mHandler.startLongPressTimer(mLongPressKeyTimeout, keyIndex, this); 650 } 651 } 652 653 private void detectAndSendKey(int index, int x, int y) { 654 final Key key = getKey(index); 655 if (key == null) { 656 callListenerOnCancelInput(); 657 return; 658 } 659 if (key.mOutputText != null) { 660 callListenerOnTextInput(key); 661 callListenerOnRelease(key, key.mCode, false); 662 } else { 663 int code = key.mCode; 664 final int[] codes = mKeyDetector.newCodeArray(); 665 mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes); 666 667 // If keyboard is in manual temporary upper case state and key has manual temporary 668 // shift code, alternate character code should be sent. 669 if (mKeyboard.isManualTemporaryUpperCase() 670 && key.mManualTemporaryUpperCaseCode != Keyboard.CODE_DUMMY) { 671 code = key.mManualTemporaryUpperCaseCode; 672 codes[0] = code; 673 } 674 675 // Swap the first and second values in the codes array if the primary code is not the 676 // first value but the second value in the array. This happens when key debouncing is 677 // in effect. 678 if (codes.length >= 2 && codes[0] != code && codes[1] == code) { 679 codes[1] = codes[0]; 680 codes[0] = code; 681 } 682 callListenerOnCodeInput(key, code, codes, x, y); 683 callListenerOnRelease(key, code, false); 684 } 685 } 686 687 public CharSequence getPreviewText(Key key) { 688 return key.mLabel; 689 } 690 691 private long mPreviousEventTime; 692 693 private void printTouchEvent(String title, int x, int y, long eventTime) { 694 final int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null); 695 final Key key = getKey(keyIndex); 696 final String code = (key == null) ? "----" : keyCodePrintable(key.mCode); 697 final long delta = eventTime - mPreviousEventTime; 698 Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %3d(%s)", title, 699 (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, delta, keyIndex, code)); 700 mPreviousEventTime = eventTime; 701 } 702 703 private static String keyCodePrintable(int primaryCode) { 704 final String modifier = isModifierCode(primaryCode) ? " modifier" : ""; 705 return String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode) + modifier; 706 } 707} 708