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