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