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