• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.content.Context;
20 import android.os.Handler;
21 import android.os.Message;
22 
23 /**
24  * Detects various gestures and events using the supplied {@link MotionEvent}s.
25  * The {@link OnGestureListener} callback will notify users when a particular
26  * motion event has occurred. This class should only be used with {@link MotionEvent}s
27  * reported via touch (don't use for trackball events).
28  *
29  * To use this class:
30  * <ul>
31  *  <li>Create an instance of the {@code GestureDetector} for your {@link View}
32  *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
33  *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
34  *          will be executed when the events occur.
35  * </ul>
36  */
37 public class GestureDetector {
38     /**
39      * The listener that is used to notify when gestures occur.
40      * If you want to listen for all the different gestures then implement
41      * this interface. If you only want to listen for a subset it might
42      * be easier to extend {@link SimpleOnGestureListener}.
43      */
44     public interface OnGestureListener {
45 
46         /**
47          * Notified when a tap occurs with the down {@link MotionEvent}
48          * that triggered it. This will be triggered immediately for
49          * every down event. All other events should be preceded by this.
50          *
51          * @param e The down motion event.
52          */
onDown(MotionEvent e)53         boolean onDown(MotionEvent e);
54 
55         /**
56          * The user has performed a down {@link MotionEvent} and not performed
57          * a move or up yet. This event is commonly used to provide visual
58          * feedback to the user to let them know that their action has been
59          * recognized i.e. highlight an element.
60          *
61          * @param e The down motion event
62          */
onShowPress(MotionEvent e)63         void onShowPress(MotionEvent e);
64 
65         /**
66          * Notified when a tap occurs with the up {@link MotionEvent}
67          * that triggered it.
68          *
69          * @param e The up motion event that completed the first tap
70          * @return true if the event is consumed, else false
71          */
onSingleTapUp(MotionEvent e)72         boolean onSingleTapUp(MotionEvent e);
73 
74         /**
75          * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
76          * current move {@link MotionEvent}. The distance in x and y is also supplied for
77          * convenience.
78          *
79          * @param e1 The first down motion event that started the scrolling.
80          * @param e2 The move motion event that triggered the current onScroll.
81          * @param distanceX The distance along the X axis that has been scrolled since the last
82          *              call to onScroll. This is NOT the distance between {@code e1}
83          *              and {@code e2}.
84          * @param distanceY The distance along the Y axis that has been scrolled since the last
85          *              call to onScroll. This is NOT the distance between {@code e1}
86          *              and {@code e2}.
87          * @return true if the event is consumed, else false
88          */
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)89         boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
90 
91         /**
92          * Notified when a long press occurs with the initial on down {@link MotionEvent}
93          * that trigged it.
94          *
95          * @param e The initial on down motion event that started the longpress.
96          */
onLongPress(MotionEvent e)97         void onLongPress(MotionEvent e);
98 
99         /**
100          * Notified of a fling event when it occurs with the initial on down {@link MotionEvent}
101          * and the matching up {@link MotionEvent}. The calculated velocity is supplied along
102          * the x and y axis in pixels per second.
103          *
104          * @param e1 The first down motion event that started the fling.
105          * @param e2 The move motion event that triggered the current onFling.
106          * @param velocityX The velocity of this fling measured in pixels per second
107          *              along the x axis.
108          * @param velocityY The velocity of this fling measured in pixels per second
109          *              along the y axis.
110          * @return true if the event is consumed, else false
111          */
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)112         boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
113     }
114 
115     /**
116      * The listener that is used to notify when a double-tap or a confirmed
117      * single-tap occur.
118      */
119     public interface OnDoubleTapListener {
120         /**
121          * Notified when a single-tap occurs.
122          * <p>
123          * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
124          * will only be called after the detector is confident that the user's
125          * first tap is not followed by a second tap leading to a double-tap
126          * gesture.
127          *
128          * @param e The down motion event of the single-tap.
129          * @return true if the event is consumed, else false
130          */
onSingleTapConfirmed(MotionEvent e)131         boolean onSingleTapConfirmed(MotionEvent e);
132 
133         /**
134          * Notified when a double-tap occurs.
135          *
136          * @param e The down motion event of the first tap of the double-tap.
137          * @return true if the event is consumed, else false
138          */
onDoubleTap(MotionEvent e)139         boolean onDoubleTap(MotionEvent e);
140 
141         /**
142          * Notified when an event within a double-tap gesture occurs, including
143          * the down, move, and up events.
144          *
145          * @param e The motion event that occurred during the double-tap gesture.
146          * @return true if the event is consumed, else false
147          */
onDoubleTapEvent(MotionEvent e)148         boolean onDoubleTapEvent(MotionEvent e);
149     }
150 
151     /**
152      * A convenience class to extend when you only want to listen for a subset
153      * of all the gestures. This implements all methods in the
154      * {@link OnGestureListener} and {@link OnDoubleTapListener} but does
155      * nothing and return {@code false} for all applicable methods.
156      */
157     public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
onSingleTapUp(MotionEvent e)158         public boolean onSingleTapUp(MotionEvent e) {
159             return false;
160         }
161 
onLongPress(MotionEvent e)162         public void onLongPress(MotionEvent e) {
163         }
164 
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)165         public boolean onScroll(MotionEvent e1, MotionEvent e2,
166                 float distanceX, float distanceY) {
167             return false;
168         }
169 
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)170         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
171                 float velocityY) {
172             return false;
173         }
174 
onShowPress(MotionEvent e)175         public void onShowPress(MotionEvent e) {
176         }
177 
onDown(MotionEvent e)178         public boolean onDown(MotionEvent e) {
179             return false;
180         }
181 
onDoubleTap(MotionEvent e)182         public boolean onDoubleTap(MotionEvent e) {
183             return false;
184         }
185 
onDoubleTapEvent(MotionEvent e)186         public boolean onDoubleTapEvent(MotionEvent e) {
187             return false;
188         }
189 
onSingleTapConfirmed(MotionEvent e)190         public boolean onSingleTapConfirmed(MotionEvent e) {
191             return false;
192         }
193     }
194 
195     private int mTouchSlopSquare;
196     private int mDoubleTapTouchSlopSquare;
197     private int mDoubleTapSlopSquare;
198     private int mMinimumFlingVelocity;
199     private int mMaximumFlingVelocity;
200 
201     private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
202     private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
203     private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
204     private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
205 
206     // constants for Message.what used by GestureHandler below
207     private static final int SHOW_PRESS = 1;
208     private static final int LONG_PRESS = 2;
209     private static final int TAP = 3;
210 
211     private final Handler mHandler;
212     private final OnGestureListener mListener;
213     private OnDoubleTapListener mDoubleTapListener;
214 
215     private boolean mStillDown;
216     private boolean mDeferConfirmSingleTap;
217     private boolean mInLongPress;
218     private boolean mAlwaysInTapRegion;
219     private boolean mAlwaysInBiggerTapRegion;
220 
221     private MotionEvent mCurrentDownEvent;
222     private MotionEvent mPreviousUpEvent;
223 
224     /**
225      * True when the user is still touching for the second tap (down, move, and
226      * up events). Can only be true if there is a double tap listener attached.
227      */
228     private boolean mIsDoubleTapping;
229 
230     private float mLastFocusX;
231     private float mLastFocusY;
232     private float mDownFocusX;
233     private float mDownFocusY;
234 
235     private boolean mIsLongpressEnabled;
236 
237     /**
238      * Determines speed during touch scrolling
239      */
240     private VelocityTracker mVelocityTracker;
241 
242     /**
243      * Consistency verifier for debugging purposes.
244      */
245     private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
246             InputEventConsistencyVerifier.isInstrumentationEnabled() ?
247                     new InputEventConsistencyVerifier(this, 0) : null;
248 
249     private class GestureHandler extends Handler {
GestureHandler()250         GestureHandler() {
251             super();
252         }
253 
GestureHandler(Handler handler)254         GestureHandler(Handler handler) {
255             super(handler.getLooper());
256         }
257 
258         @Override
handleMessage(Message msg)259         public void handleMessage(Message msg) {
260             switch (msg.what) {
261             case SHOW_PRESS:
262                 mListener.onShowPress(mCurrentDownEvent);
263                 break;
264 
265             case LONG_PRESS:
266                 dispatchLongPress();
267                 break;
268 
269             case TAP:
270                 // If the user's finger is still down, do not count it as a tap
271                 if (mDoubleTapListener != null) {
272                     if (!mStillDown) {
273                         mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
274                     } else {
275                         mDeferConfirmSingleTap = true;
276                     }
277                 }
278                 break;
279 
280             default:
281                 throw new RuntimeException("Unknown message " + msg); //never
282             }
283         }
284     }
285 
286     /**
287      * Creates a GestureDetector with the supplied listener.
288      * This variant of the constructor should be used from a non-UI thread
289      * (as it allows specifying the Handler).
290      *
291      * @param listener the listener invoked for all the callbacks, this must
292      * not be null.
293      * @param handler the handler to use
294      *
295      * @throws NullPointerException if either {@code listener} or
296      * {@code handler} is null.
297      *
298      * @deprecated Use {@link #GestureDetector(android.content.Context,
299      *      android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
300      */
301     @Deprecated
GestureDetector(OnGestureListener listener, Handler handler)302     public GestureDetector(OnGestureListener listener, Handler handler) {
303         this(null, listener, handler);
304     }
305 
306     /**
307      * Creates a GestureDetector with the supplied listener.
308      * You may only use this constructor from a UI thread (this is the usual situation).
309      * @see android.os.Handler#Handler()
310      *
311      * @param listener the listener invoked for all the callbacks, this must
312      * not be null.
313      *
314      * @throws NullPointerException if {@code listener} is null.
315      *
316      * @deprecated Use {@link #GestureDetector(android.content.Context,
317      *      android.view.GestureDetector.OnGestureListener)} instead.
318      */
319     @Deprecated
GestureDetector(OnGestureListener listener)320     public GestureDetector(OnGestureListener listener) {
321         this(null, listener, null);
322     }
323 
324     /**
325      * Creates a GestureDetector with the supplied listener.
326      * You may only use this constructor from a {@link android.os.Looper} thread.
327      * @see android.os.Handler#Handler()
328      *
329      * @param context the application's context
330      * @param listener the listener invoked for all the callbacks, this must
331      * not be null.
332      *
333      * @throws NullPointerException if {@code listener} is null.
334      */
GestureDetector(Context context, OnGestureListener listener)335     public GestureDetector(Context context, OnGestureListener listener) {
336         this(context, listener, null);
337     }
338 
339     /**
340      * Creates a GestureDetector with the supplied listener that runs deferred events on the
341      * thread associated with the supplied {@link android.os.Handler}.
342      * @see android.os.Handler#Handler()
343      *
344      * @param context the application's context
345      * @param listener the listener invoked for all the callbacks, this must
346      * not be null.
347      * @param handler the handler to use for running deferred listener events.
348      *
349      * @throws NullPointerException if {@code listener} is null.
350      */
GestureDetector(Context context, OnGestureListener listener, Handler handler)351     public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
352         if (handler != null) {
353             mHandler = new GestureHandler(handler);
354         } else {
355             mHandler = new GestureHandler();
356         }
357         mListener = listener;
358         if (listener instanceof OnDoubleTapListener) {
359             setOnDoubleTapListener((OnDoubleTapListener) listener);
360         }
361         init(context);
362     }
363 
364     /**
365      * Creates a GestureDetector with the supplied listener that runs deferred events on the
366      * thread associated with the supplied {@link android.os.Handler}.
367      * @see android.os.Handler#Handler()
368      *
369      * @param context the application's context
370      * @param listener the listener invoked for all the callbacks, this must
371      * not be null.
372      * @param handler the handler to use for running deferred listener events.
373      * @param unused currently not used.
374      *
375      * @throws NullPointerException if {@code listener} is null.
376      */
GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused)377     public GestureDetector(Context context, OnGestureListener listener, Handler handler,
378             boolean unused) {
379         this(context, listener, handler);
380     }
381 
init(Context context)382     private void init(Context context) {
383         if (mListener == null) {
384             throw new NullPointerException("OnGestureListener must not be null");
385         }
386         mIsLongpressEnabled = true;
387 
388         // Fallback to support pre-donuts releases
389         int touchSlop, doubleTapSlop, doubleTapTouchSlop;
390         if (context == null) {
391             //noinspection deprecation
392             touchSlop = ViewConfiguration.getTouchSlop();
393             doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
394             doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
395             //noinspection deprecation
396             mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
397             mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
398         } else {
399             final ViewConfiguration configuration = ViewConfiguration.get(context);
400             touchSlop = configuration.getScaledTouchSlop();
401             doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
402             doubleTapSlop = configuration.getScaledDoubleTapSlop();
403             mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
404             mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
405         }
406         mTouchSlopSquare = touchSlop * touchSlop;
407         mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
408         mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
409     }
410 
411     /**
412      * Sets the listener which will be called for double-tap and related
413      * gestures.
414      *
415      * @param onDoubleTapListener the listener invoked for all the callbacks, or
416      *        null to stop listening for double-tap gestures.
417      */
setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)418     public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
419         mDoubleTapListener = onDoubleTapListener;
420     }
421 
422     /**
423      * Set whether longpress is enabled, if this is enabled when a user
424      * presses and holds down you get a longpress event and nothing further.
425      * If it's disabled the user can press and hold down and then later
426      * moved their finger and you will get scroll events. By default
427      * longpress is enabled.
428      *
429      * @param isLongpressEnabled whether longpress should be enabled.
430      */
setIsLongpressEnabled(boolean isLongpressEnabled)431     public void setIsLongpressEnabled(boolean isLongpressEnabled) {
432         mIsLongpressEnabled = isLongpressEnabled;
433     }
434 
435     /**
436      * @return true if longpress is enabled, else false.
437      */
isLongpressEnabled()438     public boolean isLongpressEnabled() {
439         return mIsLongpressEnabled;
440     }
441 
442     /**
443      * Analyzes the given motion event and if applicable triggers the
444      * appropriate callbacks on the {@link OnGestureListener} supplied.
445      *
446      * @param ev The current motion event.
447      * @return true if the {@link OnGestureListener} consumed the event,
448      *              else false.
449      */
onTouchEvent(MotionEvent ev)450     public boolean onTouchEvent(MotionEvent ev) {
451         if (mInputEventConsistencyVerifier != null) {
452             mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
453         }
454 
455         final int action = ev.getAction();
456 
457         if (mVelocityTracker == null) {
458             mVelocityTracker = VelocityTracker.obtain();
459         }
460         mVelocityTracker.addMovement(ev);
461 
462         final boolean pointerUp =
463                 (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
464         final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
465 
466         // Determine focal point
467         float sumX = 0, sumY = 0;
468         final int count = ev.getPointerCount();
469         for (int i = 0; i < count; i++) {
470             if (skipIndex == i) continue;
471             sumX += ev.getX(i);
472             sumY += ev.getY(i);
473         }
474         final int div = pointerUp ? count - 1 : count;
475         final float focusX = sumX / div;
476         final float focusY = sumY / div;
477 
478         boolean handled = false;
479 
480         switch (action & MotionEvent.ACTION_MASK) {
481         case MotionEvent.ACTION_POINTER_DOWN:
482             mDownFocusX = mLastFocusX = focusX;
483             mDownFocusY = mLastFocusY = focusY;
484             // Cancel long press and taps
485             cancelTaps();
486             break;
487 
488         case MotionEvent.ACTION_POINTER_UP:
489             mDownFocusX = mLastFocusX = focusX;
490             mDownFocusY = mLastFocusY = focusY;
491 
492             // Check the dot product of current velocities.
493             // If the pointer that left was opposing another velocity vector, clear.
494             mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
495             final int upIndex = ev.getActionIndex();
496             final int id1 = ev.getPointerId(upIndex);
497             final float x1 = mVelocityTracker.getXVelocity(id1);
498             final float y1 = mVelocityTracker.getYVelocity(id1);
499             for (int i = 0; i < count; i++) {
500                 if (i == upIndex) continue;
501 
502                 final int id2 = ev.getPointerId(i);
503                 final float x = x1 * mVelocityTracker.getXVelocity(id2);
504                 final float y = y1 * mVelocityTracker.getYVelocity(id2);
505 
506                 final float dot = x + y;
507                 if (dot < 0) {
508                     mVelocityTracker.clear();
509                     break;
510                 }
511             }
512             break;
513 
514         case MotionEvent.ACTION_DOWN:
515             if (mDoubleTapListener != null) {
516                 boolean hadTapMessage = mHandler.hasMessages(TAP);
517                 if (hadTapMessage) mHandler.removeMessages(TAP);
518                 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
519                         isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
520                     // This is a second tap
521                     mIsDoubleTapping = true;
522                     // Give a callback with the first tap of the double-tap
523                     handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
524                     // Give a callback with down event of the double-tap
525                     handled |= mDoubleTapListener.onDoubleTapEvent(ev);
526                 } else {
527                     // This is a first tap
528                     mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
529                 }
530             }
531 
532             mDownFocusX = mLastFocusX = focusX;
533             mDownFocusY = mLastFocusY = focusY;
534             if (mCurrentDownEvent != null) {
535                 mCurrentDownEvent.recycle();
536             }
537             mCurrentDownEvent = MotionEvent.obtain(ev);
538             mAlwaysInTapRegion = true;
539             mAlwaysInBiggerTapRegion = true;
540             mStillDown = true;
541             mInLongPress = false;
542             mDeferConfirmSingleTap = false;
543 
544             if (mIsLongpressEnabled) {
545                 mHandler.removeMessages(LONG_PRESS);
546                 mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
547                         + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
548             }
549             mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
550             handled |= mListener.onDown(ev);
551             break;
552 
553         case MotionEvent.ACTION_MOVE:
554             if (mInLongPress) {
555                 break;
556             }
557             final float scrollX = mLastFocusX - focusX;
558             final float scrollY = mLastFocusY - focusY;
559             if (mIsDoubleTapping) {
560                 // Give the move events of the double-tap
561                 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
562             } else if (mAlwaysInTapRegion) {
563                 final int deltaX = (int) (focusX - mDownFocusX);
564                 final int deltaY = (int) (focusY - mDownFocusY);
565                 int distance = (deltaX * deltaX) + (deltaY * deltaY);
566                 if (distance > mTouchSlopSquare) {
567                     handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
568                     mLastFocusX = focusX;
569                     mLastFocusY = focusY;
570                     mAlwaysInTapRegion = false;
571                     mHandler.removeMessages(TAP);
572                     mHandler.removeMessages(SHOW_PRESS);
573                     mHandler.removeMessages(LONG_PRESS);
574                 }
575                 if (distance > mDoubleTapTouchSlopSquare) {
576                     mAlwaysInBiggerTapRegion = false;
577                 }
578             } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
579                 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
580                 mLastFocusX = focusX;
581                 mLastFocusY = focusY;
582             }
583             break;
584 
585         case MotionEvent.ACTION_UP:
586             mStillDown = false;
587             MotionEvent currentUpEvent = MotionEvent.obtain(ev);
588             if (mIsDoubleTapping) {
589                 // Finally, give the up event of the double-tap
590                 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
591             } else if (mInLongPress) {
592                 mHandler.removeMessages(TAP);
593                 mInLongPress = false;
594             } else if (mAlwaysInTapRegion) {
595                 handled = mListener.onSingleTapUp(ev);
596                 if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
597                     mDoubleTapListener.onSingleTapConfirmed(ev);
598                 }
599             } else {
600 
601                 // A fling must travel the minimum tap distance
602                 final VelocityTracker velocityTracker = mVelocityTracker;
603                 final int pointerId = ev.getPointerId(0);
604                 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
605                 final float velocityY = velocityTracker.getYVelocity(pointerId);
606                 final float velocityX = velocityTracker.getXVelocity(pointerId);
607 
608                 if ((Math.abs(velocityY) > mMinimumFlingVelocity)
609                         || (Math.abs(velocityX) > mMinimumFlingVelocity)){
610                     handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
611                 }
612             }
613             if (mPreviousUpEvent != null) {
614                 mPreviousUpEvent.recycle();
615             }
616             // Hold the event we obtained above - listeners may have changed the original.
617             mPreviousUpEvent = currentUpEvent;
618             if (mVelocityTracker != null) {
619                 // This may have been cleared when we called out to the
620                 // application above.
621                 mVelocityTracker.recycle();
622                 mVelocityTracker = null;
623             }
624             mIsDoubleTapping = false;
625             mDeferConfirmSingleTap = false;
626             mHandler.removeMessages(SHOW_PRESS);
627             mHandler.removeMessages(LONG_PRESS);
628             break;
629 
630         case MotionEvent.ACTION_CANCEL:
631             cancel();
632             break;
633         }
634 
635         if (!handled && mInputEventConsistencyVerifier != null) {
636             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
637         }
638         return handled;
639     }
640 
cancel()641     private void cancel() {
642         mHandler.removeMessages(SHOW_PRESS);
643         mHandler.removeMessages(LONG_PRESS);
644         mHandler.removeMessages(TAP);
645         mVelocityTracker.recycle();
646         mVelocityTracker = null;
647         mIsDoubleTapping = false;
648         mStillDown = false;
649         mAlwaysInTapRegion = false;
650         mAlwaysInBiggerTapRegion = false;
651         mDeferConfirmSingleTap = false;
652         if (mInLongPress) {
653             mInLongPress = false;
654         }
655     }
656 
cancelTaps()657     private void cancelTaps() {
658         mHandler.removeMessages(SHOW_PRESS);
659         mHandler.removeMessages(LONG_PRESS);
660         mHandler.removeMessages(TAP);
661         mIsDoubleTapping = false;
662         mAlwaysInTapRegion = false;
663         mAlwaysInBiggerTapRegion = false;
664         mDeferConfirmSingleTap = false;
665         if (mInLongPress) {
666             mInLongPress = false;
667         }
668     }
669 
isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown)670     private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
671             MotionEvent secondDown) {
672         if (!mAlwaysInBiggerTapRegion) {
673             return false;
674         }
675 
676         final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
677         if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
678             return false;
679         }
680 
681         int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
682         int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
683         return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
684     }
685 
dispatchLongPress()686     private void dispatchLongPress() {
687         mHandler.removeMessages(TAP);
688         mDeferConfirmSingleTap = false;
689         mInLongPress = true;
690         mListener.onLongPress(mCurrentDownEvent);
691     }
692 }
693