1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import static android.os.StrictMode.vmIncorrectContextUseEnabled; 20 21 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; 22 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP; 23 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; 24 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL; 25 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; 26 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; 27 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.Context; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.os.StrictMode; 35 import android.os.SystemClock; 36 import android.util.Log; 37 38 import com.android.internal.util.FrameworkStatsLog; 39 40 /** 41 * Detects various gestures and events using the supplied {@link MotionEvent}s. 42 * The {@link OnGestureListener} callback will notify users when a particular 43 * motion event has occurred. This class should only be used with {@link MotionEvent}s 44 * reported via touch (don't use for trackball events). 45 * 46 * To use this class: 47 * <ul> 48 * <li>Create an instance of the {@code GestureDetector} for your {@link View} 49 * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call 50 * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback 51 * will be executed when the events occur. 52 * <li>If listening for {@link OnContextClickListener#onContextClick(MotionEvent)} 53 * you must call {@link #onGenericMotionEvent(MotionEvent)} 54 * in {@link View#onGenericMotionEvent(MotionEvent)}. 55 * </ul> 56 */ 57 public class GestureDetector { 58 /** 59 * The listener that is used to notify when gestures occur. 60 * If you want to listen for all the different gestures then implement 61 * this interface. If you only want to listen for a subset it might 62 * be easier to extend {@link SimpleOnGestureListener}. 63 */ 64 public interface OnGestureListener { 65 66 /** 67 * Notified when a tap occurs with the down {@link MotionEvent} 68 * that triggered it. This will be triggered immediately for 69 * every down event. All other events should be preceded by this. 70 * 71 * @param e The down motion event. 72 */ onDown(MotionEvent e)73 boolean onDown(MotionEvent e); 74 75 /** 76 * The user has performed a down {@link MotionEvent} and not performed 77 * a move or up yet. This event is commonly used to provide visual 78 * feedback to the user to let them know that their action has been 79 * recognized i.e. highlight an element. 80 * 81 * @param e The down motion event 82 */ onShowPress(MotionEvent e)83 void onShowPress(MotionEvent e); 84 85 /** 86 * Notified when a tap occurs with the up {@link MotionEvent} 87 * that triggered it. 88 * 89 * @param e The up motion event that completed the first tap 90 * @return true if the event is consumed, else false 91 */ onSingleTapUp(MotionEvent e)92 boolean onSingleTapUp(MotionEvent e); 93 94 /** 95 * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the 96 * current move {@link MotionEvent}. The distance in x and y is also supplied for 97 * convenience. 98 * 99 * @param e1 The first down motion event that started the scrolling. 100 * @param e2 The move motion event that triggered the current onScroll. 101 * @param distanceX The distance along the X axis that has been scrolled since the last 102 * call to onScroll. This is NOT the distance between {@code e1} 103 * and {@code e2}. 104 * @param distanceY The distance along the Y axis that has been scrolled since the last 105 * call to onScroll. This is NOT the distance between {@code e1} 106 * and {@code e2}. 107 * @return true if the event is consumed, else false 108 */ onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)109 boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); 110 111 /** 112 * Notified when a long press occurs with the initial on down {@link MotionEvent} 113 * that trigged it. 114 * 115 * @param e The initial on down motion event that started the longpress. 116 */ onLongPress(MotionEvent e)117 void onLongPress(MotionEvent e); 118 119 /** 120 * Notified of a fling event when it occurs with the initial on down {@link MotionEvent} 121 * and the matching up {@link MotionEvent}. The calculated velocity is supplied along 122 * the x and y axis in pixels per second. 123 * 124 * @param e1 The first down motion event that started the fling. 125 * @param e2 The move motion event that triggered the current onFling. 126 * @param velocityX The velocity of this fling measured in pixels per second 127 * along the x axis. 128 * @param velocityY The velocity of this fling measured in pixels per second 129 * along the y axis. 130 * @return true if the event is consumed, else false 131 */ onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)132 boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); 133 } 134 135 /** 136 * The listener that is used to notify when a double-tap or a confirmed 137 * single-tap occur. 138 */ 139 public interface OnDoubleTapListener { 140 /** 141 * Notified when a single-tap occurs. 142 * <p> 143 * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this 144 * will only be called after the detector is confident that the user's 145 * first tap is not followed by a second tap leading to a double-tap 146 * gesture. 147 * 148 * @param e The down motion event of the single-tap. 149 * @return true if the event is consumed, else false 150 */ onSingleTapConfirmed(MotionEvent e)151 boolean onSingleTapConfirmed(MotionEvent e); 152 153 /** 154 * Notified when a double-tap occurs. Triggered on the down event of second tap. 155 * 156 * @param e The down motion event of the first tap of the double-tap. 157 * @return true if the event is consumed, else false 158 */ onDoubleTap(MotionEvent e)159 boolean onDoubleTap(MotionEvent e); 160 161 /** 162 * Notified when an event within a double-tap gesture occurs, including 163 * the down, move, and up events. 164 * 165 * @param e The motion event that occurred during the double-tap gesture. 166 * @return true if the event is consumed, else false 167 */ onDoubleTapEvent(MotionEvent e)168 boolean onDoubleTapEvent(MotionEvent e); 169 } 170 171 /** 172 * The listener that is used to notify when a context click occurs. When listening for a 173 * context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in 174 * {@link View#onGenericMotionEvent(MotionEvent)}. 175 */ 176 public interface OnContextClickListener { 177 /** 178 * Notified when a context click occurs. 179 * 180 * @param e The motion event that occurred during the context click. 181 * @return true if the event is consumed, else false 182 */ onContextClick(MotionEvent e)183 boolean onContextClick(MotionEvent e); 184 } 185 186 /** 187 * A convenience class to extend when you only want to listen for a subset 188 * of all the gestures. This implements all methods in the 189 * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener} 190 * but does nothing and return {@code false} for all applicable methods. 191 */ 192 public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener, 193 OnContextClickListener { 194 onSingleTapUp(MotionEvent e)195 public boolean onSingleTapUp(MotionEvent e) { 196 return false; 197 } 198 onLongPress(MotionEvent e)199 public void onLongPress(MotionEvent e) { 200 } 201 onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)202 public boolean onScroll(MotionEvent e1, MotionEvent e2, 203 float distanceX, float distanceY) { 204 return false; 205 } 206 onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)207 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 208 float velocityY) { 209 return false; 210 } 211 onShowPress(MotionEvent e)212 public void onShowPress(MotionEvent e) { 213 } 214 onDown(MotionEvent e)215 public boolean onDown(MotionEvent e) { 216 return false; 217 } 218 onDoubleTap(MotionEvent e)219 public boolean onDoubleTap(MotionEvent e) { 220 return false; 221 } 222 onDoubleTapEvent(MotionEvent e)223 public boolean onDoubleTapEvent(MotionEvent e) { 224 return false; 225 } 226 onSingleTapConfirmed(MotionEvent e)227 public boolean onSingleTapConfirmed(MotionEvent e) { 228 return false; 229 } 230 onContextClick(MotionEvent e)231 public boolean onContextClick(MotionEvent e) { 232 return false; 233 } 234 } 235 236 private static final String TAG = GestureDetector.class.getSimpleName(); 237 @UnsupportedAppUsage 238 private int mTouchSlopSquare; 239 private int mDoubleTapTouchSlopSquare; 240 private int mDoubleTapSlopSquare; 241 private float mAmbiguousGestureMultiplier; 242 @UnsupportedAppUsage 243 private int mMinimumFlingVelocity; 244 private int mMaximumFlingVelocity; 245 246 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 247 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); 248 private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); 249 private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); 250 private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime(); 251 252 // constants for Message.what used by GestureHandler below 253 private static final int SHOW_PRESS = 1; 254 private static final int LONG_PRESS = 2; 255 private static final int TAP = 3; 256 257 private final Handler mHandler; 258 @UnsupportedAppUsage 259 private final OnGestureListener mListener; 260 private OnDoubleTapListener mDoubleTapListener; 261 private OnContextClickListener mContextClickListener; 262 263 private boolean mStillDown; 264 private boolean mDeferConfirmSingleTap; 265 private boolean mInLongPress; 266 private boolean mInContextClick; 267 @UnsupportedAppUsage 268 private boolean mAlwaysInTapRegion; 269 private boolean mAlwaysInBiggerTapRegion; 270 private boolean mIgnoreNextUpEvent; 271 // Whether a classification has been recorded by statsd for the current event stream. Reset on 272 // ACTION_DOWN. 273 private boolean mHasRecordedClassification; 274 275 private MotionEvent mCurrentDownEvent; 276 private MotionEvent mCurrentMotionEvent; 277 private MotionEvent mPreviousUpEvent; 278 279 /** 280 * True when the user is still touching for the second tap (down, move, and 281 * up events). Can only be true if there is a double tap listener attached. 282 */ 283 private boolean mIsDoubleTapping; 284 285 private float mLastFocusX; 286 private float mLastFocusY; 287 private float mDownFocusX; 288 private float mDownFocusY; 289 290 private boolean mIsLongpressEnabled; 291 292 /** 293 * Determines speed during touch scrolling 294 */ 295 private VelocityTracker mVelocityTracker; 296 297 /** 298 * Consistency verifier for debugging purposes. 299 */ 300 private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = 301 InputEventConsistencyVerifier.isInstrumentationEnabled() ? 302 new InputEventConsistencyVerifier(this, 0) : null; 303 304 private class GestureHandler extends Handler { GestureHandler()305 GestureHandler() { 306 super(); 307 } 308 GestureHandler(Handler handler)309 GestureHandler(Handler handler) { 310 super(handler.getLooper()); 311 } 312 313 @Override handleMessage(Message msg)314 public void handleMessage(Message msg) { 315 switch (msg.what) { 316 case SHOW_PRESS: 317 mListener.onShowPress(mCurrentDownEvent); 318 break; 319 320 case LONG_PRESS: 321 recordGestureClassification(msg.arg1); 322 dispatchLongPress(); 323 break; 324 325 case TAP: 326 // If the user's finger is still down, do not count it as a tap 327 if (mDoubleTapListener != null) { 328 if (!mStillDown) { 329 recordGestureClassification( 330 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); 331 mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); 332 } else { 333 mDeferConfirmSingleTap = true; 334 } 335 } 336 break; 337 338 default: 339 throw new RuntimeException("Unknown message " + msg); //never 340 } 341 } 342 } 343 344 /** 345 * Creates a GestureDetector with the supplied listener. 346 * This variant of the constructor should be used from a non-UI thread 347 * (as it allows specifying the Handler). 348 * 349 * @param listener the listener invoked for all the callbacks, this must 350 * not be null. 351 * @param handler the handler to use 352 * 353 * @throws NullPointerException if either {@code listener} or 354 * {@code handler} is null. 355 * 356 * @deprecated Use {@link #GestureDetector(android.content.Context, 357 * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead. 358 */ 359 @Deprecated GestureDetector(OnGestureListener listener, Handler handler)360 public GestureDetector(OnGestureListener listener, Handler handler) { 361 this(null, listener, handler); 362 } 363 364 /** 365 * Creates a GestureDetector with the supplied listener. 366 * You may only use this constructor from a UI thread (this is the usual situation). 367 * @see android.os.Handler#Handler() 368 * 369 * @param listener the listener invoked for all the callbacks, this must 370 * not be null. 371 * 372 * @throws NullPointerException if {@code listener} is null. 373 * 374 * @deprecated Use {@link #GestureDetector(android.content.Context, 375 * android.view.GestureDetector.OnGestureListener)} instead. 376 */ 377 @Deprecated GestureDetector(OnGestureListener listener)378 public GestureDetector(OnGestureListener listener) { 379 this(null, listener, null); 380 } 381 382 /** 383 * Creates a GestureDetector with the supplied listener. 384 * You may only use this constructor from a {@link android.os.Looper} thread. 385 * @see android.os.Handler#Handler() 386 * 387 * @param context An {@link android.app.Activity} or a {@link Context} created from 388 * {@link Context#createWindowContext(int, Bundle)} 389 * @param listener the listener invoked for all the callbacks, this must 390 * not be null. If the listener implements the {@link OnDoubleTapListener} or 391 * {@link OnContextClickListener} then it will also be set as the listener for 392 * these callbacks (for example when using the {@link SimpleOnGestureListener}). 393 * 394 * @throws NullPointerException if {@code listener} is null. 395 */ GestureDetector(Context context, OnGestureListener listener)396 public GestureDetector(Context context, OnGestureListener listener) { 397 this(context, listener, null); 398 } 399 400 /** 401 * Creates a GestureDetector with the supplied listener that runs deferred events on the 402 * thread associated with the supplied {@link android.os.Handler}. 403 * @see android.os.Handler#Handler() 404 * 405 * @param context An {@link android.app.Activity} or a {@link Context} created from 406 * {@link Context#createWindowContext(int, Bundle)} 407 * @param listener the listener invoked for all the callbacks, this must 408 * not be null. If the listener implements the {@link OnDoubleTapListener} or 409 * {@link OnContextClickListener} then it will also be set as the listener for 410 * these callbacks (for example when using the {@link SimpleOnGestureListener}). 411 * @param handler the handler to use for running deferred listener events. 412 * 413 * @throws NullPointerException if {@code listener} is null. 414 */ GestureDetector(Context context, OnGestureListener listener, Handler handler)415 public GestureDetector(Context context, OnGestureListener listener, Handler handler) { 416 if (handler != null) { 417 mHandler = new GestureHandler(handler); 418 } else { 419 mHandler = new GestureHandler(); 420 } 421 mListener = listener; 422 if (listener instanceof OnDoubleTapListener) { 423 setOnDoubleTapListener((OnDoubleTapListener) listener); 424 } 425 if (listener instanceof OnContextClickListener) { 426 setContextClickListener((OnContextClickListener) listener); 427 } 428 init(context); 429 } 430 431 /** 432 * Creates a GestureDetector with the supplied listener that runs deferred events on the 433 * thread associated with the supplied {@link android.os.Handler}. 434 * @see android.os.Handler#Handler() 435 * 436 * @param context An {@link android.app.Activity} or a {@link Context} created from 437 * {@link Context#createWindowContext(int, Bundle)} 438 * @param listener the listener invoked for all the callbacks, this must 439 * not be null. 440 * @param handler the handler to use for running deferred listener events. 441 * @param unused currently not used. 442 * 443 * @throws NullPointerException if {@code listener} is null. 444 */ GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused)445 public GestureDetector(Context context, OnGestureListener listener, Handler handler, 446 boolean unused) { 447 this(context, listener, handler); 448 } 449 init(Context context)450 private void init(Context context) { 451 if (mListener == null) { 452 throw new NullPointerException("OnGestureListener must not be null"); 453 } 454 mIsLongpressEnabled = true; 455 456 // Fallback to support pre-donuts releases 457 int touchSlop, doubleTapSlop, doubleTapTouchSlop; 458 if (context == null) { 459 //noinspection deprecation 460 touchSlop = ViewConfiguration.getTouchSlop(); 461 doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this 462 doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); 463 //noinspection deprecation 464 mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); 465 mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); 466 mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); 467 } else { 468 if (!context.isUiContext() && vmIncorrectContextUseEnabled()) { 469 final String errorMessage = 470 "Tried to access UI constants from a non-visual Context."; 471 final String message = "GestureDetector must be accessed from Activity or other " 472 + "visual Context. Use an Activity or a Context created with " 473 + "Context#createWindowContext(int, Bundle), which are adjusted to the " 474 + "configuration and visual bounds of an area on screen."; 475 final Exception exception = new IllegalArgumentException(errorMessage); 476 StrictMode.onIncorrectContextUsed(message, exception); 477 Log.e(TAG, errorMessage + message, exception); 478 } 479 final ViewConfiguration configuration = ViewConfiguration.get(context); 480 touchSlop = configuration.getScaledTouchSlop(); 481 doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop(); 482 doubleTapSlop = configuration.getScaledDoubleTapSlop(); 483 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); 484 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity(); 485 mAmbiguousGestureMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); 486 } 487 mTouchSlopSquare = touchSlop * touchSlop; 488 mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop; 489 mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; 490 } 491 492 /** 493 * Sets the listener which will be called for double-tap and related 494 * gestures. 495 * 496 * @param onDoubleTapListener the listener invoked for all the callbacks, or 497 * null to stop listening for double-tap gestures. 498 */ setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)499 public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) { 500 mDoubleTapListener = onDoubleTapListener; 501 } 502 503 /** 504 * Sets the listener which will be called for context clicks. 505 * 506 * @param onContextClickListener the listener invoked for all the callbacks, or null to stop 507 * listening for context clicks. 508 */ setContextClickListener(OnContextClickListener onContextClickListener)509 public void setContextClickListener(OnContextClickListener onContextClickListener) { 510 mContextClickListener = onContextClickListener; 511 } 512 513 /** 514 * Set whether longpress is enabled, if this is enabled when a user 515 * presses and holds down you get a longpress event and nothing further. 516 * If it's disabled the user can press and hold down and then later 517 * moved their finger and you will get scroll events. By default 518 * longpress is enabled. 519 * 520 * @param isLongpressEnabled whether longpress should be enabled. 521 */ setIsLongpressEnabled(boolean isLongpressEnabled)522 public void setIsLongpressEnabled(boolean isLongpressEnabled) { 523 mIsLongpressEnabled = isLongpressEnabled; 524 } 525 526 /** 527 * @return true if longpress is enabled, else false. 528 */ isLongpressEnabled()529 public boolean isLongpressEnabled() { 530 return mIsLongpressEnabled; 531 } 532 533 /** 534 * Analyzes the given motion event and if applicable triggers the 535 * appropriate callbacks on the {@link OnGestureListener} supplied. 536 * 537 * @param ev The current motion event. 538 * @return true if the {@link OnGestureListener} consumed the event, 539 * else false. 540 */ onTouchEvent(MotionEvent ev)541 public boolean onTouchEvent(MotionEvent ev) { 542 if (mInputEventConsistencyVerifier != null) { 543 mInputEventConsistencyVerifier.onTouchEvent(ev, 0); 544 } 545 546 final int action = ev.getAction(); 547 548 if (mCurrentMotionEvent != null) { 549 mCurrentMotionEvent.recycle(); 550 } 551 mCurrentMotionEvent = MotionEvent.obtain(ev); 552 553 if (mVelocityTracker == null) { 554 mVelocityTracker = VelocityTracker.obtain(); 555 } 556 mVelocityTracker.addMovement(ev); 557 558 final boolean pointerUp = 559 (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP; 560 final int skipIndex = pointerUp ? ev.getActionIndex() : -1; 561 final boolean isGeneratedGesture = 562 (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0; 563 564 // Determine focal point 565 float sumX = 0, sumY = 0; 566 final int count = ev.getPointerCount(); 567 for (int i = 0; i < count; i++) { 568 if (skipIndex == i) continue; 569 sumX += ev.getX(i); 570 sumY += ev.getY(i); 571 } 572 final int div = pointerUp ? count - 1 : count; 573 final float focusX = sumX / div; 574 final float focusY = sumY / div; 575 576 boolean handled = false; 577 578 switch (action & MotionEvent.ACTION_MASK) { 579 case MotionEvent.ACTION_POINTER_DOWN: 580 mDownFocusX = mLastFocusX = focusX; 581 mDownFocusY = mLastFocusY = focusY; 582 // Cancel long press and taps 583 cancelTaps(); 584 break; 585 586 case MotionEvent.ACTION_POINTER_UP: 587 mDownFocusX = mLastFocusX = focusX; 588 mDownFocusY = mLastFocusY = focusY; 589 590 // Check the dot product of current velocities. 591 // If the pointer that left was opposing another velocity vector, clear. 592 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); 593 final int upIndex = ev.getActionIndex(); 594 final int id1 = ev.getPointerId(upIndex); 595 final float x1 = mVelocityTracker.getXVelocity(id1); 596 final float y1 = mVelocityTracker.getYVelocity(id1); 597 for (int i = 0; i < count; i++) { 598 if (i == upIndex) continue; 599 600 final int id2 = ev.getPointerId(i); 601 final float x = x1 * mVelocityTracker.getXVelocity(id2); 602 final float y = y1 * mVelocityTracker.getYVelocity(id2); 603 604 final float dot = x + y; 605 if (dot < 0) { 606 mVelocityTracker.clear(); 607 break; 608 } 609 } 610 break; 611 612 case MotionEvent.ACTION_DOWN: 613 if (mDoubleTapListener != null) { 614 boolean hadTapMessage = mHandler.hasMessages(TAP); 615 if (hadTapMessage) mHandler.removeMessages(TAP); 616 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) 617 && hadTapMessage 618 && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { 619 // This is a second tap 620 mIsDoubleTapping = true; 621 recordGestureClassification( 622 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); 623 // Give a callback with the first tap of the double-tap 624 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); 625 // Give a callback with down event of the double-tap 626 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 627 } else { 628 // This is a first tap 629 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); 630 } 631 } 632 633 mDownFocusX = mLastFocusX = focusX; 634 mDownFocusY = mLastFocusY = focusY; 635 if (mCurrentDownEvent != null) { 636 mCurrentDownEvent.recycle(); 637 } 638 mCurrentDownEvent = MotionEvent.obtain(ev); 639 mAlwaysInTapRegion = true; 640 mAlwaysInBiggerTapRegion = true; 641 mStillDown = true; 642 mInLongPress = false; 643 mDeferConfirmSingleTap = false; 644 mHasRecordedClassification = false; 645 646 if (mIsLongpressEnabled) { 647 mHandler.removeMessages(LONG_PRESS); 648 mHandler.sendMessageAtTime( 649 mHandler.obtainMessage( 650 LONG_PRESS, 651 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, 652 0 /* arg2 */), 653 mCurrentDownEvent.getDownTime() 654 + ViewConfiguration.getLongPressTimeout()); 655 } 656 mHandler.sendEmptyMessageAtTime(SHOW_PRESS, 657 mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); 658 handled |= mListener.onDown(ev); 659 break; 660 661 case MotionEvent.ACTION_MOVE: 662 if (mInLongPress || mInContextClick) { 663 break; 664 } 665 666 final int motionClassification = ev.getClassification(); 667 final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS); 668 669 final float scrollX = mLastFocusX - focusX; 670 final float scrollY = mLastFocusY - focusY; 671 if (mIsDoubleTapping) { 672 // Give the move events of the double-tap 673 recordGestureClassification( 674 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); 675 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 676 } else if (mAlwaysInTapRegion) { 677 final int deltaX = (int) (focusX - mDownFocusX); 678 final int deltaY = (int) (focusY - mDownFocusY); 679 int distance = (deltaX * deltaX) + (deltaY * deltaY); 680 int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; 681 682 final boolean ambiguousGesture = 683 motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; 684 final boolean shouldInhibitDefaultAction = 685 hasPendingLongPress && ambiguousGesture; 686 if (shouldInhibitDefaultAction) { 687 // Inhibit default long press 688 if (distance > slopSquare) { 689 // The default action here is to remove long press. But if the touch 690 // slop below gets increased, and we never exceed the modified touch 691 // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing* 692 // will happen in response to user input. To prevent this, 693 // reschedule long press with a modified timeout. 694 mHandler.removeMessages(LONG_PRESS); 695 final long longPressTimeout = ViewConfiguration.getLongPressTimeout(); 696 mHandler.sendMessageAtTime( 697 mHandler.obtainMessage( 698 LONG_PRESS, 699 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, 700 0 /* arg2 */), 701 ev.getDownTime() 702 + (long) (longPressTimeout * mAmbiguousGestureMultiplier)); 703 } 704 // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll 705 // until the gesture is resolved. 706 // However, for safety, simply increase the touch slop in case the 707 // classification is erroneous. Since the value is squared, multiply twice. 708 slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier; 709 } 710 711 if (distance > slopSquare) { 712 recordGestureClassification( 713 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); 714 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); 715 mLastFocusX = focusX; 716 mLastFocusY = focusY; 717 mAlwaysInTapRegion = false; 718 mHandler.removeMessages(TAP); 719 mHandler.removeMessages(SHOW_PRESS); 720 mHandler.removeMessages(LONG_PRESS); 721 } 722 int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare; 723 if (distance > doubleTapSlopSquare) { 724 mAlwaysInBiggerTapRegion = false; 725 } 726 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { 727 recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); 728 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); 729 mLastFocusX = focusX; 730 mLastFocusY = focusY; 731 } 732 final boolean deepPress = 733 motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; 734 if (deepPress && hasPendingLongPress) { 735 mHandler.removeMessages(LONG_PRESS); 736 mHandler.sendMessage( 737 mHandler.obtainMessage( 738 LONG_PRESS, 739 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS, 740 0 /* arg2 */)); 741 } 742 break; 743 744 case MotionEvent.ACTION_UP: 745 mStillDown = false; 746 MotionEvent currentUpEvent = MotionEvent.obtain(ev); 747 if (mIsDoubleTapping) { 748 // Finally, give the up event of the double-tap 749 recordGestureClassification( 750 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); 751 handled |= mDoubleTapListener.onDoubleTapEvent(ev); 752 } else if (mInLongPress) { 753 mHandler.removeMessages(TAP); 754 mInLongPress = false; 755 } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { 756 recordGestureClassification( 757 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); 758 handled = mListener.onSingleTapUp(ev); 759 if (mDeferConfirmSingleTap && mDoubleTapListener != null) { 760 mDoubleTapListener.onSingleTapConfirmed(ev); 761 } 762 } else if (!mIgnoreNextUpEvent) { 763 764 // A fling must travel the minimum tap distance 765 final VelocityTracker velocityTracker = mVelocityTracker; 766 final int pointerId = ev.getPointerId(0); 767 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); 768 final float velocityY = velocityTracker.getYVelocity(pointerId); 769 final float velocityX = velocityTracker.getXVelocity(pointerId); 770 771 if ((Math.abs(velocityY) > mMinimumFlingVelocity) 772 || (Math.abs(velocityX) > mMinimumFlingVelocity)) { 773 handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY); 774 } 775 } 776 if (mPreviousUpEvent != null) { 777 mPreviousUpEvent.recycle(); 778 } 779 // Hold the event we obtained above - listeners may have changed the original. 780 mPreviousUpEvent = currentUpEvent; 781 if (mVelocityTracker != null) { 782 // This may have been cleared when we called out to the 783 // application above. 784 mVelocityTracker.recycle(); 785 mVelocityTracker = null; 786 } 787 mIsDoubleTapping = false; 788 mDeferConfirmSingleTap = false; 789 mIgnoreNextUpEvent = false; 790 mHandler.removeMessages(SHOW_PRESS); 791 mHandler.removeMessages(LONG_PRESS); 792 break; 793 794 case MotionEvent.ACTION_CANCEL: 795 cancel(); 796 break; 797 } 798 799 if (!handled && mInputEventConsistencyVerifier != null) { 800 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0); 801 } 802 return handled; 803 } 804 805 /** 806 * Analyzes the given generic motion event and if applicable triggers the 807 * appropriate callbacks on the {@link OnGestureListener} supplied. 808 * 809 * @param ev The current motion event. 810 * @return true if the {@link OnGestureListener} consumed the event, 811 * else false. 812 */ onGenericMotionEvent(MotionEvent ev)813 public boolean onGenericMotionEvent(MotionEvent ev) { 814 if (mInputEventConsistencyVerifier != null) { 815 mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0); 816 } 817 818 final int actionButton = ev.getActionButton(); 819 switch (ev.getActionMasked()) { 820 case MotionEvent.ACTION_BUTTON_PRESS: 821 if (mContextClickListener != null && !mInContextClick && !mInLongPress 822 && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY 823 || actionButton == MotionEvent.BUTTON_SECONDARY)) { 824 if (mContextClickListener.onContextClick(ev)) { 825 mInContextClick = true; 826 mHandler.removeMessages(LONG_PRESS); 827 mHandler.removeMessages(TAP); 828 return true; 829 } 830 } 831 break; 832 833 case MotionEvent.ACTION_BUTTON_RELEASE: 834 if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY 835 || actionButton == MotionEvent.BUTTON_SECONDARY)) { 836 mInContextClick = false; 837 mIgnoreNextUpEvent = true; 838 } 839 break; 840 } 841 return false; 842 } 843 cancel()844 private void cancel() { 845 mHandler.removeMessages(SHOW_PRESS); 846 mHandler.removeMessages(LONG_PRESS); 847 mHandler.removeMessages(TAP); 848 mVelocityTracker.recycle(); 849 mVelocityTracker = null; 850 mIsDoubleTapping = false; 851 mStillDown = false; 852 mAlwaysInTapRegion = false; 853 mAlwaysInBiggerTapRegion = false; 854 mDeferConfirmSingleTap = false; 855 mInLongPress = false; 856 mInContextClick = false; 857 mIgnoreNextUpEvent = false; 858 } 859 cancelTaps()860 private void cancelTaps() { 861 mHandler.removeMessages(SHOW_PRESS); 862 mHandler.removeMessages(LONG_PRESS); 863 mHandler.removeMessages(TAP); 864 mIsDoubleTapping = false; 865 mAlwaysInTapRegion = false; 866 mAlwaysInBiggerTapRegion = false; 867 mDeferConfirmSingleTap = false; 868 mInLongPress = false; 869 mInContextClick = false; 870 mIgnoreNextUpEvent = false; 871 } 872 isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown)873 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, 874 MotionEvent secondDown) { 875 if (!mAlwaysInBiggerTapRegion) { 876 return false; 877 } 878 879 final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime(); 880 if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) { 881 return false; 882 } 883 884 int deltaX = (int) firstDown.getX() - (int) secondDown.getX(); 885 int deltaY = (int) firstDown.getY() - (int) secondDown.getY(); 886 final boolean isGeneratedGesture = 887 (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0; 888 int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare; 889 return (deltaX * deltaX + deltaY * deltaY < slopSquare); 890 } 891 dispatchLongPress()892 private void dispatchLongPress() { 893 mHandler.removeMessages(TAP); 894 mDeferConfirmSingleTap = false; 895 mInLongPress = true; 896 mListener.onLongPress(mCurrentDownEvent); 897 } 898 recordGestureClassification(int classification)899 private void recordGestureClassification(int classification) { 900 if (mHasRecordedClassification 901 || classification 902 == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) { 903 // Only record the first classification for an event stream. 904 return; 905 } 906 if (mCurrentDownEvent == null || mCurrentMotionEvent == null) { 907 // If the complete event stream wasn't seen, don't record anything. 908 mHasRecordedClassification = true; 909 return; 910 } 911 FrameworkStatsLog.write( 912 FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED, 913 getClass().getName(), 914 classification, 915 (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()), 916 (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(), 917 mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY())); 918 mHasRecordedClassification = true; 919 } 920 } 921