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
17package android.view;
18
19import android.content.Context;
20import android.os.Handler;
21import 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 */
40public 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) && hadTapMessage &&
561                        isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
562                    // This is a second tap
563                    mIsDoubleTapping = true;
564                    // Give a callback with the first tap of the double-tap
565                    handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
566                    // Give a callback with down event of the double-tap
567                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);
568                } else {
569                    // This is a first tap
570                    mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
571                }
572            }
573
574            mDownFocusX = mLastFocusX = focusX;
575            mDownFocusY = mLastFocusY = focusY;
576            if (mCurrentDownEvent != null) {
577                mCurrentDownEvent.recycle();
578            }
579            mCurrentDownEvent = MotionEvent.obtain(ev);
580            mAlwaysInTapRegion = true;
581            mAlwaysInBiggerTapRegion = true;
582            mStillDown = true;
583            mInLongPress = false;
584            mDeferConfirmSingleTap = false;
585
586            if (mIsLongpressEnabled) {
587                mHandler.removeMessages(LONG_PRESS);
588                mHandler.sendEmptyMessageAtTime(LONG_PRESS,
589                        mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
590            }
591            mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
592                    mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
593            handled |= mListener.onDown(ev);
594            break;
595
596        case MotionEvent.ACTION_MOVE:
597            if (mInLongPress || mInContextClick) {
598                break;
599            }
600            final float scrollX = mLastFocusX - focusX;
601            final float scrollY = mLastFocusY - focusY;
602            if (mIsDoubleTapping) {
603                // Give the move events of the double-tap
604                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
605            } else if (mAlwaysInTapRegion) {
606                final int deltaX = (int) (focusX - mDownFocusX);
607                final int deltaY = (int) (focusY - mDownFocusY);
608                int distance = (deltaX * deltaX) + (deltaY * deltaY);
609                int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
610                if (distance > slopSquare) {
611                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
612                    mLastFocusX = focusX;
613                    mLastFocusY = focusY;
614                    mAlwaysInTapRegion = false;
615                    mHandler.removeMessages(TAP);
616                    mHandler.removeMessages(SHOW_PRESS);
617                    mHandler.removeMessages(LONG_PRESS);
618                }
619                int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
620                if (distance > doubleTapSlopSquare) {
621                    mAlwaysInBiggerTapRegion = false;
622                }
623            } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
624                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
625                mLastFocusX = focusX;
626                mLastFocusY = focusY;
627            }
628            break;
629
630        case MotionEvent.ACTION_UP:
631            mStillDown = false;
632            MotionEvent currentUpEvent = MotionEvent.obtain(ev);
633            if (mIsDoubleTapping) {
634                // Finally, give the up event of the double-tap
635                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
636            } else if (mInLongPress) {
637                mHandler.removeMessages(TAP);
638                mInLongPress = false;
639            } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
640                handled = mListener.onSingleTapUp(ev);
641                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
642                    mDoubleTapListener.onSingleTapConfirmed(ev);
643                }
644            } else if (!mIgnoreNextUpEvent) {
645
646                // A fling must travel the minimum tap distance
647                final VelocityTracker velocityTracker = mVelocityTracker;
648                final int pointerId = ev.getPointerId(0);
649                velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
650                final float velocityY = velocityTracker.getYVelocity(pointerId);
651                final float velocityX = velocityTracker.getXVelocity(pointerId);
652
653                if ((Math.abs(velocityY) > mMinimumFlingVelocity)
654                        || (Math.abs(velocityX) > mMinimumFlingVelocity)){
655                    handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
656                }
657            }
658            if (mPreviousUpEvent != null) {
659                mPreviousUpEvent.recycle();
660            }
661            // Hold the event we obtained above - listeners may have changed the original.
662            mPreviousUpEvent = currentUpEvent;
663            if (mVelocityTracker != null) {
664                // This may have been cleared when we called out to the
665                // application above.
666                mVelocityTracker.recycle();
667                mVelocityTracker = null;
668            }
669            mIsDoubleTapping = false;
670            mDeferConfirmSingleTap = false;
671            mIgnoreNextUpEvent = false;
672            mHandler.removeMessages(SHOW_PRESS);
673            mHandler.removeMessages(LONG_PRESS);
674            break;
675
676        case MotionEvent.ACTION_CANCEL:
677            cancel();
678            break;
679        }
680
681        if (!handled && mInputEventConsistencyVerifier != null) {
682            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
683        }
684        return handled;
685    }
686
687    /**
688     * Analyzes the given generic motion event and if applicable triggers the
689     * appropriate callbacks on the {@link OnGestureListener} supplied.
690     *
691     * @param ev The current motion event.
692     * @return true if the {@link OnGestureListener} consumed the event,
693     *              else false.
694     */
695    public boolean onGenericMotionEvent(MotionEvent ev) {
696        if (mInputEventConsistencyVerifier != null) {
697            mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
698        }
699
700        final int actionButton = ev.getActionButton();
701        switch (ev.getActionMasked()) {
702            case MotionEvent.ACTION_BUTTON_PRESS:
703                if (mContextClickListener != null && !mInContextClick && !mInLongPress
704                        && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
705                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
706                    if (mContextClickListener.onContextClick(ev)) {
707                        mInContextClick = true;
708                        mHandler.removeMessages(LONG_PRESS);
709                        mHandler.removeMessages(TAP);
710                        return true;
711                    }
712                }
713                break;
714
715            case MotionEvent.ACTION_BUTTON_RELEASE:
716                if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
717                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
718                    mInContextClick = false;
719                    mIgnoreNextUpEvent = true;
720                }
721                break;
722        }
723        return false;
724    }
725
726    private void cancel() {
727        mHandler.removeMessages(SHOW_PRESS);
728        mHandler.removeMessages(LONG_PRESS);
729        mHandler.removeMessages(TAP);
730        mVelocityTracker.recycle();
731        mVelocityTracker = null;
732        mIsDoubleTapping = false;
733        mStillDown = false;
734        mAlwaysInTapRegion = false;
735        mAlwaysInBiggerTapRegion = false;
736        mDeferConfirmSingleTap = false;
737        mInLongPress = false;
738        mInContextClick = false;
739        mIgnoreNextUpEvent = false;
740    }
741
742    private void cancelTaps() {
743        mHandler.removeMessages(SHOW_PRESS);
744        mHandler.removeMessages(LONG_PRESS);
745        mHandler.removeMessages(TAP);
746        mIsDoubleTapping = false;
747        mAlwaysInTapRegion = false;
748        mAlwaysInBiggerTapRegion = false;
749        mDeferConfirmSingleTap = false;
750        mInLongPress = false;
751        mInContextClick = false;
752        mIgnoreNextUpEvent = false;
753    }
754
755    private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
756            MotionEvent secondDown) {
757        if (!mAlwaysInBiggerTapRegion) {
758            return false;
759        }
760
761        final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
762        if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
763            return false;
764        }
765
766        int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
767        int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
768        final boolean isGeneratedGesture =
769                (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
770        int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
771        return (deltaX * deltaX + deltaY * deltaY < slopSquare);
772    }
773
774    private void dispatchLongPress() {
775        mHandler.removeMessages(TAP);
776        mDeferConfirmSingleTap = false;
777        mInLongPress = true;
778        mListener.onLongPress(mCurrentDownEvent);
779    }
780}
781