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