• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.Message;
22 
23 /**
24  * Detects various gestures and events using the supplied {@link MotionEvent}s.
25  * The {@link OnGestureListener} callback will notify users when a particular
26  * motion event has occurred. This class should only be used with {@link MotionEvent}s
27  * reported via touch (don't use for trackball events).
28  *
29  * To use this class:
30  * <ul>
31  *  <li>Create an instance of the {@code GestureDetector} for your {@link View}
32  *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
33  *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
34  *          will be executed when the events occur.
35  *  <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          */
onDown(MotionEvent e)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          */
onShowPress(MotionEvent e)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          */
onSingleTapUp(MotionEvent e)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          */
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)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          */
onLongPress(MotionEvent e)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          */
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)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          */
onSingleTapConfirmed(MotionEvent e)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          */
onDoubleTap(MotionEvent e)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          */
onDoubleTapEvent(MotionEvent e)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          */
onContextClick(MotionEvent e)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 
onSingleTapUp(MotionEvent e)178         public boolean onSingleTapUp(MotionEvent e) {
179             return false;
180         }
181 
onLongPress(MotionEvent e)182         public void onLongPress(MotionEvent e) {
183         }
184 
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)185         public boolean onScroll(MotionEvent e1, MotionEvent e2,
186                 float distanceX, float distanceY) {
187             return false;
188         }
189 
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)190         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
191                 float velocityY) {
192             return false;
193         }
194 
onShowPress(MotionEvent e)195         public void onShowPress(MotionEvent e) {
196         }
197 
onDown(MotionEvent e)198         public boolean onDown(MotionEvent e) {
199             return false;
200         }
201 
onDoubleTap(MotionEvent e)202         public boolean onDoubleTap(MotionEvent e) {
203             return false;
204         }
205 
onDoubleTapEvent(MotionEvent e)206         public boolean onDoubleTapEvent(MotionEvent e) {
207             return false;
208         }
209 
onSingleTapConfirmed(MotionEvent e)210         public boolean onSingleTapConfirmed(MotionEvent e) {
211             return false;
212         }
213 
onContextClick(MotionEvent e)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 {
GestureHandler()277         GestureHandler() {
278             super();
279         }
280 
GestureHandler(Handler handler)281         GestureHandler(Handler handler) {
282             super(handler.getLooper());
283         }
284 
285         @Override
handleMessage(Message msg)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
GestureDetector(OnGestureListener listener, Handler handler)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
GestureDetector(OnGestureListener listener)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      */
GestureDetector(Context context, OnGestureListener listener)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      */
GestureDetector(Context context, OnGestureListener listener, Handler handler)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      */
GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused)407     public GestureDetector(Context context, OnGestureListener listener, Handler handler,
408             boolean unused) {
409         this(context, listener, handler);
410     }
411 
init(Context context)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      */
setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener)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      */
setContextClickListener(OnContextClickListener onContextClickListener)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      */
setIsLongpressEnabled(boolean isLongpressEnabled)471     public void setIsLongpressEnabled(boolean isLongpressEnabled) {
472         mIsLongpressEnabled = isLongpressEnabled;
473     }
474 
475     /**
476      * @return true if longpress is enabled, else false.
477      */
isLongpressEnabled()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      */
onTouchEvent(MotionEvent ev)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      */
onGenericMotionEvent(MotionEvent ev)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 
cancel()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 
cancelTaps()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 
isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown)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 
dispatchLongPress()766     private void dispatchLongPress() {
767         mHandler.removeMessages(TAP);
768         mDeferConfirmSingleTap = false;
769         mInLongPress = true;
770         mListener.onLongPress(mCurrentDownEvent);
771     }
772 }
773