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