blob: bc2953e0c0fe8587424f7c32947964f42e50a353 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Adam Powell216bccf2010-02-01 15:03:17 -080019import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.os.Handler;
21import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022
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.
Mady Mellor015020e2015-06-05 14:41:22 -070035 * <li>If listening for {@link OnContextClickListener#onContextClick(MotionEvent)}
Mady Mellorb0933442015-05-27 14:15:57 -070036 * you must call {@link #onGenericMotionEvent(MotionEvent)}
37 * in {@link View#onGenericMotionEvent(MotionEvent)}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038 * </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 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700155 * 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
Mady Mellorb0933442015-05-27 14:15:57 -0700157 * {@link View#onGenericMotionEvent(MotionEvent)}.
Mady Mellora6b16452015-04-14 18:03:34 -0700158 */
Mady Mellor015020e2015-06-05 14:41:22 -0700159 public interface OnContextClickListener {
Mady Mellora6b16452015-04-14 18:03:34 -0700160 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700161 * Notified when a context click occurs.
Mady Mellora6b16452015-04-14 18:03:34 -0700162 *
Mady Mellor015020e2015-06-05 14:41:22 -0700163 * @param e The motion event that occurred during the context click.
Mady Mellora6b16452015-04-14 18:03:34 -0700164 * @return true if the event is consumed, else false
165 */
Mady Mellor015020e2015-06-05 14:41:22 -0700166 boolean onContextClick(MotionEvent e);
Mady Mellora6b16452015-04-14 18:03:34 -0700167 }
168
169 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 * 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
Mady Mellor015020e2015-06-05 14:41:22 -0700172 * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
Mady Mellora6b16452015-04-14 18:03:34 -0700173 * but does nothing and return {@code false} for all applicable methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 */
Mady Mellora6b16452015-04-14 18:03:34 -0700175 public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
Mady Mellor015020e2015-06-05 14:41:22 -0700176 OnContextClickListener {
Mady Mellora6b16452015-04-14 18:03:34 -0700177
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 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 }
Mady Mellora6b16452015-04-14 18:03:34 -0700213
Mady Mellor015020e2015-06-05 14:41:22 -0700214 public boolean onContextClick(MotionEvent e) {
Mady Mellora6b16452015-04-14 18:03:34 -0700215 return false;
216 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 }
218
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 private int mTouchSlopSquare;
Gilles Debunne006fa482011-10-27 17:23:36 -0700220 private int mDoubleTapTouchSlopSquare;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 private int mDoubleTapSlopSquare;
222 private int mMinimumFlingVelocity;
Romain Guy4296fc42009-07-06 11:48:52 -0700223 private int mMaximumFlingVelocity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224
225 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
226 private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
Adam Powellaf1785f2013-09-05 13:44:45 -0700228 private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229
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;
Mady Mellor015020e2015-06-05 14:41:22 -0700238 private OnContextClickListener mContextClickListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239
240 private boolean mStillDown;
Adam Powelleca3e602013-02-15 11:26:45 -0800241 private boolean mDeferConfirmSingleTap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 private boolean mInLongPress;
Mady Mellor015020e2015-06-05 14:41:22 -0700243 private boolean mInContextClick;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 private boolean mAlwaysInTapRegion;
245 private boolean mAlwaysInBiggerTapRegion;
Mady Mellorb0933442015-05-27 14:15:57 -0700246 private boolean mIgnoreNextUpEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247
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
Adam Powell05a1e1f2012-08-29 13:54:44 -0700257 private float mLastFocusX;
258 private float mLastFocusY;
259 private float mDownFocusX;
260 private float mDownFocusY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261
262 private boolean mIsLongpressEnabled;
263
264 /**
265 * Determines speed during touch scrolling
266 */
267 private VelocityTracker mVelocityTracker;
268
Jeff Brown21bc5c92011-02-28 18:27:14 -0800269 /**
270 * Consistency verifier for debugging purposes.
271 */
272 private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
273 InputEventConsistencyVerifier.isInstrumentationEnabled() ?
274 new InputEventConsistencyVerifier(this, 0) : null;
275
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 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
Adam Powelleca3e602013-02-15 11:26:45 -0800298 if (mDoubleTapListener != null) {
299 if (!mStillDown) {
300 mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
301 } else {
302 mDeferConfirmSingleTap = true;
303 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 }
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.
Scott Mainc27ea252013-08-30 13:56:05 -0700353 * You may only use this constructor from a {@link android.os.Looper} thread.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 * @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 /**
Scott Mainc27ea252013-08-30 13:56:05 -0700367 * Creates a GestureDetector with the supplied listener that runs deferred events on the
368 * thread associated with the supplied {@link android.os.Handler}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 * @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.
Scott Mainc27ea252013-08-30 13:56:05 -0700374 * @param handler the handler to use for running deferred listener events.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 *
376 * @throws NullPointerException if {@code listener} is null.
377 */
378 public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
Adam Powell05a1e1f2012-08-29 13:54:44 -0700379 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 }
Mady Mellor015020e2015-06-05 14:41:22 -0700388 if (listener instanceof OnContextClickListener) {
389 setContextClickListener((OnContextClickListener) listener);
Mady Mellora6b16452015-04-14 18:03:34 -0700390 }
Adam Powell05a1e1f2012-08-29 13:54:44 -0700391 init(context);
Adam Powell216bccf2010-02-01 15:03:17 -0800392 }
393
394 /**
Scott Mainc27ea252013-08-30 13:56:05 -0700395 * Creates a GestureDetector with the supplied listener that runs deferred events on the
396 * thread associated with the supplied {@link android.os.Handler}.
Adam Powell216bccf2010-02-01 15:03:17 -0800397 * @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.
Scott Mainc27ea252013-08-30 13:56:05 -0700402 * @param handler the handler to use for running deferred listener events.
403 * @param unused currently not used.
Adam Powell216bccf2010-02-01 15:03:17 -0800404 *
405 * @throws NullPointerException if {@code listener} is null.
406 */
407 public GestureDetector(Context context, OnGestureListener listener, Handler handler,
Adam Powell05a1e1f2012-08-29 13:54:44 -0700408 boolean unused) {
409 this(context, listener, handler);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 }
411
Adam Powell05a1e1f2012-08-29 13:54:44 -0700412 private void init(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 if (mListener == null) {
414 throw new NullPointerException("OnGestureListener must not be null");
415 }
416 mIsLongpressEnabled = true;
417
418 // Fallback to support pre-donuts releases
Gilles Debunne006fa482011-10-27 17:23:36 -0700419 int touchSlop, doubleTapSlop, doubleTapTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800420 if (context == null) {
421 //noinspection deprecation
422 touchSlop = ViewConfiguration.getTouchSlop();
Gilles Debunne006fa482011-10-27 17:23:36 -0700423 doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
425 //noinspection deprecation
426 mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
Romain Guy4296fc42009-07-06 11:48:52 -0700427 mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 } else {
429 final ViewConfiguration configuration = ViewConfiguration.get(context);
430 touchSlop = configuration.getScaledTouchSlop();
Gilles Debunne006fa482011-10-27 17:23:36 -0700431 doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800432 doubleTapSlop = configuration.getScaledDoubleTapSlop();
433 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
Romain Guy4296fc42009-07-06 11:48:52 -0700434 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435 }
436 mTouchSlopSquare = touchSlop * touchSlop;
Gilles Debunne006fa482011-10-27 17:23:36 -0700437 mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 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 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700453 * Sets the listener which will be called for context clicks.
Mady Mellora6b16452015-04-14 18:03:34 -0700454 *
Mady Mellor015020e2015-06-05 14:41:22 -0700455 * @param onContextClickListener the listener invoked for all the callbacks, or null to stop
456 * listening for context clicks.
Mady Mellora6b16452015-04-14 18:03:34 -0700457 */
Mady Mellor015020e2015-06-05 14:41:22 -0700458 public void setContextClickListener(OnContextClickListener onContextClickListener) {
459 mContextClickListener = onContextClickListener;
Mady Mellora6b16452015-04-14 18:03:34 -0700460 }
461
462 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800463 * 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) {
Jeff Brown21bc5c92011-02-28 18:27:14 -0800491 if (mInputEventConsistencyVerifier != null) {
492 mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
493 }
494
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800496
497 if (mVelocityTracker == null) {
498 mVelocityTracker = VelocityTracker.obtain();
499 }
500 mVelocityTracker.addMovement(ev);
501
Adam Powellf90165a2012-08-31 11:11:39 -0700502 final boolean pointerUp =
503 (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700504 final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
Dennis Kempinac1b31d2016-11-02 17:02:25 -0700505 final boolean isGeneratedGesture =
506 (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700507
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 boolean handled = false;
521
Adam Powell216bccf2010-02-01 15:03:17 -0800522 switch (action & MotionEvent.ACTION_MASK) {
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800523 case MotionEvent.ACTION_POINTER_DOWN:
524 mDownFocusX = mLastFocusX = focusX;
525 mDownFocusY = mLastFocusY = focusY;
526 // Cancel long press and taps
527 cancelTaps();
528 break;
Adam Powell216bccf2010-02-01 15:03:17 -0800529
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800530 case MotionEvent.ACTION_POINTER_UP:
531 mDownFocusX = mLastFocusX = focusX;
532 mDownFocusY = mLastFocusY = focusY;
Adam Powellcd663592012-09-11 09:52:37 -0700533
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800534 // 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;
Adam Powellcd663592012-09-11 09:52:37 -0700543
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800544 final int id2 = ev.getPointerId(i);
545 final float x = x1 * mVelocityTracker.getXVelocity(id2);
546 final float y = y1 * mVelocityTracker.getYVelocity(id2);
Adam Powellcd663592012-09-11 09:52:37 -0700547
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800548 final float dot = x + y;
549 if (dot < 0) {
550 mVelocityTracker.clear();
551 break;
552 }
553 }
554 break;
555
556 case MotionEvent.ACTION_DOWN:
557 if (mDoubleTapListener != null) {
558 boolean hadTapMessage = mHandler.hasMessages(TAP);
559 if (hadTapMessage) mHandler.removeMessages(TAP);
560 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
561 && hadTapMessage
562 && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
563 // This is a second tap
564 mIsDoubleTapping = true;
565 // Give a callback with the first tap of the double-tap
566 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
567 // Give a callback with down event of the double-tap
568 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
569 } else {
570 // This is a first tap
571 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
572 }
573 }
574
575 mDownFocusX = mLastFocusX = focusX;
576 mDownFocusY = mLastFocusY = focusY;
577 if (mCurrentDownEvent != null) {
578 mCurrentDownEvent.recycle();
579 }
580 mCurrentDownEvent = MotionEvent.obtain(ev);
581 mAlwaysInTapRegion = true;
582 mAlwaysInBiggerTapRegion = true;
583 mStillDown = true;
584 mInLongPress = false;
585 mDeferConfirmSingleTap = false;
586
587 if (mIsLongpressEnabled) {
588 mHandler.removeMessages(LONG_PRESS);
589 mHandler.sendEmptyMessageAtTime(LONG_PRESS,
590 mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
591 }
592 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
593 mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
594 handled |= mListener.onDown(ev);
595 break;
596
597 case MotionEvent.ACTION_MOVE:
598 if (mInLongPress || mInContextClick) {
Adam Powellcd663592012-09-11 09:52:37 -0700599 break;
600 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800601 final float scrollX = mLastFocusX - focusX;
602 final float scrollY = mLastFocusY - focusY;
603 if (mIsDoubleTapping) {
604 // Give the move events of the double-tap
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800605 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800606 } else if (mAlwaysInTapRegion) {
607 final int deltaX = (int) (focusX - mDownFocusX);
608 final int deltaY = (int) (focusY - mDownFocusY);
609 int distance = (deltaX * deltaX) + (deltaY * deltaY);
610 int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
611 if (distance > slopSquare) {
612 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
613 mLastFocusX = focusX;
614 mLastFocusY = focusY;
615 mAlwaysInTapRegion = false;
616 mHandler.removeMessages(TAP);
617 mHandler.removeMessages(SHOW_PRESS);
618 mHandler.removeMessages(LONG_PRESS);
619 }
620 int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
621 if (distance > doubleTapSlopSquare) {
622 mAlwaysInBiggerTapRegion = false;
623 }
624 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800625 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
Adam Powell05a1e1f2012-08-29 13:54:44 -0700626 mLastFocusX = focusX;
627 mLastFocusY = focusY;
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800628 }
629 break;
630
631 case MotionEvent.ACTION_UP:
632 mStillDown = false;
633 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
634 if (mIsDoubleTapping) {
635 // Finally, give the up event of the double-tap
636 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
637 } else if (mInLongPress) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800638 mHandler.removeMessages(TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800639 mInLongPress = false;
640 } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
641 handled = mListener.onSingleTapUp(ev);
642 if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
643 mDoubleTapListener.onSingleTapConfirmed(ev);
644 }
645 } else if (!mIgnoreNextUpEvent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800647 // A fling must travel the minimum tap distance
648 final VelocityTracker velocityTracker = mVelocityTracker;
649 final int pointerId = ev.getPointerId(0);
650 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
651 final float velocityY = velocityTracker.getYVelocity(pointerId);
652 final float velocityX = velocityTracker.getXVelocity(pointerId);
653
654 if ((Math.abs(velocityY) > mMinimumFlingVelocity)
655 || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
656 handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
657 }
Adam Powelleca3e602013-02-15 11:26:45 -0800658 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800659 if (mPreviousUpEvent != null) {
660 mPreviousUpEvent.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800661 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800662 // Hold the event we obtained above - listeners may have changed the original.
663 mPreviousUpEvent = currentUpEvent;
664 if (mVelocityTracker != null) {
665 // This may have been cleared when we called out to the
666 // application above.
667 mVelocityTracker.recycle();
668 mVelocityTracker = null;
669 }
670 mIsDoubleTapping = false;
671 mDeferConfirmSingleTap = false;
672 mIgnoreNextUpEvent = false;
673 mHandler.removeMessages(SHOW_PRESS);
674 mHandler.removeMessages(LONG_PRESS);
675 break;
Jeff Brownbbdc50b2011-04-19 23:46:52 -0700676
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800677 case MotionEvent.ACTION_CANCEL:
678 cancel();
679 break;
Jeff Brownbbdc50b2011-04-19 23:46:52 -0700680 }
681
682 if (!handled && mInputEventConsistencyVerifier != null) {
683 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 }
685 return handled;
686 }
687
Mady Mellorb0933442015-05-27 14:15:57 -0700688 /**
689 * Analyzes the given generic motion event and if applicable triggers the
690 * appropriate callbacks on the {@link OnGestureListener} supplied.
691 *
692 * @param ev The current motion event.
693 * @return true if the {@link OnGestureListener} consumed the event,
694 * else false.
695 */
696 public boolean onGenericMotionEvent(MotionEvent ev) {
697 if (mInputEventConsistencyVerifier != null) {
698 mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
699 }
700
Mady Mellor015020e2015-06-05 14:41:22 -0700701 final int actionButton = ev.getActionButton();
Mady Mellorb0933442015-05-27 14:15:57 -0700702 switch (ev.getActionMasked()) {
703 case MotionEvent.ACTION_BUTTON_PRESS:
Mady Mellor015020e2015-06-05 14:41:22 -0700704 if (mContextClickListener != null && !mInContextClick && !mInLongPress
705 && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
706 || actionButton == MotionEvent.BUTTON_SECONDARY)) {
707 if (mContextClickListener.onContextClick(ev)) {
708 mInContextClick = true;
Mady Mellorb0933442015-05-27 14:15:57 -0700709 mHandler.removeMessages(LONG_PRESS);
710 mHandler.removeMessages(TAP);
711 return true;
712 }
713 }
714 break;
715
716 case MotionEvent.ACTION_BUTTON_RELEASE:
Mady Mellor015020e2015-06-05 14:41:22 -0700717 if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
718 || actionButton == MotionEvent.BUTTON_SECONDARY)) {
719 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700720 mIgnoreNextUpEvent = true;
721 }
722 break;
723 }
724 return false;
725 }
726
Adam Powell216bccf2010-02-01 15:03:17 -0800727 private void cancel() {
728 mHandler.removeMessages(SHOW_PRESS);
729 mHandler.removeMessages(LONG_PRESS);
730 mHandler.removeMessages(TAP);
731 mVelocityTracker.recycle();
732 mVelocityTracker = null;
733 mIsDoubleTapping = false;
734 mStillDown = false;
Adam Powell17921ee2011-06-09 11:39:21 -0700735 mAlwaysInTapRegion = false;
736 mAlwaysInBiggerTapRegion = false;
Adam Powelleca3e602013-02-15 11:26:45 -0800737 mDeferConfirmSingleTap = false;
Mady Mellora6b16452015-04-14 18:03:34 -0700738 mInLongPress = false;
Mady Mellor015020e2015-06-05 14:41:22 -0700739 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700740 mIgnoreNextUpEvent = false;
Adam Powell216bccf2010-02-01 15:03:17 -0800741 }
742
Adam Powell05a1e1f2012-08-29 13:54:44 -0700743 private void cancelTaps() {
744 mHandler.removeMessages(SHOW_PRESS);
745 mHandler.removeMessages(LONG_PRESS);
746 mHandler.removeMessages(TAP);
747 mIsDoubleTapping = false;
748 mAlwaysInTapRegion = false;
749 mAlwaysInBiggerTapRegion = false;
Adam Powelleca3e602013-02-15 11:26:45 -0800750 mDeferConfirmSingleTap = false;
Mady Mellora6b16452015-04-14 18:03:34 -0700751 mInLongPress = false;
Mady Mellor015020e2015-06-05 14:41:22 -0700752 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700753 mIgnoreNextUpEvent = false;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700754 }
755
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800756 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
757 MotionEvent secondDown) {
758 if (!mAlwaysInBiggerTapRegion) {
759 return false;
760 }
761
Adam Powellaf1785f2013-09-05 13:44:45 -0700762 final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
763 if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800764 return false;
765 }
766
767 int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
768 int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
Dennis Kempinac1b31d2016-11-02 17:02:25 -0700769 final boolean isGeneratedGesture =
770 (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
771 int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
772 return (deltaX * deltaX + deltaY * deltaY < slopSquare);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 }
774
775 private void dispatchLongPress() {
776 mHandler.removeMessages(TAP);
Adam Powelleca3e602013-02-15 11:26:45 -0800777 mDeferConfirmSingleTap = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 mInLongPress = true;
779 mListener.onLongPress(mCurrentDownEvent);
780 }
781}