PointerTracker.java revision d2173b5737bf791a65f6b1e2980f26ebd94369c5
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 { 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 mCurrentKey = newKey; 412 } 413 final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4; 414 mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth; 415 } 416 417 public boolean isInSlidingKeyInput() { 418 return mIsInSlidingKeyInput; 419 } 420 421 public Key getKey() { 422 return mCurrentKey; 423 } 424 425 public boolean isModifier() { 426 return mCurrentKey != null && mCurrentKey.isModifier(); 427 } 428 429 public Key getKeyOn(int x, int y) { 430 return mKeyDetector.detectHitKey(x, y); 431 } 432 433 private void setReleasedKeyGraphics(Key key) { 434 mDrawingProxy.dismissKeyPreview(this); 435 if (key == null) { 436 return; 437 } 438 439 // Even if the key is disabled, update the key release graphics just in case. 440 updateReleaseKeyGraphics(key); 441 442 if (key.isShift()) { 443 for (final Key shiftKey : mKeyboard.mShiftKeys) { 444 if (shiftKey != key) { 445 updateReleaseKeyGraphics(shiftKey); 446 } 447 } 448 } 449 450 if (key.altCodeWhileTyping()) { 451 final int altCode = key.mAltCode; 452 final Key altKey = mKeyboard.getKey(altCode); 453 if (altKey != null) { 454 updateReleaseKeyGraphics(altKey); 455 } 456 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 457 if (k != key && k.mAltCode == altCode) { 458 updateReleaseKeyGraphics(k); 459 } 460 } 461 } 462 } 463 464 private void setPressedKeyGraphics(Key key) { 465 if (key == null) { 466 return; 467 } 468 469 // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. 470 final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); 471 final boolean needsToUpdateGraphics = key.isEnabled() || altersCode; 472 if (!needsToUpdateGraphics) { 473 return; 474 } 475 476 if (!key.noKeyPreview() && !mInGesture) { 477 mDrawingProxy.showKeyPreview(this); 478 } 479 updatePressKeyGraphics(key); 480 481 if (key.isShift()) { 482 for (final Key shiftKey : mKeyboard.mShiftKeys) { 483 if (shiftKey != key) { 484 updatePressKeyGraphics(shiftKey); 485 } 486 } 487 } 488 489 if (key.altCodeWhileTyping() && mTimerProxy.isTypingState()) { 490 final int altCode = key.mAltCode; 491 final Key altKey = mKeyboard.getKey(altCode); 492 if (altKey != null) { 493 updatePressKeyGraphics(altKey); 494 } 495 for (final Key k : mKeyboard.mAltCodeKeysWhileTyping) { 496 if (k != key && k.mAltCode == altCode) { 497 updatePressKeyGraphics(k); 498 } 499 } 500 } 501 } 502 503 private void updateReleaseKeyGraphics(Key key) { 504 key.onReleased(); 505 mDrawingProxy.invalidateKey(key); 506 } 507 508 private void updatePressKeyGraphics(Key key) { 509 key.onPressed(); 510 mDrawingProxy.invalidateKey(key); 511 } 512 513 public void drawGestureTrail(Canvas canvas, Paint paint) { 514 if (mInGesture) { 515 mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY); 516 } 517 } 518 519 public int getLastX() { 520 return mLastX; 521 } 522 523 public int getLastY() { 524 return mLastY; 525 } 526 527 public long getDownTime() { 528 return mDownTime; 529 } 530 531 private Key onDownKey(int x, int y, long eventTime) { 532 mDownTime = eventTime; 533 return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); 534 } 535 536 private Key onMoveKeyInternal(int x, int y) { 537 mLastX = x; 538 mLastY = y; 539 return mKeyDetector.detectHitKey(x, y); 540 } 541 542 private Key onMoveKey(int x, int y) { 543 return onMoveKeyInternal(x, y); 544 } 545 546 private Key onMoveToNewKey(Key newKey, int x, int y) { 547 mCurrentKey = newKey; 548 mKeyX = x; 549 mKeyY = y; 550 return newKey; 551 } 552 553 private void startBatchInput() { 554 if (DEBUG_LISTENER) { 555 Log.d(TAG, "onStartBatchInput"); 556 } 557 mInGesture = true; 558 mListener.onStartBatchInput(); 559 } 560 561 private void updateBatchInput(InputPointers batchPoints) { 562 if (DEBUG_LISTENER) { 563 Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); 564 } 565 mListener.onUpdateBatchInput(batchPoints); 566 } 567 568 private void endBatchInput(InputPointers batchPoints) { 569 if (DEBUG_LISTENER) { 570 Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize()); 571 } 572 mListener.onEndBatchInput(batchPoints); 573 clearBatchInputRecognitionStateOfThisPointerTracker(); 574 clearBatchInputPointsOfAllPointerTrackers(); 575 sWasInGesture = true; 576 } 577 578 private void abortBatchInput() { 579 clearBatchInputRecognitionStateOfThisPointerTracker(); 580 clearBatchInputPointsOfAllPointerTrackers(); 581 } 582 583 private void clearBatchInputRecognitionStateOfThisPointerTracker() { 584 mIsPossibleGesture = false; 585 mInGesture = false; 586 mLastRecognitionPointSize = 0; 587 mLastRecognitionTime = 0; 588 } 589 590 private boolean updateBatchInputRecognitionState(long eventTime, int size) { 591 if (size > mLastRecognitionPointSize 592 && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) { 593 mLastRecognitionPointSize = size; 594 mLastRecognitionTime = eventTime; 595 return true; 596 } 597 return false; 598 } 599 600 public void processMotionEvent(int action, int x, int y, long eventTime, 601 KeyEventHandler handler) { 602 switch (action) { 603 case MotionEvent.ACTION_DOWN: 604 case MotionEvent.ACTION_POINTER_DOWN: 605 onDownEvent(x, y, eventTime, handler); 606 break; 607 case MotionEvent.ACTION_UP: 608 case MotionEvent.ACTION_POINTER_UP: 609 onUpEvent(x, y, eventTime); 610 break; 611 case MotionEvent.ACTION_MOVE: 612 onMoveEvent(x, y, eventTime, null); 613 break; 614 case MotionEvent.ACTION_CANCEL: 615 onCancelEvent(x, y, eventTime); 616 break; 617 } 618 } 619 620 public void onDownEvent(int x, int y, long eventTime, KeyEventHandler handler) { 621 if (DEBUG_EVENT) 622 printTouchEvent("onDownEvent:", x, y, eventTime); 623 624 mDrawingProxy = handler.getDrawingProxy(); 625 mTimerProxy = handler.getTimerProxy(); 626 setKeyboardActionListener(handler.getKeyboardActionListener()); 627 setKeyDetectorInner(handler.getKeyDetector()); 628 // Naive up-to-down noise filter. 629 final long deltaT = eventTime - mUpTime; 630 if (deltaT < sParams.mTouchNoiseThresholdTime) { 631 final int dx = x - mLastX; 632 final int dy = y - mLastY; 633 final int distanceSquared = (dx * dx + dy * dy); 634 if (distanceSquared < sTouchNoiseThresholdDistanceSquared) { 635 if (DEBUG_MODE) 636 Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT 637 + " distance=" + distanceSquared); 638 if (ProductionFlag.IS_EXPERIMENTAL) { 639 ResearchLogger.pointerTracker_onDownEvent(deltaT, distanceSquared); 640 } 641 mKeyAlreadyProcessed = true; 642 return; 643 } 644 } 645 646 final PointerTrackerQueue queue = sPointerTrackerQueue; 647 final Key key = getKeyOn(x, y); 648 if (queue != null) { 649 if (key != null && key.isModifier()) { 650 // Before processing a down event of modifier key, all pointers already being 651 // tracked should be released. 652 queue.releaseAllPointers(eventTime); 653 } 654 queue.add(this); 655 } 656 onDownEventInternal(x, y, eventTime); 657 if (queue != null && queue.size() == 1) { 658 mIsPossibleGesture = false; 659 // A gesture should start only from the letter key. 660 if (sShouldHandleGesture && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel 661 && key != null && Keyboard.isLetterCode(key.mCode)) { 662 mIsPossibleGesture = true; 663 // TODO: pointer times should be relative to first down even in entire batch input 664 // instead of resetting to 0 for each new down event. 665 mGestureStroke.addPoint(x, y, 0, false); 666 } 667 } 668 } 669 670 private void onDownEventInternal(int x, int y, long eventTime) { 671 Key key = onDownKey(x, y, eventTime); 672 // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding 673 // from modifier key, or 3) this pointer's KeyDetector always allows sliding input. 674 mIsAllowedSlidingKeyInput = sParams.mSlidingKeyInputEnabled 675 || (key != null && key.isModifier()) 676 || mKeyDetector.alwaysAllowsSlidingInput(); 677 mKeyboardLayoutHasBeenChanged = false; 678 mKeyAlreadyProcessed = false; 679 mIsInSlidingKeyInput = false; 680 mIgnoreModifierKey = false; 681 if (key != null) { 682 // This onPress call may have changed keyboard layout. Those cases are detected at 683 // {@link #setKeyboard}. In those cases, we should update key according to the new 684 // keyboard layout. 685 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 686 key = onDownKey(x, y, eventTime); 687 } 688 689 startRepeatKey(key); 690 startLongPressTimer(key); 691 setPressedKeyGraphics(key); 692 } 693 } 694 695 private void startSlidingKeyInput(Key key) { 696 if (!mIsInSlidingKeyInput) { 697 mIgnoreModifierKey = key.isModifier(); 698 } 699 mIsInSlidingKeyInput = true; 700 } 701 702 private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime, 703 boolean isHistorical, Key key) { 704 final int gestureTime = (int)(eventTime - tracker.getDownTime()); 705 if (sShouldHandleGesture && mIsPossibleGesture) { 706 final GestureStroke stroke = mGestureStroke; 707 stroke.addPoint(x, y, gestureTime, isHistorical); 708 if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) { 709 startBatchInput(); 710 } 711 } 712 713 if (key != null && mInGesture) { 714 final InputPointers batchPoints = getIncrementalBatchPoints(); 715 mDrawingProxy.showGestureTrail(this); 716 if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { 717 updateBatchInput(batchPoints); 718 } 719 } 720 } 721 722 public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) { 723 if (DEBUG_MOVE_EVENT) 724 printTouchEvent("onMoveEvent:", x, y, eventTime); 725 if (mKeyAlreadyProcessed) 726 return; 727 728 if (me != null) { 729 // Add historical points to gesture path. 730 final int pointerIndex = me.findPointerIndex(mPointerId); 731 final int historicalSize = me.getHistorySize(); 732 for (int h = 0; h < historicalSize; h++) { 733 final int historicalX = (int)me.getHistoricalX(pointerIndex, h); 734 final int historicalY = (int)me.getHistoricalY(pointerIndex, h); 735 final long historicalTime = me.getHistoricalEventTime(h); 736 onGestureMoveEvent(this, historicalX, historicalY, historicalTime, 737 true /* isHistorical */, null); 738 } 739 } 740 741 final int lastX = mLastX; 742 final int lastY = mLastY; 743 final Key oldKey = mCurrentKey; 744 Key key = onMoveKey(x, y); 745 746 // Register move event on gesture tracker. 747 onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key); 748 if (mInGesture) { 749 mIgnoreModifierKey = true; 750 mTimerProxy.cancelLongPressTimer(); 751 mIsInSlidingKeyInput = true; 752 mCurrentKey = null; 753 setReleasedKeyGraphics(oldKey); 754 } 755 756 if (key != null) { 757 if (oldKey == null) { 758 // The pointer has been slid in to the new key, but the finger was not on any keys. 759 // In this case, we must call onPress() to notify that the new key is being pressed. 760 // This onPress call may have changed keyboard layout. Those cases are detected at 761 // {@link #setKeyboard}. In those cases, we should update key according to the 762 // new keyboard layout. 763 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 764 key = onMoveKey(x, y); 765 } 766 onMoveToNewKey(key, x, y); 767 startLongPressTimer(key); 768 setPressedKeyGraphics(key); 769 } else if (isMajorEnoughMoveToBeOnNewKey(x, y, key)) { 770 // The pointer has been slid in to the new key from the previous key, we must call 771 // onRelease() first to notify that the previous key has been released, then call 772 // onPress() to notify that the new key is being pressed. 773 setReleasedKeyGraphics(oldKey); 774 callListenerOnRelease(oldKey, oldKey.mCode, true); 775 startSlidingKeyInput(oldKey); 776 mTimerProxy.cancelKeyTimers(); 777 startRepeatKey(key); 778 if (mIsAllowedSlidingKeyInput) { 779 // This onPress call may have changed keyboard layout. Those cases are detected 780 // at {@link #setKeyboard}. In those cases, we should update key according 781 // to the new keyboard layout. 782 if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { 783 key = onMoveKey(x, y); 784 } 785 onMoveToNewKey(key, x, y); 786 startLongPressTimer(key); 787 setPressedKeyGraphics(key); 788 } else { 789 // HACK: On some devices, quick successive touches may be translated to sudden 790 // move by touch panel firmware. This hack detects the case and translates the 791 // move event to successive up and down events. 792 final int dx = x - lastX; 793 final int dy = y - lastY; 794 final int lastMoveSquared = dx * dx + dy * dy; 795 // TODO: Should find a way to balance gesture detection and this hack. 796 if (sNeedsPhantomSuddenMoveEventHack 797 && lastMoveSquared >= mKeyQuarterWidthSquared 798 && !mIsPossibleGesture) { 799 if (DEBUG_MODE) { 800 Log.w(TAG, String.format("onMoveEvent:" 801 + " phantom sudden move event is translated to " 802 + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y)); 803 } 804 // TODO: This should be moved to outside of this nested if-clause? 805 if (ProductionFlag.IS_EXPERIMENTAL) { 806 ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); 807 } 808 onUpEventInternal(x, y, eventTime); 809 onDownEventInternal(x, y, eventTime); 810 } else { 811 // HACK: If there are currently multiple touches, register the key even if 812 // the finger slides off the key. This defends against noise from some 813 // touch panels when there are close multiple touches. 814 // Caveat: When in chording input mode with a modifier key, we don't use 815 // this hack. 816 if (me != null && me.getPointerCount() > 1 817 && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { 818 onUpEventInternal(x, y, eventTime); 819 } 820 if (!mIsPossibleGesture) { 821 mKeyAlreadyProcessed = true; 822 } 823 setReleasedKeyGraphics(oldKey); 824 } 825 } 826 } 827 } else { 828 if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, key)) { 829 // The pointer has been slid out from the previous key, we must call onRelease() to 830 // notify that the previous key has been released. 831 setReleasedKeyGraphics(oldKey); 832 callListenerOnRelease(oldKey, oldKey.mCode, true); 833 startSlidingKeyInput(oldKey); 834 mTimerProxy.cancelLongPressTimer(); 835 if (mIsAllowedSlidingKeyInput) { 836 onMoveToNewKey(key, x, y); 837 } else { 838 if (!mIsPossibleGesture) { 839 mKeyAlreadyProcessed = true; 840 } 841 } 842 } 843 } 844 } 845 846 public void onUpEvent(int x, int y, long eventTime) { 847 if (DEBUG_EVENT) 848 printTouchEvent("onUpEvent :", x, y, eventTime); 849 850 final PointerTrackerQueue queue = sPointerTrackerQueue; 851 if (queue != null) { 852 if (!mInGesture) { 853 if (mCurrentKey != null && mCurrentKey.isModifier()) { 854 // Before processing an up event of modifier key, all pointers already being 855 // tracked should be released. 856 queue.releaseAllPointersExcept(this, eventTime); 857 } else { 858 queue.releaseAllPointersOlderThan(this, eventTime); 859 } 860 } 861 queue.remove(this); 862 } 863 onUpEventInternal(x, y, eventTime); 864 } 865 866 // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. 867 // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a 868 // "virtual" up event. 869 public void onPhantomUpEvent(int x, int y, long eventTime) { 870 if (DEBUG_EVENT) 871 printTouchEvent("onPhntEvent:", x, y, eventTime); 872 onUpEventInternal(x, y, eventTime); 873 mKeyAlreadyProcessed = true; 874 } 875 876 private void onUpEventInternal(int x, int y, long eventTime) { 877 mTimerProxy.cancelKeyTimers(); 878 mIsInSlidingKeyInput = false; 879 mIsPossibleGesture = false; 880 // Release the last pressed key. 881 setReleasedKeyGraphics(mCurrentKey); 882 if (mIsShowingMoreKeysPanel) { 883 mDrawingProxy.dismissMoreKeysPanel(); 884 mIsShowingMoreKeysPanel = false; 885 } 886 887 if (mInGesture) { 888 // Register up event on gesture tracker. 889 // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding, 890 // and/or tapping mode? 891 endBatchInput(getAllBatchPoints()); 892 if (mCurrentKey != null) { 893 callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true); 894 mCurrentKey = null; 895 } 896 mDrawingProxy.showGestureTrail(this); 897 return; 898 } 899 // This event will be recognized as a regular code input. Clear unused batch points so they 900 // are not mistakenly included in the next batch event. 901 clearBatchInputPointsOfAllPointerTrackers(); 902 if (mKeyAlreadyProcessed) 903 return; 904 if (mCurrentKey != null && !mCurrentKey.isRepeatable()) { 905 detectAndSendKey(mCurrentKey, mKeyX, mKeyY); 906 } 907 } 908 909 public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) { 910 abortBatchInput(); 911 onLongPressed(); 912 mIsShowingMoreKeysPanel = true; 913 onDownEvent(x, y, SystemClock.uptimeMillis(), handler); 914 } 915 916 public void onLongPressed() { 917 mKeyAlreadyProcessed = true; 918 setReleasedKeyGraphics(mCurrentKey); 919 final PointerTrackerQueue queue = sPointerTrackerQueue; 920 if (queue != null) { 921 queue.remove(this); 922 } 923 } 924 925 public void onCancelEvent(int x, int y, long eventTime) { 926 if (DEBUG_EVENT) 927 printTouchEvent("onCancelEvt:", x, y, eventTime); 928 929 final PointerTrackerQueue queue = sPointerTrackerQueue; 930 if (queue != null) { 931 queue.releaseAllPointersExcept(this, eventTime); 932 queue.remove(this); 933 } 934 onCancelEventInternal(); 935 } 936 937 private void onCancelEventInternal() { 938 mTimerProxy.cancelKeyTimers(); 939 setReleasedKeyGraphics(mCurrentKey); 940 mIsInSlidingKeyInput = false; 941 if (mIsShowingMoreKeysPanel) { 942 mDrawingProxy.dismissMoreKeysPanel(); 943 mIsShowingMoreKeysPanel = false; 944 } 945 } 946 947 private void startRepeatKey(Key key) { 948 if (key != null && key.isRepeatable() && !mInGesture) { 949 onRegisterKey(key); 950 mTimerProxy.startKeyRepeatTimer(this); 951 } 952 } 953 954 public void onRegisterKey(Key key) { 955 if (key != null) { 956 detectAndSendKey(key, key.mX, key.mY); 957 mTimerProxy.startTypingStateTimer(key); 958 } 959 } 960 961 private boolean isMajorEnoughMoveToBeOnNewKey(int x, int y, Key newKey) { 962 if (mKeyDetector == null) 963 throw new NullPointerException("keyboard and/or key detector not set"); 964 Key curKey = mCurrentKey; 965 if (newKey == curKey) { 966 return false; 967 } else if (curKey != null) { 968 return curKey.squaredDistanceToEdge(x, y) 969 >= mKeyDetector.getKeyHysteresisDistanceSquared(); 970 } else { 971 return true; 972 } 973 } 974 975 private void startLongPressTimer(Key key) { 976 if (key != null && key.isLongPressEnabled() && !mInGesture) { 977 mTimerProxy.startLongPressTimer(this); 978 } 979 } 980 981 private void detectAndSendKey(Key key, int x, int y) { 982 if (key == null) { 983 callListenerOnCancelInput(); 984 return; 985 } 986 987 int code = key.mCode; 988 callListenerOnCodeInput(key, code, x, y); 989 callListenerOnRelease(key, code, false); 990 sWasInGesture = false; 991 } 992 993 private void printTouchEvent(String title, int x, int y, long eventTime) { 994 final Key key = mKeyDetector.detectHitKey(x, y); 995 final String code = KeyDetector.printableCode(key); 996 Log.d(TAG, String.format("%s%s[%d] %4d %4d %5d %s", title, 997 (mKeyAlreadyProcessed ? "-" : " "), mPointerId, x, y, eventTime, code)); 998 } 999} 1000