PointerTracker.java revision 8335c59ea7715f3dbc6625f128a7a038f314a89f
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.graphics.Canvas; 20import android.graphics.Paint; 21import android.os.SystemClock; 22import android.util.Log; 23import android.view.MotionEvent; 24import android.view.View; 25import android.widget.TextView; 26 27import com.android.inputmethod.accessibility.AccessibilityUtils; 28import com.android.inputmethod.keyboard.internal.GestureStroke; 29import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; 30import com.android.inputmethod.latin.InputPointers; 31import com.android.inputmethod.latin.LatinImeLogger; 32import com.android.inputmethod.latin.Utils; 33import com.android.inputmethod.latin.define.ProductionFlag; 34import com.android.inputmethod.research.ResearchLogger; 35 36import java.util.ArrayList; 37 38public class PointerTracker implements PointerTrackerQueue.Element { 39 private static final String TAG = PointerTracker.class.getSimpleName(); 40 private static final boolean DEBUG_EVENT = false; 41 private static final boolean DEBUG_MOVE_EVENT = false; 42 private static final boolean DEBUG_LISTENER = false; 43 private static boolean DEBUG_MODE = LatinImeLogger.sDBG; 44 45 /** True if {@link PointerTracker}s should handle gesture events. */ 46 private static boolean sShouldHandleGesture = false; 47 private static boolean sMainDictionaryAvailable = false; 48 private static boolean sGestureHandlingEnabledByInputField = false; 49 private static boolean sGestureHandlingEnabledByUser = false; 50 51 private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec 52 53 public interface KeyEventHandler { 54 /** 55 * Get KeyDetector object that is used for this PointerTracker. 56 * @return the KeyDetector object that is used for this PointerTracker 57 */ 58 public KeyDetector getKeyDetector(); 59 60 /** 61 * Get KeyboardActionListener object that is used to register key code and so on. 62 * @return the KeyboardActionListner for this PointerTracker 63 */ 64 public KeyboardActionListener getKeyboardActionListener(); 65 66 /** 67 * Get DrawingProxy object that is used for this PointerTracker. 68 * @return the DrawingProxy object that is used for this PointerTracker 69 */ 70 public DrawingProxy getDrawingProxy(); 71 72 /** 73 * Get TimerProxy object that handles key repeat and long press timer event for this 74 * PointerTracker. 75 * @return the TimerProxy object that handles key repeat and long press timer event. 76 */ 77 public TimerProxy getTimerProxy(); 78 } 79 80 public interface DrawingProxy extends MoreKeysPanel.Controller { 81 public void invalidateKey(Key key); 82 public TextView inflateKeyPreviewText(); 83 public void showKeyPreview(PointerTracker tracker); 84 public void dismissKeyPreview(PointerTracker tracker); 85 public void showGestureTrail(PointerTracker tracker); 86 } 87 88 public interface TimerProxy { 89 public void startTypingStateTimer(Key typedKey); 90 public boolean isTypingState(); 91 public void startKeyRepeatTimer(PointerTracker tracker); 92 public void startLongPressTimer(PointerTracker tracker); 93 public void startLongPressTimer(int code); 94 public void cancelLongPressTimer(); 95 public void startDoubleTapTimer(); 96 public void cancelDoubleTapTimer(); 97 public boolean isInDoubleTapTimeout(); 98 public void cancelKeyTimers(); 99 100 public static class Adapter implements TimerProxy { 101 @Override 102 public void startTypingStateTimer(Key typedKey) {} 103 @Override 104 public boolean isTypingState() { return false; } 105 @Override 106 public void startKeyRepeatTimer(PointerTracker tracker) {} 107 @Override 108 public void startLongPressTimer(PointerTracker tracker) {} 109 @Override 110 public void startLongPressTimer(int code) {} 111 @Override 112 public void cancelLongPressTimer() {} 113 @Override 114 public void startDoubleTapTimer() {} 115 @Override 116 public void cancelDoubleTapTimer() {} 117 @Override 118 public boolean isInDoubleTapTimeout() { return false; } 119 @Override 120 public void cancelKeyTimers() {} 121 } 122 } 123 124 // Parameters for pointer handling. 125 private static MainKeyboardView.PointerTrackerParams sParams; 126 private static int sTouchNoiseThresholdDistanceSquared; 127 private static boolean sNeedsPhantomSuddenMoveEventHack; 128 129 private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); 130 private static final InputPointers sAggregratedPointers = new InputPointers( 131 GestureStroke.DEFAULT_CAPACITY); 132 private static PointerTrackerQueue sPointerTrackerQueue; 133 // HACK: Change gesture detection criteria depending on this variable. 134 // TODO: Find more comprehensive ways to detect a gesture start. 135 // True when the previous user input was a gesture input, not a typing input. 136 private static boolean sWasInGesture; 137 138 public final int mPointerId; 139 140 private DrawingProxy mDrawingProxy; 141 private TimerProxy mTimerProxy; 142 private KeyDetector mKeyDetector; 143 private KeyboardActionListener mListener = EMPTY_LISTENER; 144 145 private Keyboard mKeyboard; 146 private int mKeyQuarterWidthSquared; 147 private final TextView mKeyPreviewText; 148 149 private boolean mIsAlphabetKeyboard; 150 private boolean mIsPossibleGesture = false; 151 private boolean mInGesture = false; 152 153 // TODO: Remove these variables 154 private int mLastRecognitionPointSize = 0; 155 private long mLastRecognitionTime = 0; 156 157 // The position and time at which first down event occurred. 158 private long mDownTime; 159 private long mUpTime; 160 161 // The current key where this pointer is. 162 private Key mCurrentKey = null; 163 // The position where the current key was recognized for the first time. 164 private int mKeyX; 165 private int mKeyY; 166 167 // Last pointer position. 168 private int mLastX; 169 private int mLastY; 170 171 // true if keyboard layout has been changed. 172 private boolean mKeyboardLayoutHasBeenChanged; 173 174 // true if event is already translated to a key action. 175 private boolean mKeyAlreadyProcessed; 176 177 // true if this pointer has been long-pressed and is showing a more keys panel. 178 private boolean mIsShowingMoreKeysPanel; 179 180 // true if this pointer is in sliding key input 181 boolean mIsInSlidingKeyInput; 182 183 // true if sliding key is allowed. 184 private boolean mIsAllowedSlidingKeyInput; 185 186 // ignore modifier key if true 187 private boolean mIgnoreModifierKey; 188 189 // Empty {@link KeyboardActionListener} 190 private static final KeyboardActionListener EMPTY_LISTENER = 191 new KeyboardActionListener.Adapter(); 192 193 private final GestureStroke mGestureStroke; 194 195 public static void init(boolean hasDistinctMultitouch, 196 boolean needsPhantomSuddenMoveEventHack) { 197 if (hasDistinctMultitouch) { 198 sPointerTrackerQueue = new PointerTrackerQueue(); 199 } else { 200 sPointerTrackerQueue = null; 201 } 202 sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; 203 204 setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT); 205 } 206 207 public static void setParameters(MainKeyboardView.PointerTrackerParams params) { 208 sParams = params; 209 sTouchNoiseThresholdDistanceSquared = (int)( 210 params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance); 211 } 212 213 private static void updateGestureHandlingMode() { 214 sShouldHandleGesture = sMainDictionaryAvailable 215 && sGestureHandlingEnabledByInputField 216 && sGestureHandlingEnabledByUser 217 && !AccessibilityUtils.getInstance().isTouchExplorationEnabled(); 218 } 219 220 // Note that this method is called from a non-UI thread. 221 public static void setMainDictionaryAvailability(boolean mainDictionaryAvailable) { 222 sMainDictionaryAvailable = mainDictionaryAvailable; 223 updateGestureHandlingMode(); 224 } 225 226 public static void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) { 227 sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser; 228 updateGestureHandlingMode(); 229 } 230 231 public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) { 232 final ArrayList<PointerTracker> trackers = sTrackers; 233 234 // Create pointer trackers until we can get 'id+1'-th tracker, if needed. 235 for (int i = trackers.size(); i <= id; i++) { 236 final PointerTracker tracker = new PointerTracker(i, handler); 237 trackers.add(tracker); 238 } 239 240 return trackers.get(id); 241 } 242 243 public static boolean isAnyInSlidingKeyInput() { 244 return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; 245 } 246 247 public static void setKeyboardActionListener(KeyboardActionListener listener) { 248 final int trackersSize = sTrackers.size(); 249 for (int i = 0; i < trackersSize; ++i) { 250 final PointerTracker tracker = sTrackers.get(i); 251 tracker.mListener = listener; 252 } 253 } 254 255 public static void setKeyDetector(KeyDetector keyDetector) { 256 final int trackersSize = sTrackers.size(); 257 for (int i = 0; i < trackersSize; ++i) { 258 final PointerTracker tracker = sTrackers.get(i); 259 tracker.setKeyDetectorInner(keyDetector); 260 // Mark that keyboard layout has been changed. 261 tracker.mKeyboardLayoutHasBeenChanged = true; 262 } 263 final Keyboard keyboard = keyDetector.getKeyboard(); 264 sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput(); 265 updateGestureHandlingMode(); 266 } 267 268 public static void dismissAllKeyPreviews() { 269 final int trackersSize = sTrackers.size(); 270 for (int i = 0; i < trackersSize; ++i) { 271 final PointerTracker tracker = sTrackers.get(i); 272 tracker.getKeyPreviewText().setVisibility(View.INVISIBLE); 273 tracker.setReleasedKeyGraphics(tracker.mCurrentKey); 274 } 275 } 276 277 // TODO: To handle multi-touch gestures we may want to move this method to 278 // {@link PointerTrackerQueue}. 279 private static InputPointers getIncrementalBatchPoints() { 280 final int trackersSize = sTrackers.size(); 281 for (int i = 0; i < trackersSize; ++i) { 282 final PointerTracker tracker = sTrackers.get(i); 283 tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers); 284 } 285 return sAggregratedPointers; 286 } 287 288 // TODO: To handle multi-touch gestures we may want to move this method to 289 // {@link PointerTrackerQueue}. 290 private static InputPointers getAllBatchPoints() { 291 final int trackersSize = sTrackers.size(); 292 for (int i = 0; i < trackersSize; ++i) { 293 final PointerTracker tracker = sTrackers.get(i); 294 tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers); 295 } 296 return sAggregratedPointers; 297 } 298 299 // TODO: To handle multi-touch gestures we may want to move this method to 300 // {@link PointerTrackerQueue}. 301 public static void clearBatchInputPointsOfAllPointerTrackers() { 302 final int trackersSize = sTrackers.size(); 303 for (int i = 0; i < trackersSize; ++i) { 304 final PointerTracker tracker = sTrackers.get(i); 305 tracker.mGestureStroke.reset(); 306 } 307 sAggregratedPointers.reset(); 308 } 309 310 private PointerTracker(int id, KeyEventHandler handler) { 311 if (handler == null) 312 throw new NullPointerException(); 313 mPointerId = id; 314 mGestureStroke = new GestureStroke(id); 315 setKeyDetectorInner(handler.getKeyDetector()); 316 mListener = handler.getKeyboardActionListener(); 317 mDrawingProxy = handler.getDrawingProxy(); 318 mTimerProxy = handler.getTimerProxy(); 319 mKeyPreviewText = mDrawingProxy.inflateKeyPreviewText(); 320 } 321 322 public TextView getKeyPreviewText() { 323 return mKeyPreviewText; 324 } 325 326 // Returns true if keyboard has been changed by this callback. 327 private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { 328 if (mInGesture) { 329 return false; 330 } 331 final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); 332 if (DEBUG_LISTENER) { 333 Log.d(TAG, "onPress : " + KeyDetector.printableCode(key) 334 + " ignoreModifier=" + ignoreModifierKey 335 + " enabled=" + key.isEnabled()); 336 } 337 if (ignoreModifierKey) { 338 return false; 339 } 340 if (key.isEnabled()) { 341 mListener.onPressKey(key.mCode); 342 final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; 343 mKeyboardLayoutHasBeenChanged = false; 344 mTimerProxy.startTypingStateTimer(key); 345 return keyboardLayoutHasBeenChanged; 346 } 347 return false; 348 } 349 350 // Note that we need primaryCode argument because the keyboard may in shifted state and the 351 // primaryCode is different from {@link Key#mCode}. 352 private void callListenerOnCodeInput(Key key, int primaryCode, int x, int y) { 353 final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); 354 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 355 final int code = altersCode ? key.mAltCode : primaryCode; 356 if (DEBUG_LISTENER) { 357 Log.d(TAG, "onCodeInput: " + Keyboard.printableCode(code) + " text=" + key.mOutputText 358 + " x=" + x + " y=" + y 359 + " ignoreModifier=" + ignoreModifierKey + " altersCode=" + altersCode 360 + " enabled=" + key.isEnabled()); 361 } 362 if (ProductionFlag.IS_EXPERIMENTAL) { 363 ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, 364 altersCode, code); 365 } 366 if (ignoreModifierKey) { 367 return; 368 } 369 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 370 if (key.isEnabled() || altersCode) { 371 if (code == Keyboard.CODE_OUTPUT_TEXT) { 372 mListener.onTextInput(key.mOutputText); 373 } else if (code != Keyboard.CODE_UNSPECIFIED) { 374 mListener.onCodeInput(code, x, y); 375 } 376 } 377 } 378 379 // Note that we need primaryCode argument because the keyboard may in shifted state and the 380 // primaryCode is different from {@link Key#mCode}. 381 private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) { 382 if (mInGesture) { 383 return; 384 } 385 final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); 386 if (DEBUG_LISTENER) { 387 Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode) 388 + " sliding=" + withSliding + " ignoreModifier=" + ignoreModifierKey 389 + " enabled="+ key.isEnabled()); 390 } 391 if (ProductionFlag.IS_EXPERIMENTAL) { 392 ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, 393 ignoreModifierKey); 394 } 395 if (ignoreModifierKey) { 396 return; 397 } 398 if (key.isEnabled()) { 399 mListener.onReleaseKey(primaryCode, withSliding); 400 } 401 } 402 403 private void callListenerOnCancelInput() { 404 if (DEBUG_LISTENER) 405 Log.d(TAG, "onCancelInput"); 406 if (ProductionFlag.IS_EXPERIMENTAL) { 407 ResearchLogger.pointerTracker_callListenerOnCancelInput(); 408 } 409 mListener.onCancelInput(); 410 } 411 412 private void setKeyDetectorInner(KeyDetector keyDetector) { 413 mKeyDetector = keyDetector; 414 mKeyboard = keyDetector.getKeyboard(); 415 mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard(); 416 mGestureStroke.setGestureSampleLength( 417 mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); 418 final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); 419 if (newKey != mCurrentKey) { 420 if (mDrawingProxy != null) { 421 setReleasedKeyGraphics(mCurrentKey); 422 } 423 // Keep {@link #mCurrentKey} that comes from previous keyboard. 424 } 425 final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; 426 mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; 427 } 428 429 @Override 430 public boolean isInSlidingKeyInput() { 431 return mIsInSlidingKeyInput; 432 } 433 434 public Key getKey() { 435 return mCurrentKey; 436 } 437 438 @Override 439 public boolean isModifier() { 440 return mCurrentKey != null && mCurrentKey.isModifier(); 441 } 442 443 public Key getKeyOn(int x, int y) { 444 return mKeyDetector.detectHitKey(x, y); 445 } 446 447 private void setReleasedKeyGraphics(Key key) { 448 mDrawingProxy.dismissKeyPreview(this); 449 if (key == null) { 450 return; 451 } 452 453 // Even if the key is disabled, update the key release graphics just in case. 454 updateReleaseKeyGraphics(key); 455 456 if (key.isShift()) { 457 for (final Key shiftKey : mKeyboard.mShiftKeys) { 458 if (shiftKey != key) { 459 updateReleaseKeyGraphics(shiftKey); 460 } 461 } 462 } 463 464 if (key.altCodeWhileTyping()) { 465 final int altCode = key.mAltCode; 466 final Key altKey = mKeyboard.getKey(altCode); 467 if (altKey != null) { 468 updateReleaseKeyGraphics(altKey); 469 } 470 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 471 if (k != key && k.mAltCode == altCode) { 472 updateReleaseKeyGraphics(k); 473 } 474 } 475 } 476 } 477 478 private void setPressedKeyGraphics(Key key) { 479 if (key == null) { 480 return; 481 } 482 483 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 484 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 485 final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; 486 if (!needsToUpdateGraphics) { 487 return; 488 } 489 490 if (!key.noKeyPreview() && !mInGesture) { 491 mDrawingProxy.showKeyPreview(this); 492 } 493 updatePressKeyGraphics(key); 494 495 if (key.isShift()) { 496 for (final Key shiftKey : mKeyboard.mShiftKeys) { 497 if (shiftKey != key) { 498 updatePressKeyGraphics(shiftKey); 499 } 500 } 501 } 502 503 if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { 504 final int altCode = key.mAltCode; 505 final Key altKey = mKeyboard.getKey(altCode); 506 if (altKey != null) { 507 updatePressKeyGraphics(altKey); 508 } 509 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 510 if (k != key && k.mAltCode == altCode) { 511 updatePressKeyGraphics(k); 512 } 513 } 514 } 515 } 516 517 private void updateReleaseKeyGraphics(Key key) { 518 key.onReleased(); 519 mDrawingProxy.invalidateKey(key); 520 } 521 522 private void updatePressKeyGraphics(Key key) { 523 key.onPressed(); 524 mDrawingProxy.invalidateKey(key); 525 } 526 527 public void drawGestureTrail(Canvas canvas, Paint paint) { 528 if (mInGesture) { 529 mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY); 530 } 531 } 532 533 public int getLastX() { 534 return mLastX; 535 } 536 537 public int getLastY() { 538 return mLastY; 539 } 540 541 public long getDownTime() { 542 return mDownTime; 543 } 544 545 private Key onDownKey(int x, int y, long eventTime) { 546 mDownTime = eventTime; 547 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 548 } 549 550 private Key onMoveKeyInternal(int x, int y) { 551 mLastX = x; 552 mLastY = y; 553 return mKeyDetector.detectHitKey(x, y); 554 } 555 556 private Key onMoveKey(int x, int y) { 557 return onMoveKeyInternal(x, y); 558 } 559 560 private Key onMoveToNewKey(Key newKey, int x, int y) { 561 mCurrentKey = newKey; 562 mKeyX = x; 563 mKeyY = y; 564 return newKey; 565 } 566 567 private void startBatchInput() { 568 if (DEBUG_LISTENER) { 569 Log.d(TAG, "onStartBatchInput"); 570 } 571 mInGesture = true; 572 mListener.onStartBatchInput(); 573 } 574 575 private void updateBatchInput(InputPointers batchPoints) { 576 if (DEBUG_LISTENER) { 577 Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); 578 } 579 mListener.onUpdateBatchInput(batchPoints); 580 } 581 582 private void endBatchInput(InputPointers batchPoints) { 583 if (DEBUG_LISTENER) { 584 Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize()); 585 } 586 mListener.onEndBatchInput(batchPoints); 587 clearBatchInputRecognitionStateOfThisPointerTracker(); 588 clearBatchInputPointsOfAllPointerTrackers(); 589 sWasInGesture = true; 590 } 591 592 private void abortBatchInput() { 593 clearBatchInputRecognitionStateOfThisPointerTracker(); 594 clearBatchInputPointsOfAllPointerTrackers(); 595 } 596 597 private void clearBatchInputRecognitionStateOfThisPointerTracker() { 598 mIsPossibleGesture = false; 599 mInGesture = false; 600 mLastRecognitionPointSize = 0; 601 mLastRecognitionTime = 0; 602 } 603 604 private boolean updateBatchInputRecognitionState(long eventTime, int size) { 605 if (size > mLastRecognitionPointSize 606 && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) { 607 mLastRecognitionPointSize = size; 608 mLastRecognitionTime = eventTime; 609 return true; 610 } 611 return false; 612 } 613 614 public void processMotionEvent(int action, int x, int y, long eventTime, 615 KeyEventHandler handler) { 616 switch (action) { 617 case MotionEvent.ACTION_DOWN: 618 case MotionEvent.ACTION_POINTER_DOWN: 619 onDownEvent(x, y, eventTime, handler); 620 break; 621 case MotionEvent.ACTION_UP: 622 case MotionEvent.ACTION_POINTER_UP: 623 onUpEvent(x, y, eventTime); 624 break; 625 case MotionEvent.ACTION_MOVE: 626 onMoveEvent(x, y, eventTime, null); 627 break; 628 case MotionEvent.ACTION_CANCEL: 629 onCancelEvent(x, y, eventTime); 630 break; 631 } 632 } 633 634 public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) { 635 if (DEBUG_EVENT) 636 printTouchEvent("onDownEvent:", x, y, eventTime); 637 638 mDrawingProxy = handler.getDrawingProxy(); 639 mTimerProxy = handler.getTimerProxy(); 640 setKeyboardActionListener(handler.getKeyboardActionListener()); 641 setKeyDetectorInner(handler.getKeyDetector()); 642 // Naive up-to-down noise filter. 643 final long deltaT = eventTime - mUpTime; 644 if (deltaT < sParams.mTouchNoiseThresholdTime) { 645 final int dx = x - mLastX; 646 final int dy = y - mLastY; 647 final int distanceSquared = (dx * dx + dy * dy); 648 if (distanceSquared < sTouchNoiseThresholdDistanceSquared) { 649 if (DEBUG_MODE) 650 Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT 651 + " distance=" + distanceSquared); 652 if (ProductionFlag.IS_EXPERIMENTAL) { 653 ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared); 654 } 655 mKeyAlreadyProcessed = true; 656 return; 657 } 658 } 659 660 final PointerTrackerQueue queue = sPointerTrackerQueue; 661 final Key key = getKeyOn(x, y); 662 if (queue != null) { 663 if (key != null && key.isModifier()) { 664 // Before processing a down event of modifier key, all pointers already being 665 // tracked should be released. 666 queue.releaseAllPointers(eventTime); 667 } 668 queue.add(this); 669 } 670 onDownEventInternal(x, y, eventTime); 671 if (queue != null && queue.size() == 1) { 672 mIsPossibleGesture = false; 673 // A gesture should start only from the letter key. 674 if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel 675 && key != null && Keyboard.isLetterCode(key.mCode)) { 676 mIsPossibleGesture = true; 677 // TODO: pointer times should be relative to first down even in entire batch input 678 // instead of resetting to 0 for each new down event. 679 mGestureStroke.addPoint(x, y, 0, false); 680 } 681 } 682 } 683 684 private void onDownEventInternal(int x, int y, long eventTime) { 685 Key key = onDownKey(x, y, eventTime); 686 // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding 687 // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. 688 mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled 689 || (key != null && key.isModifier()) 690 || mKeyDetector.alwaysAllowsSlidingInput(); 691 mKeyboardLayoutHasBeenChanged = false; 692 mKeyAlreadyProcessed = false; 693 mIsInSlidingKeyInput = false; 694 mIgnoreModifierKey = false; 695 if (key != null) { 696 // This onPress call may have changed keyboard layout. Those cases are detected at 697 // {@link #setKeyboard}. In those cases, we should update key according to the new 698 // keyboard layout. 699 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 700 key = onDownKey(x, y, eventTime); 701 } 702 703 startRepeatKey(key); 704 startLongPressTimer(key); 705 setPressedKeyGraphics(key); 706 } 707 } 708 709 private void startSlidingKeyInput(Key key) { 710 if (!mIsInSlidingKeyInput) { 711 mIgnoreModifierKey = key.isModifier(); 712 } 713 mIsInSlidingKeyInput = true; 714 } 715 716 private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime, 717 boolean isHistorical, Key key) { 718 final int gestureTime = (int)(eventTime - tracker.getDownTime()); 719 if (sShouldHandleGesture && mIsPossibleGesture) { 720 final GestureStroke stroke = mGestureStroke; 721 stroke.addPoint(x, y, gestureTime, isHistorical); 722 if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) { 723 startBatchInput(); 724 } 725 } 726 727 if (key != null && mInGesture) { 728 final InputPointers batchPoints = getIncrementalBatchPoints(); 729 mDrawingProxy.showGestureTrail(this); 730 if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { 731 updateBatchInput(batchPoints); 732 } 733 } 734 } 735 736 public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) { 737 if (DEBUG_MOVE_EVENT) 738 printTouchEvent("onMoveEvent:", x, y, eventTime); 739 if (mKeyAlreadyProcessed) 740 return; 741 742 if (me != null) { 743 // Add historical points to gesture path. 744 final int pointerIndex = me.findPointerIndex(mPointerId); 745 final int historicalSize = me.getHistorySize(); 746 for (int h = 0; h < historicalSize; h++) { 747 final int historicalX = (int)me.getHistoricalX(pointerIndex, h); 748 final int historicalY = (int)me.getHistoricalY(pointerIndex, h); 749 final long historicalTime = me.getHistoricalEventTime(h); 750 onGestureMoveEvent(this, historicalX, historicalY, historicalTime, 751 true /* isHistorical */, null); 752 } 753 } 754 755 final int lastX = mLastX; 756 final int lastY = mLastY; 757 final Key oldKey = mCurrentKey; 758 Key key = onMoveKey(x, y); 759 760 // Register move event on gesture tracker. 761 onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key); 762 if (mInGesture) { 763 mIgnoreModifierKey = true; 764 mTimerProxy.cancelLongPressTimer(); 765 mIsInSlidingKeyInput = true; 766 mCurrentKey = null; 767 setReleasedKeyGraphics(oldKey); 768 } 769 770 if (key != null) { 771 if (oldKey == null) { 772 // The pointer has been slid in to the new key, but the finger was not on any keys. 773 // In this case, we must call onPress() to notify that the new key is being pressed. 774 // This onPress call may have changed keyboard layout. Those cases are detected at 775 // {@link #setKeyboard}. In those cases, we should update key according to the 776 // new keyboard layout. 777 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 778 key = onMoveKey(x, y); 779 } 780 onMoveToNewKey(key, x, y); 781 startLongPressTimer(key); 782 setPressedKeyGraphics(key); 783 } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) { 784 // The pointer has been slid in to the new key from the previous key, we must call 785 // onRelease() first to notify that the previous key has been released, then call 786 // onPress() to notify that the new key is being pressed. 787 setReleasedKeyGraphics(oldKey); 788 callListenerOnRelease(oldKey, oldKey.mCode, true); 789 startSlidingKeyInput(oldKey); 790 mTimerProxy.cancelKeyTimers(); 791 startRepeatKey(key); 792 if (mIsAllowedSlidingKeyInput) { 793 // This onPress call may have changed keyboard layout. Those cases are detected 794 // at {@link #setKeyboard}. In those cases, we should update key according 795 // to the new keyboard layout. 796 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 797 key = onMoveKey(x, y); 798 } 799 onMoveToNewKey(key, x, y); 800 startLongPressTimer(key); 801 setPressedKeyGraphics(key); 802 } else { 803 // HACK: On some devices, quick successive touches may be translated to sudden 804 // move by touch panel firmware. This hack detects the case and translates the 805 // move event to successive up and down events. 806 final int dx = x - lastX; 807 final int dy = y - lastY; 808 final int lastMoveSquared = dx * dx + dy * dy; 809 // TODO: Should find a way to balance gesture detection and this hack. 810 if (sNeedsPhantomSuddenMoveEventHack 811 && lastMoveSquared >= mKeyQuarterWidthSquared 812 && !mIsPossibleGesture) { 813 if (DEBUG_MODE) { 814 Log.w(TAG, String.format("onMoveEvent:" 815 + " phantom sudden move event is translated to " 816 + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y)); 817 } 818 // TODO: This should be moved to outside of this nested if-clause? 819 if (ProductionFlag.IS_EXPERIMENTAL) { 820 ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); 821 } 822 onUpEventInternal(); 823 onDownEventInternal(x, y, eventTime); 824 } else { 825 // HACK: If there are currently multiple touches, register the key even if 826 // the finger slides off the key. This defends against noise from some 827 // touch panels when there are close multiple touches. 828 // Caveat: When in chording input mode with a modifier key, we don't use 829 // this hack. 830 if (me != null && me.getPointerCount() > 1 831 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { 832 onUpEventInternal(); 833 } 834 if (!mIsPossibleGesture) { 835 mKeyAlreadyProcessed = true; 836 } 837 setReleasedKeyGraphics(oldKey); 838 } 839 } 840 } 841 } else { 842 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) { 843 // The pointer has been slid out from the previous key, we must call onRelease() to 844 // notify that the previous key has been released. 845 setReleasedKeyGraphics(oldKey); 846 callListenerOnRelease(oldKey, oldKey.mCode, true); 847 startSlidingKeyInput(oldKey); 848 mTimerProxy.cancelLongPressTimer(); 849 if (mIsAllowedSlidingKeyInput) { 850 onMoveToNewKey(key, x, y); 851 } else { 852 if (!mIsPossibleGesture) { 853 mKeyAlreadyProcessed = true; 854 } 855 } 856 } 857 } 858 } 859 860 public void onUpEvent(int x, int y, long eventTime) { 861 if (DEBUG_EVENT) 862 printTouchEvent("onUpEvent :", x, y, eventTime); 863 864 final PointerTrackerQueue queue = sPointerTrackerQueue; 865 if (queue != null) { 866 if (!mInGesture) { 867 if (mCurrentKey != null && mCurrentKey.isModifier()) { 868 // Before processing an up event of modifier key, all pointers already being 869 // tracked should be released. 870 queue.releaseAllPointersExcept(this, eventTime); 871 } else { 872 queue.releaseAllPointersOlderThan(this, eventTime); 873 } 874 } 875 queue.remove(this); 876 } 877 onUpEventInternal(); 878 } 879 880 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 881 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 882 // "virtual" up event. 883 @Override 884 public void onPhantomUpEvent(long eventTime) { 885 if (DEBUG_EVENT) 886 printTouchEvent("onPhntEvent:", getLastX(), getLastY(), eventTime); 887 onUpEventInternal(); 888 mKeyAlreadyProcessed = true; 889 } 890 891 private void onUpEventInternal() { 892 mTimerProxy.cancelKeyTimers(); 893 mIsInSlidingKeyInput = false; 894 mIsPossibleGesture = false; 895 // Release the last pressed key. 896 setReleasedKeyGraphics(mCurrentKey); 897 if (mIsShowingMoreKeysPanel) { 898 mDrawingProxy.dismissMoreKeysPanel(); 899 mIsShowingMoreKeysPanel = false; 900 } 901 902 if (mInGesture) { 903 // Register up event on gesture tracker. 904 // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding, 905 // and/or tapping mode? 906 endBatchInput(getAllBatchPoints()); 907 if (mCurrentKey != null) { 908 callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true); 909 mCurrentKey = null; 910 } 911 mDrawingProxy.showGestureTrail(this); 912 return; 913 } 914 // This event will be recognized as a regular code input. Clear unused batch points so they 915 // are not mistakenly included in the next batch event. 916 clearBatchInputPointsOfAllPointerTrackers(); 917 if (mKeyAlreadyProcessed) 918 return; 919 if (mCurrentKey != null && !mCurrentKey.isRepeatable()) { 920 detectAndSendKey(mCurrentKey, mKeyX, mKeyY); 921 } 922 } 923 924 public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) { 925 abortBatchInput(); 926 onLongPressed(); 927 mIsShowingMoreKeysPanel = true; 928 onDownEvent(x, y, SystemClock.uptimeMillis(), handler); 929 } 930 931 public void onLongPressed() { 932 mKeyAlreadyProcessed = true; 933 setReleasedKeyGraphics(mCurrentKey); 934 final PointerTrackerQueue queue = sPointerTrackerQueue; 935 if (queue != null) { 936 queue.remove(this); 937 } 938 } 939 940 public void onCancelEvent(int x, int y, long eventTime) { 941 if (DEBUG_EVENT) 942 printTouchEvent("onCancelEvt:", x, y, eventTime); 943 944 final PointerTrackerQueue queue = sPointerTrackerQueue; 945 if (queue != null) { 946 queue.releaseAllPointersExcept(this, eventTime); 947 queue.remove(this); 948 } 949 onCancelEventInternal(); 950 } 951 952 private void onCancelEventInternal() { 953 mTimerProxy.cancelKeyTimers(); 954 setReleasedKeyGraphics(mCurrentKey); 955 mIsInSlidingKeyInput = false; 956 if (mIsShowingMoreKeysPanel) { 957 mDrawingProxy.dismissMoreKeysPanel(); 958 mIsShowingMoreKeysPanel = false; 959 } 960 } 961 962 private void startRepeatKey(Key key) { 963 if (key != null && key.isRepeatable() && !mInGesture) { 964 onRegisterKey(key); 965 mTimerProxy.startKeyRepeatTimer(this); 966 } 967 } 968 969 public void onRegisterKey(Key key) { 970 if (key != null) { 971 detectAndSendKey(key, key.mX, key.mY); 972 mTimerProxy.startTypingStateTimer(key); 973 } 974 } 975 976 private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) { 977 if (mKeyDetector == null) 978 throw new NullPointerException("keyboard and/or key detector not set"); 979 Key curKey = mCurrentKey; 980 if (newKey == curKey) { 981 return false; 982 } else if (curKey != null) { 983 return curKey.squaredDistanceToEdge(x, y) 984 >= mKeyDetector.getKeyHysteresisDistanceSquared(); 985 } else { 986 return true; 987 } 988 } 989 990 private void startLongPressTimer(Key key) { 991 if (key != null && key.isLongPressEnabled() && !mInGesture) { 992 mTimerProxy.startLongPressTimer(this); 993 } 994 } 995 996 private void detectAndSendKey(Key key, int x, int y) { 997 if (key == null) { 998 callListenerOnCancelInput(); 999 return; 1000 } 1001 1002 int code = key.mCode; 1003 callListenerOnCodeInput(key, code, x, y); 1004 callListenerOnRelease(key, code, false); 1005 sWasInGesture = false; 1006 } 1007 1008 private void printTouchEvent(String title, int x, int y, long eventTime) { 1009 final Key key = mKeyDetector.detectHitKey(x, y); 1010 final String code = KeyDetector.printableCode(key); 1011 Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title, 1012 (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code)); 1013 } 1014} 1015