blob: f6c72c4eefbc9bf331413ee0306d2f42441ed770 [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
Muhammad Qureshie2b24322020-01-28 10:54:17 -080019import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
20import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP;
21import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
22import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL;
23import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
24import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
Philip Quinn5be044f2019-01-29 12:47:09 -080025
Artur Satayevad9254c2019-12-10 17:47:54 +000026import android.compat.annotation.UnsupportedAppUsage;
Adam Powell216bccf2010-02-01 15:03:17 -080027import android.content.Context;
Jackal Guo198a2622018-11-19 11:51:31 +080028import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.os.Handler;
30import android.os.Message;
Philip Quinn5be044f2019-01-29 12:47:09 -080031import android.os.SystemClock;
Muhammad Qureshie2b24322020-01-28 10:54:17 -080032
33import com.android.internal.util.FrameworkStatsLog;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034
35/**
36 * Detects various gestures and events using the supplied {@link MotionEvent}s.
37 * The {@link OnGestureListener} callback will notify users when a particular
38 * motion event has occurred. This class should only be used with {@link MotionEvent}s
39 * reported via touch (don't use for trackball events).
40 *
41 * To use this class:
42 * <ul>
43 * <li>Create an instance of the {@code GestureDetector} for your {@link View}
44 * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
45 * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
46 * will be executed when the events occur.
Mady Mellor015020e2015-06-05 14:41:22 -070047 * <li>If listening for {@link OnContextClickListener#onContextClick(MotionEvent)}
Mady Mellorb0933442015-05-27 14:15:57 -070048 * you must call {@link #onGenericMotionEvent(MotionEvent)}
49 * in {@link View#onGenericMotionEvent(MotionEvent)}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 * </ul>
51 */
52public class GestureDetector {
53 /**
54 * The listener that is used to notify when gestures occur.
55 * If you want to listen for all the different gestures then implement
56 * this interface. If you only want to listen for a subset it might
57 * be easier to extend {@link SimpleOnGestureListener}.
58 */
59 public interface OnGestureListener {
60
61 /**
62 * Notified when a tap occurs with the down {@link MotionEvent}
63 * that triggered it. This will be triggered immediately for
64 * every down event. All other events should be preceded by this.
65 *
66 * @param e The down motion event.
67 */
68 boolean onDown(MotionEvent e);
69
70 /**
71 * The user has performed a down {@link MotionEvent} and not performed
72 * a move or up yet. This event is commonly used to provide visual
73 * feedback to the user to let them know that their action has been
74 * recognized i.e. highlight an element.
75 *
76 * @param e The down motion event
77 */
78 void onShowPress(MotionEvent e);
79
80 /**
81 * Notified when a tap occurs with the up {@link MotionEvent}
82 * that triggered it.
83 *
84 * @param e The up motion event that completed the first tap
85 * @return true if the event is consumed, else false
86 */
87 boolean onSingleTapUp(MotionEvent e);
88
89 /**
90 * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
91 * current move {@link MotionEvent}. The distance in x and y is also supplied for
92 * convenience.
93 *
94 * @param e1 The first down motion event that started the scrolling.
95 * @param e2 The move motion event that triggered the current onScroll.
96 * @param distanceX The distance along the X axis that has been scrolled since the last
97 * call to onScroll. This is NOT the distance between {@code e1}
98 * and {@code e2}.
99 * @param distanceY The distance along the Y axis that has been scrolled since the last
100 * call to onScroll. This is NOT the distance between {@code e1}
101 * and {@code e2}.
102 * @return true if the event is consumed, else false
103 */
104 boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
105
106 /**
107 * Notified when a long press occurs with the initial on down {@link MotionEvent}
108 * that trigged it.
109 *
110 * @param e The initial on down motion event that started the longpress.
111 */
112 void onLongPress(MotionEvent e);
113
114 /**
115 * Notified of a fling event when it occurs with the initial on down {@link MotionEvent}
116 * and the matching up {@link MotionEvent}. The calculated velocity is supplied along
117 * the x and y axis in pixels per second.
118 *
119 * @param e1 The first down motion event that started the fling.
120 * @param e2 The move motion event that triggered the current onFling.
121 * @param velocityX The velocity of this fling measured in pixels per second
122 * along the x axis.
123 * @param velocityY The velocity of this fling measured in pixels per second
124 * along the y axis.
125 * @return true if the event is consumed, else false
126 */
127 boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
128 }
129
130 /**
131 * The listener that is used to notify when a double-tap or a confirmed
132 * single-tap occur.
133 */
134 public interface OnDoubleTapListener {
135 /**
136 * Notified when a single-tap occurs.
137 * <p>
138 * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
139 * will only be called after the detector is confident that the user's
140 * first tap is not followed by a second tap leading to a double-tap
141 * gesture.
142 *
143 * @param e The down motion event of the single-tap.
144 * @return true if the event is consumed, else false
145 */
146 boolean onSingleTapConfirmed(MotionEvent e);
147
148 /**
Paul Sowdenafe846d2019-10-31 11:01:56 -0700149 * Notified when a double-tap occurs. Triggered on the down event of second tap.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 *
151 * @param e The down motion event of the first tap of the double-tap.
152 * @return true if the event is consumed, else false
153 */
154 boolean onDoubleTap(MotionEvent e);
155
156 /**
157 * Notified when an event within a double-tap gesture occurs, including
158 * the down, move, and up events.
159 *
160 * @param e The motion event that occurred during the double-tap gesture.
161 * @return true if the event is consumed, else false
162 */
163 boolean onDoubleTapEvent(MotionEvent e);
164 }
165
166 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700167 * The listener that is used to notify when a context click occurs. When listening for a
168 * context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in
Mady Mellorb0933442015-05-27 14:15:57 -0700169 * {@link View#onGenericMotionEvent(MotionEvent)}.
Mady Mellora6b16452015-04-14 18:03:34 -0700170 */
Mady Mellor015020e2015-06-05 14:41:22 -0700171 public interface OnContextClickListener {
Mady Mellora6b16452015-04-14 18:03:34 -0700172 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700173 * Notified when a context click occurs.
Mady Mellora6b16452015-04-14 18:03:34 -0700174 *
Mady Mellor015020e2015-06-05 14:41:22 -0700175 * @param e The motion event that occurred during the context click.
Mady Mellora6b16452015-04-14 18:03:34 -0700176 * @return true if the event is consumed, else false
177 */
Mady Mellor015020e2015-06-05 14:41:22 -0700178 boolean onContextClick(MotionEvent e);
Mady Mellora6b16452015-04-14 18:03:34 -0700179 }
180
181 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 * A convenience class to extend when you only want to listen for a subset
183 * of all the gestures. This implements all methods in the
Mady Mellor015020e2015-06-05 14:41:22 -0700184 * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
Mady Mellora6b16452015-04-14 18:03:34 -0700185 * but does nothing and return {@code false} for all applicable methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 */
Mady Mellora6b16452015-04-14 18:03:34 -0700187 public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
Mady Mellor015020e2015-06-05 14:41:22 -0700188 OnContextClickListener {
Mady Mellora6b16452015-04-14 18:03:34 -0700189
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 public boolean onSingleTapUp(MotionEvent e) {
191 return false;
192 }
193
194 public void onLongPress(MotionEvent e) {
195 }
196
197 public boolean onScroll(MotionEvent e1, MotionEvent e2,
198 float distanceX, float distanceY) {
199 return false;
200 }
201
202 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
203 float velocityY) {
204 return false;
205 }
206
207 public void onShowPress(MotionEvent e) {
208 }
209
210 public boolean onDown(MotionEvent e) {
211 return false;
212 }
213
214 public boolean onDoubleTap(MotionEvent e) {
215 return false;
216 }
217
218 public boolean onDoubleTapEvent(MotionEvent e) {
219 return false;
220 }
221
222 public boolean onSingleTapConfirmed(MotionEvent e) {
223 return false;
224 }
Mady Mellora6b16452015-04-14 18:03:34 -0700225
Mady Mellor015020e2015-06-05 14:41:22 -0700226 public boolean onContextClick(MotionEvent e) {
Mady Mellora6b16452015-04-14 18:03:34 -0700227 return false;
228 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 }
230
Mathew Inwooda570dee2018-08-17 14:56:00 +0100231 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 private int mTouchSlopSquare;
Gilles Debunne006fa482011-10-27 17:23:36 -0700233 private int mDoubleTapTouchSlopSquare;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 private int mDoubleTapSlopSquare;
Philip Quinn1687d422020-01-24 12:49:46 -0800235 private float mAmbiguousGestureMultiplier;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100236 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 private int mMinimumFlingVelocity;
Romain Guy4296fc42009-07-06 11:48:52 -0700238 private int mMaximumFlingVelocity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239
Jackal Guo198a2622018-11-19 11:51:31 +0800240 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
242 private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
Adam Powellaf1785f2013-09-05 13:44:45 -0700244 private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245
246 // constants for Message.what used by GestureHandler below
247 private static final int SHOW_PRESS = 1;
248 private static final int LONG_PRESS = 2;
249 private static final int TAP = 3;
250
251 private final Handler mHandler;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100252 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 private final OnGestureListener mListener;
254 private OnDoubleTapListener mDoubleTapListener;
Mady Mellor015020e2015-06-05 14:41:22 -0700255 private OnContextClickListener mContextClickListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256
257 private boolean mStillDown;
Adam Powelleca3e602013-02-15 11:26:45 -0800258 private boolean mDeferConfirmSingleTap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 private boolean mInLongPress;
Mady Mellor015020e2015-06-05 14:41:22 -0700260 private boolean mInContextClick;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100261 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262 private boolean mAlwaysInTapRegion;
263 private boolean mAlwaysInBiggerTapRegion;
Mady Mellorb0933442015-05-27 14:15:57 -0700264 private boolean mIgnoreNextUpEvent;
Philip Quinn5be044f2019-01-29 12:47:09 -0800265 // Whether a classification has been recorded by statsd for the current event stream. Reset on
266 // ACTION_DOWN.
267 private boolean mHasRecordedClassification;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268
269 private MotionEvent mCurrentDownEvent;
Philip Quinn5be044f2019-01-29 12:47:09 -0800270 private MotionEvent mCurrentMotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 private MotionEvent mPreviousUpEvent;
272
273 /**
274 * True when the user is still touching for the second tap (down, move, and
275 * up events). Can only be true if there is a double tap listener attached.
276 */
277 private boolean mIsDoubleTapping;
278
Adam Powell05a1e1f2012-08-29 13:54:44 -0700279 private float mLastFocusX;
280 private float mLastFocusY;
281 private float mDownFocusX;
282 private float mDownFocusY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283
284 private boolean mIsLongpressEnabled;
285
286 /**
287 * Determines speed during touch scrolling
288 */
289 private VelocityTracker mVelocityTracker;
290
Jeff Brown21bc5c92011-02-28 18:27:14 -0800291 /**
292 * Consistency verifier for debugging purposes.
293 */
294 private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
295 InputEventConsistencyVerifier.isInstrumentationEnabled() ?
296 new InputEventConsistencyVerifier(this, 0) : null;
297
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 private class GestureHandler extends Handler {
299 GestureHandler() {
300 super();
301 }
302
303 GestureHandler(Handler handler) {
304 super(handler.getLooper());
305 }
306
307 @Override
308 public void handleMessage(Message msg) {
309 switch (msg.what) {
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800310 case SHOW_PRESS:
311 mListener.onShowPress(mCurrentDownEvent);
312 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800314 case LONG_PRESS:
Philip Quinn5be044f2019-01-29 12:47:09 -0800315 recordGestureClassification(msg.arg1);
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800316 dispatchLongPress();
317 break;
318
319 case TAP:
320 // If the user's finger is still down, do not count it as a tap
321 if (mDoubleTapListener != null) {
322 if (!mStillDown) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800323 recordGestureClassification(
324 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800325 mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
326 } else {
327 mDeferConfirmSingleTap = true;
328 }
329 }
330 break;
331
332 default:
333 throw new RuntimeException("Unknown message " + msg); //never
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 }
335 }
336 }
337
338 /**
339 * Creates a GestureDetector with the supplied listener.
340 * This variant of the constructor should be used from a non-UI thread
341 * (as it allows specifying the Handler).
342 *
343 * @param listener the listener invoked for all the callbacks, this must
344 * not be null.
345 * @param handler the handler to use
346 *
347 * @throws NullPointerException if either {@code listener} or
348 * {@code handler} is null.
349 *
350 * @deprecated Use {@link #GestureDetector(android.content.Context,
351 * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
352 */
353 @Deprecated
354 public GestureDetector(OnGestureListener listener, Handler handler) {
355 this(null, listener, handler);
356 }
357
358 /**
359 * Creates a GestureDetector with the supplied listener.
360 * You may only use this constructor from a UI thread (this is the usual situation).
361 * @see android.os.Handler#Handler()
362 *
363 * @param listener the listener invoked for all the callbacks, this must
364 * not be null.
365 *
366 * @throws NullPointerException if {@code listener} is null.
367 *
368 * @deprecated Use {@link #GestureDetector(android.content.Context,
369 * android.view.GestureDetector.OnGestureListener)} instead.
370 */
371 @Deprecated
372 public GestureDetector(OnGestureListener listener) {
373 this(null, listener, null);
374 }
375
376 /**
377 * Creates a GestureDetector with the supplied listener.
Scott Mainc27ea252013-08-30 13:56:05 -0700378 * You may only use this constructor from a {@link android.os.Looper} thread.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 * @see android.os.Handler#Handler()
380 *
381 * @param context the application's context
382 * @param listener the listener invoked for all the callbacks, this must
Paul Sowdenafe846d2019-10-31 11:01:56 -0700383 * not be null. If the listener implements the {@link OnDoubleTapListener} or
384 * {@link OnContextClickListener} then it will also be set as the listener for
385 * these callbacks (for example when using the {@link SimpleOnGestureListener}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 *
387 * @throws NullPointerException if {@code listener} is null.
388 */
389 public GestureDetector(Context context, OnGestureListener listener) {
390 this(context, listener, null);
391 }
392
393 /**
Scott Mainc27ea252013-08-30 13:56:05 -0700394 * Creates a GestureDetector with the supplied listener that runs deferred events on the
395 * thread associated with the supplied {@link android.os.Handler}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 * @see android.os.Handler#Handler()
397 *
398 * @param context the application's context
399 * @param listener the listener invoked for all the callbacks, this must
Paul Sowdenafe846d2019-10-31 11:01:56 -0700400 * not be null. If the listener implements the {@link OnDoubleTapListener} or
401 * {@link OnContextClickListener} then it will also be set as the listener for
402 * these callbacks (for example when using the {@link SimpleOnGestureListener}).
Scott Mainc27ea252013-08-30 13:56:05 -0700403 * @param handler the handler to use for running deferred listener events.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -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 if (handler != null) {
409 mHandler = new GestureHandler(handler);
410 } else {
411 mHandler = new GestureHandler();
412 }
413 mListener = listener;
414 if (listener instanceof OnDoubleTapListener) {
415 setOnDoubleTapListener((OnDoubleTapListener) listener);
416 }
Mady Mellor015020e2015-06-05 14:41:22 -0700417 if (listener instanceof OnContextClickListener) {
418 setContextClickListener((OnContextClickListener) listener);
Mady Mellora6b16452015-04-14 18:03:34 -0700419 }
Adam Powell05a1e1f2012-08-29 13:54:44 -0700420 init(context);
Adam Powell216bccf2010-02-01 15:03:17 -0800421 }
422
423 /**
Scott Mainc27ea252013-08-30 13:56:05 -0700424 * Creates a GestureDetector with the supplied listener that runs deferred events on the
425 * thread associated with the supplied {@link android.os.Handler}.
Adam Powell216bccf2010-02-01 15:03:17 -0800426 * @see android.os.Handler#Handler()
427 *
428 * @param context the application's context
429 * @param listener the listener invoked for all the callbacks, this must
430 * not be null.
Scott Mainc27ea252013-08-30 13:56:05 -0700431 * @param handler the handler to use for running deferred listener events.
432 * @param unused currently not used.
Adam Powell216bccf2010-02-01 15:03:17 -0800433 *
434 * @throws NullPointerException if {@code listener} is null.
435 */
436 public GestureDetector(Context context, OnGestureListener listener, Handler handler,
Adam Powell05a1e1f2012-08-29 13:54:44 -0700437 boolean unused) {
438 this(context, listener, handler);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 }
440
Adam Powell05a1e1f2012-08-29 13:54:44 -0700441 private void init(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 if (mListener == null) {
443 throw new NullPointerException("OnGestureListener must not be null");
444 }
445 mIsLongpressEnabled = true;
446
447 // Fallback to support pre-donuts releases
Gilles Debunne006fa482011-10-27 17:23:36 -0700448 int touchSlop, doubleTapSlop, doubleTapTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 if (context == null) {
450 //noinspection deprecation
451 touchSlop = ViewConfiguration.getTouchSlop();
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800452 doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
454 //noinspection deprecation
455 mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
Romain Guy4296fc42009-07-06 11:48:52 -0700456 mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
Philip Quinn1687d422020-01-24 12:49:46 -0800457 mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 } else {
459 final ViewConfiguration configuration = ViewConfiguration.get(context);
460 touchSlop = configuration.getScaledTouchSlop();
Gilles Debunne006fa482011-10-27 17:23:36 -0700461 doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 doubleTapSlop = configuration.getScaledDoubleTapSlop();
463 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
Romain Guy4296fc42009-07-06 11:48:52 -0700464 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
Philip Quinn1687d422020-01-24 12:49:46 -0800465 mAmbiguousGestureMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800466 }
467 mTouchSlopSquare = touchSlop * touchSlop;
Gilles Debunne006fa482011-10-27 17:23:36 -0700468 mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
470 }
471
472 /**
473 * Sets the listener which will be called for double-tap and related
474 * gestures.
475 *
476 * @param onDoubleTapListener the listener invoked for all the callbacks, or
477 * null to stop listening for double-tap gestures.
478 */
479 public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
480 mDoubleTapListener = onDoubleTapListener;
481 }
482
483 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700484 * Sets the listener which will be called for context clicks.
Mady Mellora6b16452015-04-14 18:03:34 -0700485 *
Mady Mellor015020e2015-06-05 14:41:22 -0700486 * @param onContextClickListener the listener invoked for all the callbacks, or null to stop
487 * listening for context clicks.
Mady Mellora6b16452015-04-14 18:03:34 -0700488 */
Mady Mellor015020e2015-06-05 14:41:22 -0700489 public void setContextClickListener(OnContextClickListener onContextClickListener) {
490 mContextClickListener = onContextClickListener;
Mady Mellora6b16452015-04-14 18:03:34 -0700491 }
492
493 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 * Set whether longpress is enabled, if this is enabled when a user
495 * presses and holds down you get a longpress event and nothing further.
496 * If it's disabled the user can press and hold down and then later
497 * moved their finger and you will get scroll events. By default
498 * longpress is enabled.
499 *
500 * @param isLongpressEnabled whether longpress should be enabled.
501 */
502 public void setIsLongpressEnabled(boolean isLongpressEnabled) {
503 mIsLongpressEnabled = isLongpressEnabled;
504 }
505
506 /**
507 * @return true if longpress is enabled, else false.
508 */
509 public boolean isLongpressEnabled() {
510 return mIsLongpressEnabled;
511 }
512
513 /**
514 * Analyzes the given motion event and if applicable triggers the
515 * appropriate callbacks on the {@link OnGestureListener} supplied.
516 *
517 * @param ev The current motion event.
518 * @return true if the {@link OnGestureListener} consumed the event,
519 * else false.
520 */
521 public boolean onTouchEvent(MotionEvent ev) {
Jeff Brown21bc5c92011-02-28 18:27:14 -0800522 if (mInputEventConsistencyVerifier != null) {
523 mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
524 }
525
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527
Philip Quinn5be044f2019-01-29 12:47:09 -0800528 if (mCurrentMotionEvent != null) {
529 mCurrentMotionEvent.recycle();
530 }
531 mCurrentMotionEvent = MotionEvent.obtain(ev);
532
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 if (mVelocityTracker == null) {
534 mVelocityTracker = VelocityTracker.obtain();
535 }
536 mVelocityTracker.addMovement(ev);
537
Adam Powellf90165a2012-08-31 11:11:39 -0700538 final boolean pointerUp =
539 (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700540 final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
Dennis Kempinac1b31d2016-11-02 17:02:25 -0700541 final boolean isGeneratedGesture =
542 (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700543
544 // Determine focal point
545 float sumX = 0, sumY = 0;
546 final int count = ev.getPointerCount();
547 for (int i = 0; i < count; i++) {
548 if (skipIndex == i) continue;
549 sumX += ev.getX(i);
550 sumY += ev.getY(i);
551 }
552 final int div = pointerUp ? count - 1 : count;
553 final float focusX = sumX / div;
554 final float focusY = sumY / div;
555
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800556 boolean handled = false;
557
Adam Powell216bccf2010-02-01 15:03:17 -0800558 switch (action & MotionEvent.ACTION_MASK) {
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800559 case MotionEvent.ACTION_POINTER_DOWN:
560 mDownFocusX = mLastFocusX = focusX;
561 mDownFocusY = mLastFocusY = focusY;
562 // Cancel long press and taps
563 cancelTaps();
564 break;
Adam Powell216bccf2010-02-01 15:03:17 -0800565
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800566 case MotionEvent.ACTION_POINTER_UP:
567 mDownFocusX = mLastFocusX = focusX;
568 mDownFocusY = mLastFocusY = focusY;
Adam Powellcd663592012-09-11 09:52:37 -0700569
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800570 // Check the dot product of current velocities.
571 // If the pointer that left was opposing another velocity vector, clear.
572 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
573 final int upIndex = ev.getActionIndex();
574 final int id1 = ev.getPointerId(upIndex);
575 final float x1 = mVelocityTracker.getXVelocity(id1);
576 final float y1 = mVelocityTracker.getYVelocity(id1);
577 for (int i = 0; i < count; i++) {
578 if (i == upIndex) continue;
Adam Powellcd663592012-09-11 09:52:37 -0700579
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800580 final int id2 = ev.getPointerId(i);
581 final float x = x1 * mVelocityTracker.getXVelocity(id2);
582 final float y = y1 * mVelocityTracker.getYVelocity(id2);
Adam Powellcd663592012-09-11 09:52:37 -0700583
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800584 final float dot = x + y;
585 if (dot < 0) {
586 mVelocityTracker.clear();
587 break;
588 }
589 }
590 break;
591
592 case MotionEvent.ACTION_DOWN:
593 if (mDoubleTapListener != null) {
594 boolean hadTapMessage = mHandler.hasMessages(TAP);
595 if (hadTapMessage) mHandler.removeMessages(TAP);
596 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
597 && hadTapMessage
598 && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
599 // This is a second tap
600 mIsDoubleTapping = true;
Philip Quinn5be044f2019-01-29 12:47:09 -0800601 recordGestureClassification(
602 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800603 // Give a callback with the first tap of the double-tap
604 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
605 // Give a callback with down event of the double-tap
606 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
607 } else {
608 // This is a first tap
609 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
610 }
611 }
612
613 mDownFocusX = mLastFocusX = focusX;
614 mDownFocusY = mLastFocusY = focusY;
615 if (mCurrentDownEvent != null) {
616 mCurrentDownEvent.recycle();
617 }
618 mCurrentDownEvent = MotionEvent.obtain(ev);
619 mAlwaysInTapRegion = true;
620 mAlwaysInBiggerTapRegion = true;
621 mStillDown = true;
622 mInLongPress = false;
623 mDeferConfirmSingleTap = false;
Philip Quinn5be044f2019-01-29 12:47:09 -0800624 mHasRecordedClassification = false;
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800625
626 if (mIsLongpressEnabled) {
627 mHandler.removeMessages(LONG_PRESS);
Philip Quinn5be044f2019-01-29 12:47:09 -0800628 mHandler.sendMessageAtTime(
629 mHandler.obtainMessage(
630 LONG_PRESS,
631 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
632 0 /* arg2 */),
633 mCurrentDownEvent.getDownTime()
634 + ViewConfiguration.getLongPressTimeout());
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800635 }
636 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
637 mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
638 handled |= mListener.onDown(ev);
639 break;
640
641 case MotionEvent.ACTION_MOVE:
642 if (mInLongPress || mInContextClick) {
Adam Powellcd663592012-09-11 09:52:37 -0700643 break;
644 }
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800645
646 final int motionClassification = ev.getClassification();
647 final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
648
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800649 final float scrollX = mLastFocusX - focusX;
650 final float scrollY = mLastFocusY - focusY;
651 if (mIsDoubleTapping) {
652 // Give the move events of the double-tap
Philip Quinn5be044f2019-01-29 12:47:09 -0800653 recordGestureClassification(
654 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800655 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800656 } else if (mAlwaysInTapRegion) {
657 final int deltaX = (int) (focusX - mDownFocusX);
658 final int deltaY = (int) (focusY - mDownFocusY);
659 int distance = (deltaX * deltaX) + (deltaY * deltaY);
660 int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800661
662 final boolean ambiguousGesture =
663 motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
664 final boolean shouldInhibitDefaultAction =
665 hasPendingLongPress && ambiguousGesture;
666 if (shouldInhibitDefaultAction) {
667 // Inhibit default long press
668 if (distance > slopSquare) {
669 // The default action here is to remove long press. But if the touch
670 // slop below gets increased, and we never exceed the modified touch
671 // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
672 // will happen in response to user input. To prevent this,
673 // reschedule long press with a modified timeout.
674 mHandler.removeMessages(LONG_PRESS);
Siarhei Vishniakou8b047dd2019-01-24 16:04:13 -0800675 final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
Philip Quinn5be044f2019-01-29 12:47:09 -0800676 mHandler.sendMessageAtTime(
677 mHandler.obtainMessage(
678 LONG_PRESS,
679 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
680 0 /* arg2 */),
Philip Quinn1687d422020-01-24 12:49:46 -0800681 ev.getDownTime()
682 + (long) (longPressTimeout * mAmbiguousGestureMultiplier));
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800683 }
684 // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
685 // until the gesture is resolved.
686 // However, for safety, simply increase the touch slop in case the
687 // classification is erroneous. Since the value is squared, multiply twice.
Philip Quinn1687d422020-01-24 12:49:46 -0800688 slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier;
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800689 }
690
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800691 if (distance > slopSquare) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800692 recordGestureClassification(
693 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800694 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
695 mLastFocusX = focusX;
696 mLastFocusY = focusY;
697 mAlwaysInTapRegion = false;
698 mHandler.removeMessages(TAP);
699 mHandler.removeMessages(SHOW_PRESS);
700 mHandler.removeMessages(LONG_PRESS);
701 }
702 int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
703 if (distance > doubleTapSlopSquare) {
704 mAlwaysInBiggerTapRegion = false;
705 }
706 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800707 recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
Adam Powell05a1e1f2012-08-29 13:54:44 -0700709 mLastFocusX = focusX;
710 mLastFocusY = focusY;
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800711 }
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800712 final boolean deepPress =
713 motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
714 if (deepPress && hasPendingLongPress) {
715 mHandler.removeMessages(LONG_PRESS);
Philip Quinn5be044f2019-01-29 12:47:09 -0800716 mHandler.sendMessage(
717 mHandler.obtainMessage(
718 LONG_PRESS,
719 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,
720 0 /* arg2 */));
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800721 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800722 break;
723
724 case MotionEvent.ACTION_UP:
725 mStillDown = false;
726 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
727 if (mIsDoubleTapping) {
728 // Finally, give the up event of the double-tap
Philip Quinn5be044f2019-01-29 12:47:09 -0800729 recordGestureClassification(
730 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800731 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
732 } else if (mInLongPress) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 mHandler.removeMessages(TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800734 mInLongPress = false;
735 } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800736 recordGestureClassification(
737 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800738 handled = mListener.onSingleTapUp(ev);
739 if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
740 mDoubleTapListener.onSingleTapConfirmed(ev);
741 }
742 } else if (!mIgnoreNextUpEvent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800743
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800744 // A fling must travel the minimum tap distance
745 final VelocityTracker velocityTracker = mVelocityTracker;
746 final int pointerId = ev.getPointerId(0);
747 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
748 final float velocityY = velocityTracker.getYVelocity(pointerId);
749 final float velocityX = velocityTracker.getXVelocity(pointerId);
750
751 if ((Math.abs(velocityY) > mMinimumFlingVelocity)
752 || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
753 handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
754 }
Adam Powelleca3e602013-02-15 11:26:45 -0800755 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800756 if (mPreviousUpEvent != null) {
757 mPreviousUpEvent.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800758 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800759 // Hold the event we obtained above - listeners may have changed the original.
760 mPreviousUpEvent = currentUpEvent;
761 if (mVelocityTracker != null) {
762 // This may have been cleared when we called out to the
763 // application above.
764 mVelocityTracker.recycle();
765 mVelocityTracker = null;
766 }
767 mIsDoubleTapping = false;
768 mDeferConfirmSingleTap = false;
769 mIgnoreNextUpEvent = false;
770 mHandler.removeMessages(SHOW_PRESS);
771 mHandler.removeMessages(LONG_PRESS);
772 break;
Jeff Brownbbdc50b2011-04-19 23:46:52 -0700773
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800774 case MotionEvent.ACTION_CANCEL:
775 cancel();
776 break;
Jeff Brownbbdc50b2011-04-19 23:46:52 -0700777 }
778
779 if (!handled && mInputEventConsistencyVerifier != null) {
780 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800781 }
782 return handled;
783 }
784
Mady Mellorb0933442015-05-27 14:15:57 -0700785 /**
786 * Analyzes the given generic motion event and if applicable triggers the
787 * appropriate callbacks on the {@link OnGestureListener} supplied.
788 *
789 * @param ev The current motion event.
790 * @return true if the {@link OnGestureListener} consumed the event,
791 * else false.
792 */
793 public boolean onGenericMotionEvent(MotionEvent ev) {
794 if (mInputEventConsistencyVerifier != null) {
795 mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
796 }
797
Mady Mellor015020e2015-06-05 14:41:22 -0700798 final int actionButton = ev.getActionButton();
Mady Mellorb0933442015-05-27 14:15:57 -0700799 switch (ev.getActionMasked()) {
800 case MotionEvent.ACTION_BUTTON_PRESS:
Mady Mellor015020e2015-06-05 14:41:22 -0700801 if (mContextClickListener != null && !mInContextClick && !mInLongPress
802 && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
803 || actionButton == MotionEvent.BUTTON_SECONDARY)) {
804 if (mContextClickListener.onContextClick(ev)) {
805 mInContextClick = true;
Mady Mellorb0933442015-05-27 14:15:57 -0700806 mHandler.removeMessages(LONG_PRESS);
807 mHandler.removeMessages(TAP);
808 return true;
809 }
810 }
811 break;
812
813 case MotionEvent.ACTION_BUTTON_RELEASE:
Mady Mellor015020e2015-06-05 14:41:22 -0700814 if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
815 || actionButton == MotionEvent.BUTTON_SECONDARY)) {
816 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700817 mIgnoreNextUpEvent = true;
818 }
819 break;
820 }
821 return false;
822 }
823
Adam Powell216bccf2010-02-01 15:03:17 -0800824 private void cancel() {
825 mHandler.removeMessages(SHOW_PRESS);
826 mHandler.removeMessages(LONG_PRESS);
827 mHandler.removeMessages(TAP);
828 mVelocityTracker.recycle();
829 mVelocityTracker = null;
830 mIsDoubleTapping = false;
831 mStillDown = false;
Adam Powell17921ee2011-06-09 11:39:21 -0700832 mAlwaysInTapRegion = false;
833 mAlwaysInBiggerTapRegion = false;
Adam Powelleca3e602013-02-15 11:26:45 -0800834 mDeferConfirmSingleTap = false;
Mady Mellora6b16452015-04-14 18:03:34 -0700835 mInLongPress = false;
Mady Mellor015020e2015-06-05 14:41:22 -0700836 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700837 mIgnoreNextUpEvent = false;
Adam Powell216bccf2010-02-01 15:03:17 -0800838 }
839
Adam Powell05a1e1f2012-08-29 13:54:44 -0700840 private void cancelTaps() {
841 mHandler.removeMessages(SHOW_PRESS);
842 mHandler.removeMessages(LONG_PRESS);
843 mHandler.removeMessages(TAP);
844 mIsDoubleTapping = false;
845 mAlwaysInTapRegion = false;
846 mAlwaysInBiggerTapRegion = false;
Adam Powelleca3e602013-02-15 11:26:45 -0800847 mDeferConfirmSingleTap = false;
Mady Mellora6b16452015-04-14 18:03:34 -0700848 mInLongPress = false;
Mady Mellor015020e2015-06-05 14:41:22 -0700849 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700850 mIgnoreNextUpEvent = false;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700851 }
852
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800853 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
854 MotionEvent secondDown) {
855 if (!mAlwaysInBiggerTapRegion) {
856 return false;
857 }
858
Adam Powellaf1785f2013-09-05 13:44:45 -0700859 final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
860 if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800861 return false;
862 }
863
864 int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
865 int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
Dennis Kempinac1b31d2016-11-02 17:02:25 -0700866 final boolean isGeneratedGesture =
867 (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
868 int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
869 return (deltaX * deltaX + deltaY * deltaY < slopSquare);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870 }
871
872 private void dispatchLongPress() {
873 mHandler.removeMessages(TAP);
Adam Powelleca3e602013-02-15 11:26:45 -0800874 mDeferConfirmSingleTap = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 mInLongPress = true;
876 mListener.onLongPress(mCurrentDownEvent);
877 }
Philip Quinn5be044f2019-01-29 12:47:09 -0800878
879 private void recordGestureClassification(int classification) {
880 if (mHasRecordedClassification
881 || classification
882 == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
883 // Only record the first classification for an event stream.
884 return;
885 }
886 if (mCurrentDownEvent == null || mCurrentMotionEvent == null) {
887 // If the complete event stream wasn't seen, don't record anything.
888 mHasRecordedClassification = true;
889 return;
890 }
Muhammad Qureshie2b24322020-01-28 10:54:17 -0800891 FrameworkStatsLog.write(
892 FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED,
Philip Quinn5be044f2019-01-29 12:47:09 -0800893 getClass().getName(),
894 classification,
895 (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()),
896 (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(),
897 mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY()));
898 mHasRecordedClassification = true;
899 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900}