Home | History | Annotate | Download | only in view
      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          */
     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          */
     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          */
     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          */
     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          */
     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          */
    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          */
    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          */
    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          */
    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 {
    158         public boolean onSingleTapUp(MotionEvent e) {
    159             return false;
    160         }
    161 
    162         public void onLongPress(MotionEvent e) {
    163         }
    164 
    165         public boolean onScroll(MotionEvent e1, MotionEvent e2,
    166                 float distanceX, float distanceY) {
    167             return false;
    168         }
    169 
    170         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
    171                 float velocityY) {
    172             return false;
    173         }
    174 
    175         public void onShowPress(MotionEvent e) {
    176         }
    177 
    178         public boolean onDown(MotionEvent e) {
    179             return false;
    180         }
    181 
    182         public boolean onDoubleTap(MotionEvent e) {
    183             return false;
    184         }
    185 
    186         public boolean onDoubleTapEvent(MotionEvent e) {
    187             return false;
    188         }
    189 
    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 {
    250         GestureHandler() {
    251             super();
    252         }
    253 
    254         GestureHandler(Handler handler) {
    255             super(handler.getLooper());
    256         }
    257 
    258         @Override
    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
    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
    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      */
    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      */
    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      */
    377     public GestureDetector(Context context, OnGestureListener listener, Handler handler,
    378             boolean unused) {
    379         this(context, listener, handler);
    380     }
    381 
    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      */
    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      */
    431     public void setIsLongpressEnabled(boolean isLongpressEnabled) {
    432         mIsLongpressEnabled = isLongpressEnabled;
    433     }
    434 
    435     /**
    436      * @return true if longpress is enabled, else false.
    437      */
    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      */
    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 
    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 
    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 
    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 
    686     private void dispatchLongPress() {
    687         mHandler.removeMessages(TAP);
    688         mDeferConfirmSingleTap = false;
    689         mInLongPress = true;
    690         mListener.onLongPress(mCurrentDownEvent);
    691     }
    692 }
    693