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         final boolean isGeneratedGesture =
    506                 (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
    507 
    508         // Determine focal point
    509         float sumX = 0, sumY = 0;
    510         final int count = ev.getPointerCount();
    511         for (int i = 0; i < count; i++) {
    512             if (skipIndex == i) continue;
    513             sumX += ev.getX(i);
    514             sumY += ev.getY(i);
    515         }
    516         final int div = pointerUp ? count - 1 : count;
    517         final float focusX = sumX / div;
    518         final float focusY = sumY / div;
    519 
    520         boolean handled = false;
    521 
    522         switch (action & MotionEvent.ACTION_MASK) {
    523             case MotionEvent.ACTION_POINTER_DOWN:
    524                 mDownFocusX = mLastFocusX = focusX;
    525                 mDownFocusY = mLastFocusY = focusY;
    526                 // Cancel long press and taps
    527                 cancelTaps();
    528                 break;
    529 
    530             case MotionEvent.ACTION_POINTER_UP:
    531                 mDownFocusX = mLastFocusX = focusX;
    532                 mDownFocusY = mLastFocusY = focusY;
    533 
    534                 // Check the dot product of current velocities.
    535                 // If the pointer that left was opposing another velocity vector, clear.
    536                 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
    537                 final int upIndex = ev.getActionIndex();
    538                 final int id1 = ev.getPointerId(upIndex);
    539                 final float x1 = mVelocityTracker.getXVelocity(id1);
    540                 final float y1 = mVelocityTracker.getYVelocity(id1);
    541                 for (int i = 0; i < count; i++) {
    542                     if (i == upIndex) continue;
    543 
    544                     final int id2 = ev.getPointerId(i);
    545                     final float x = x1 * mVelocityTracker.getXVelocity(id2);
    546                     final float y = y1 * mVelocityTracker.getYVelocity(id2);
    547 
    548                     final float dot = x + y;
    549                     if (dot < 0) {
    550                         mVelocityTracker.clear();
    551                         break;
    552                     }
    553                 }
    554                 break;
    555 
    556             case MotionEvent.ACTION_DOWN:
    557                 if (mDoubleTapListener != null) {
    558                     boolean hadTapMessage = mHandler.hasMessages(TAP);
    559                     if (hadTapMessage) mHandler.removeMessages(TAP);
    560                     if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
    561                             && hadTapMessage
    562                             && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
    563                         // This is a second tap
    564                         mIsDoubleTapping = true;
    565                         // Give a callback with the first tap of the double-tap
    566                         handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
    567                         // Give a callback with down event of the double-tap
    568                         handled |= mDoubleTapListener.onDoubleTapEvent(ev);
    569                     } else {
    570                         // This is a first tap
    571                         mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
    572                     }
    573                 }
    574 
    575                 mDownFocusX = mLastFocusX = focusX;
    576                 mDownFocusY = mLastFocusY = focusY;
    577                 if (mCurrentDownEvent != null) {
    578                     mCurrentDownEvent.recycle();
    579                 }
    580                 mCurrentDownEvent = MotionEvent.obtain(ev);
    581                 mAlwaysInTapRegion = true;
    582                 mAlwaysInBiggerTapRegion = true;
    583                 mStillDown = true;
    584                 mInLongPress = false;
    585                 mDeferConfirmSingleTap = false;
    586 
    587                 if (mIsLongpressEnabled) {
    588                     mHandler.removeMessages(LONG_PRESS);
    589                     mHandler.sendEmptyMessageAtTime(LONG_PRESS,
    590                             mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
    591                 }
    592                 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
    593                         mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
    594                 handled |= mListener.onDown(ev);
    595                 break;
    596 
    597             case MotionEvent.ACTION_MOVE:
    598                 if (mInLongPress || mInContextClick) {
    599                     break;
    600                 }
    601                 final float scrollX = mLastFocusX - focusX;
    602                 final float scrollY = mLastFocusY - focusY;
    603                 if (mIsDoubleTapping) {
    604                     // Give the move events of the double-tap
    605                     handled |= mDoubleTapListener.onDoubleTapEvent(ev);
    606                 } else if (mAlwaysInTapRegion) {
    607                     final int deltaX = (int) (focusX - mDownFocusX);
    608                     final int deltaY = (int) (focusY - mDownFocusY);
    609                     int distance = (deltaX * deltaX) + (deltaY * deltaY);
    610                     int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
    611                     if (distance > slopSquare) {
    612                         handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
    613                         mLastFocusX = focusX;
    614                         mLastFocusY = focusY;
    615                         mAlwaysInTapRegion = false;
    616                         mHandler.removeMessages(TAP);
    617                         mHandler.removeMessages(SHOW_PRESS);
    618                         mHandler.removeMessages(LONG_PRESS);
    619                     }
    620                     int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
    621                     if (distance > doubleTapSlopSquare) {
    622                         mAlwaysInBiggerTapRegion = false;
    623                     }
    624                 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
    625                     handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
    626                     mLastFocusX = focusX;
    627                     mLastFocusY = focusY;
    628                 }
    629                 break;
    630 
    631             case MotionEvent.ACTION_UP:
    632                 mStillDown = false;
    633                 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
    634                 if (mIsDoubleTapping) {
    635                     // Finally, give the up event of the double-tap
    636                     handled |= mDoubleTapListener.onDoubleTapEvent(ev);
    637                 } else if (mInLongPress) {
    638                     mHandler.removeMessages(TAP);
    639                     mInLongPress = false;
    640                 } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
    641                     handled = mListener.onSingleTapUp(ev);
    642                     if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
    643                         mDoubleTapListener.onSingleTapConfirmed(ev);
    644                     }
    645                 } else if (!mIgnoreNextUpEvent) {
    646 
    647                     // A fling must travel the minimum tap distance
    648                     final VelocityTracker velocityTracker = mVelocityTracker;
    649                     final int pointerId = ev.getPointerId(0);
    650                     velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
    651                     final float velocityY = velocityTracker.getYVelocity(pointerId);
    652                     final float velocityX = velocityTracker.getXVelocity(pointerId);
    653 
    654                     if ((Math.abs(velocityY) > mMinimumFlingVelocity)
    655                             || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
    656                         handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
    657                     }
    658                 }
    659                 if (mPreviousUpEvent != null) {
    660                     mPreviousUpEvent.recycle();
    661                 }
    662                 // Hold the event we obtained above - listeners may have changed the original.
    663                 mPreviousUpEvent = currentUpEvent;
    664                 if (mVelocityTracker != null) {
    665                     // This may have been cleared when we called out to the
    666                     // application above.
    667                     mVelocityTracker.recycle();
    668                     mVelocityTracker = null;
    669                 }
    670                 mIsDoubleTapping = false;
    671                 mDeferConfirmSingleTap = false;
    672                 mIgnoreNextUpEvent = false;
    673                 mHandler.removeMessages(SHOW_PRESS);
    674                 mHandler.removeMessages(LONG_PRESS);
    675                 break;
    676 
    677             case MotionEvent.ACTION_CANCEL:
    678                 cancel();
    679                 break;
    680         }
    681 
    682         if (!handled && mInputEventConsistencyVerifier != null) {
    683             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
    684         }
    685         return handled;
    686     }
    687 
    688     /**
    689      * Analyzes the given generic motion event and if applicable triggers the
    690      * appropriate callbacks on the {@link OnGestureListener} supplied.
    691      *
    692      * @param ev The current motion event.
    693      * @return true if the {@link OnGestureListener} consumed the event,
    694      *              else false.
    695      */
    696     public boolean onGenericMotionEvent(MotionEvent ev) {
    697         if (mInputEventConsistencyVerifier != null) {
    698             mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
    699         }
    700 
    701         final int actionButton = ev.getActionButton();
    702         switch (ev.getActionMasked()) {
    703             case MotionEvent.ACTION_BUTTON_PRESS:
    704                 if (mContextClickListener != null && !mInContextClick && !mInLongPress
    705                         && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
    706                         || actionButton == MotionEvent.BUTTON_SECONDARY)) {
    707                     if (mContextClickListener.onContextClick(ev)) {
    708                         mInContextClick = true;
    709                         mHandler.removeMessages(LONG_PRESS);
    710                         mHandler.removeMessages(TAP);
    711                         return true;
    712                     }
    713                 }
    714                 break;
    715 
    716             case MotionEvent.ACTION_BUTTON_RELEASE:
    717                 if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
    718                         || actionButton == MotionEvent.BUTTON_SECONDARY)) {
    719                     mInContextClick = false;
    720                     mIgnoreNextUpEvent = true;
    721                 }
    722                 break;
    723         }
    724         return false;
    725     }
    726 
    727     private void cancel() {
    728         mHandler.removeMessages(SHOW_PRESS);
    729         mHandler.removeMessages(LONG_PRESS);
    730         mHandler.removeMessages(TAP);
    731         mVelocityTracker.recycle();
    732         mVelocityTracker = null;
    733         mIsDoubleTapping = false;
    734         mStillDown = false;
    735         mAlwaysInTapRegion = false;
    736         mAlwaysInBiggerTapRegion = false;
    737         mDeferConfirmSingleTap = false;
    738         mInLongPress = false;
    739         mInContextClick = false;
    740         mIgnoreNextUpEvent = false;
    741     }
    742 
    743     private void cancelTaps() {
    744         mHandler.removeMessages(SHOW_PRESS);
    745         mHandler.removeMessages(LONG_PRESS);
    746         mHandler.removeMessages(TAP);
    747         mIsDoubleTapping = false;
    748         mAlwaysInTapRegion = false;
    749         mAlwaysInBiggerTapRegion = false;
    750         mDeferConfirmSingleTap = false;
    751         mInLongPress = false;
    752         mInContextClick = false;
    753         mIgnoreNextUpEvent = false;
    754     }
    755 
    756     private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
    757             MotionEvent secondDown) {
    758         if (!mAlwaysInBiggerTapRegion) {
    759             return false;
    760         }
    761 
    762         final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
    763         if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
    764             return false;
    765         }
    766 
    767         int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
    768         int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
    769         final boolean isGeneratedGesture =
    770                 (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
    771         int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
    772         return (deltaX * deltaX + deltaY * deltaY < slopSquare);
    773     }
    774 
    775     private void dispatchLongPress() {
    776         mHandler.removeMessages(TAP);
    777         mDeferConfirmSingleTap = false;
    778         mInLongPress = true;
    779         mListener.onLongPress(mCurrentDownEvent);
    780     }
    781 }
    782