blob: d59ee92dba7edb91db919385754154784d63d6ac [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
Philip Quinn5be044f2019-01-29 12:47:09 -080019import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
20import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP;
21import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
22import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL;
23import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
24import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
25
Mathew Inwooda570dee2018-08-17 14:56:00 +010026import android.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;
32import android.util.StatsLog;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033
34/**
35 * Detects various gestures and events using the supplied {@link MotionEvent}s.
36 * The {@link OnGestureListener} callback will notify users when a particular
37 * motion event has occurred. This class should only be used with {@link MotionEvent}s
38 * reported via touch (don't use for trackball events).
39 *
40 * To use this class:
41 * <ul>
42 * <li>Create an instance of the {@code GestureDetector} for your {@link View}
43 * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
44 * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback
45 * will be executed when the events occur.
Mady Mellor015020e2015-06-05 14:41:22 -070046 * <li>If listening for {@link OnContextClickListener#onContextClick(MotionEvent)}
Mady Mellorb0933442015-05-27 14:15:57 -070047 * you must call {@link #onGenericMotionEvent(MotionEvent)}
48 * in {@link View#onGenericMotionEvent(MotionEvent)}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 * </ul>
50 */
51public class GestureDetector {
52 /**
53 * The listener that is used to notify when gestures occur.
54 * If you want to listen for all the different gestures then implement
55 * this interface. If you only want to listen for a subset it might
56 * be easier to extend {@link SimpleOnGestureListener}.
57 */
58 public interface OnGestureListener {
59
60 /**
61 * Notified when a tap occurs with the down {@link MotionEvent}
62 * that triggered it. This will be triggered immediately for
63 * every down event. All other events should be preceded by this.
64 *
65 * @param e The down motion event.
66 */
67 boolean onDown(MotionEvent e);
68
69 /**
70 * The user has performed a down {@link MotionEvent} and not performed
71 * a move or up yet. This event is commonly used to provide visual
72 * feedback to the user to let them know that their action has been
73 * recognized i.e. highlight an element.
74 *
75 * @param e The down motion event
76 */
77 void onShowPress(MotionEvent e);
78
79 /**
80 * Notified when a tap occurs with the up {@link MotionEvent}
81 * that triggered it.
82 *
83 * @param e The up motion event that completed the first tap
84 * @return true if the event is consumed, else false
85 */
86 boolean onSingleTapUp(MotionEvent e);
87
88 /**
89 * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the
90 * current move {@link MotionEvent}. The distance in x and y is also supplied for
91 * convenience.
92 *
93 * @param e1 The first down motion event that started the scrolling.
94 * @param e2 The move motion event that triggered the current onScroll.
95 * @param distanceX The distance along the X axis that has been scrolled since the last
96 * call to onScroll. This is NOT the distance between {@code e1}
97 * and {@code e2}.
98 * @param distanceY The distance along the Y axis that has been scrolled since the last
99 * call to onScroll. This is NOT the distance between {@code e1}
100 * and {@code e2}.
101 * @return true if the event is consumed, else false
102 */
103 boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
104
105 /**
106 * Notified when a long press occurs with the initial on down {@link MotionEvent}
107 * that trigged it.
108 *
109 * @param e The initial on down motion event that started the longpress.
110 */
111 void onLongPress(MotionEvent e);
112
113 /**
114 * Notified of a fling event when it occurs with the initial on down {@link MotionEvent}
115 * and the matching up {@link MotionEvent}. The calculated velocity is supplied along
116 * the x and y axis in pixels per second.
117 *
118 * @param e1 The first down motion event that started the fling.
119 * @param e2 The move motion event that triggered the current onFling.
120 * @param velocityX The velocity of this fling measured in pixels per second
121 * along the x axis.
122 * @param velocityY The velocity of this fling measured in pixels per second
123 * along the y axis.
124 * @return true if the event is consumed, else false
125 */
126 boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
127 }
128
129 /**
130 * The listener that is used to notify when a double-tap or a confirmed
131 * single-tap occur.
132 */
133 public interface OnDoubleTapListener {
134 /**
135 * Notified when a single-tap occurs.
136 * <p>
137 * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
138 * will only be called after the detector is confident that the user's
139 * first tap is not followed by a second tap leading to a double-tap
140 * gesture.
141 *
142 * @param e The down motion event of the single-tap.
143 * @return true if the event is consumed, else false
144 */
145 boolean onSingleTapConfirmed(MotionEvent e);
146
147 /**
Paul Sowdenafe846d2019-10-31 11:01:56 -0700148 * Notified when a double-tap occurs. Triggered on the down event of second tap.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 *
150 * @param e The down motion event of the first tap of the double-tap.
151 * @return true if the event is consumed, else false
152 */
153 boolean onDoubleTap(MotionEvent e);
154
155 /**
156 * Notified when an event within a double-tap gesture occurs, including
157 * the down, move, and up events.
158 *
159 * @param e The motion event that occurred during the double-tap gesture.
160 * @return true if the event is consumed, else false
161 */
162 boolean onDoubleTapEvent(MotionEvent e);
163 }
164
165 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700166 * The listener that is used to notify when a context click occurs. When listening for a
167 * context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in
Mady Mellorb0933442015-05-27 14:15:57 -0700168 * {@link View#onGenericMotionEvent(MotionEvent)}.
Mady Mellora6b16452015-04-14 18:03:34 -0700169 */
Mady Mellor015020e2015-06-05 14:41:22 -0700170 public interface OnContextClickListener {
Mady Mellora6b16452015-04-14 18:03:34 -0700171 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700172 * Notified when a context click occurs.
Mady Mellora6b16452015-04-14 18:03:34 -0700173 *
Mady Mellor015020e2015-06-05 14:41:22 -0700174 * @param e The motion event that occurred during the context click.
Mady Mellora6b16452015-04-14 18:03:34 -0700175 * @return true if the event is consumed, else false
176 */
Mady Mellor015020e2015-06-05 14:41:22 -0700177 boolean onContextClick(MotionEvent e);
Mady Mellora6b16452015-04-14 18:03:34 -0700178 }
179
180 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 * A convenience class to extend when you only want to listen for a subset
182 * of all the gestures. This implements all methods in the
Mady Mellor015020e2015-06-05 14:41:22 -0700183 * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
Mady Mellora6b16452015-04-14 18:03:34 -0700184 * but does nothing and return {@code false} for all applicable methods.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 */
Mady Mellora6b16452015-04-14 18:03:34 -0700186 public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
Mady Mellor015020e2015-06-05 14:41:22 -0700187 OnContextClickListener {
Mady Mellora6b16452015-04-14 18:03:34 -0700188
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 public boolean onSingleTapUp(MotionEvent e) {
190 return false;
191 }
192
193 public void onLongPress(MotionEvent e) {
194 }
195
196 public boolean onScroll(MotionEvent e1, MotionEvent e2,
197 float distanceX, float distanceY) {
198 return false;
199 }
200
201 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
202 float velocityY) {
203 return false;
204 }
205
206 public void onShowPress(MotionEvent e) {
207 }
208
209 public boolean onDown(MotionEvent e) {
210 return false;
211 }
212
213 public boolean onDoubleTap(MotionEvent e) {
214 return false;
215 }
216
217 public boolean onDoubleTapEvent(MotionEvent e) {
218 return false;
219 }
220
221 public boolean onSingleTapConfirmed(MotionEvent e) {
222 return false;
223 }
Mady Mellora6b16452015-04-14 18:03:34 -0700224
Mady Mellor015020e2015-06-05 14:41:22 -0700225 public boolean onContextClick(MotionEvent e) {
Mady Mellora6b16452015-04-14 18:03:34 -0700226 return false;
227 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 }
229
Mathew Inwooda570dee2018-08-17 14:56:00 +0100230 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 private int mTouchSlopSquare;
Gilles Debunne006fa482011-10-27 17:23:36 -0700232 private int mDoubleTapTouchSlopSquare;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 private int mDoubleTapSlopSquare;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100234 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 private int mMinimumFlingVelocity;
Romain Guy4296fc42009-07-06 11:48:52 -0700236 private int mMaximumFlingVelocity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237
Jackal Guo198a2622018-11-19 11:51:31 +0800238 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
240 private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
Adam Powellaf1785f2013-09-05 13:44:45 -0700242 private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243
244 // constants for Message.what used by GestureHandler below
245 private static final int SHOW_PRESS = 1;
246 private static final int LONG_PRESS = 2;
247 private static final int TAP = 3;
248
249 private final Handler mHandler;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100250 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 private final OnGestureListener mListener;
252 private OnDoubleTapListener mDoubleTapListener;
Mady Mellor015020e2015-06-05 14:41:22 -0700253 private OnContextClickListener mContextClickListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254
255 private boolean mStillDown;
Adam Powelleca3e602013-02-15 11:26:45 -0800256 private boolean mDeferConfirmSingleTap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 private boolean mInLongPress;
Mady Mellor015020e2015-06-05 14:41:22 -0700258 private boolean mInContextClick;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100259 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 private boolean mAlwaysInTapRegion;
261 private boolean mAlwaysInBiggerTapRegion;
Mady Mellorb0933442015-05-27 14:15:57 -0700262 private boolean mIgnoreNextUpEvent;
Philip Quinn5be044f2019-01-29 12:47:09 -0800263 // Whether a classification has been recorded by statsd for the current event stream. Reset on
264 // ACTION_DOWN.
265 private boolean mHasRecordedClassification;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266
267 private MotionEvent mCurrentDownEvent;
Philip Quinn5be044f2019-01-29 12:47:09 -0800268 private MotionEvent mCurrentMotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 private MotionEvent mPreviousUpEvent;
270
271 /**
272 * True when the user is still touching for the second tap (down, move, and
273 * up events). Can only be true if there is a double tap listener attached.
274 */
275 private boolean mIsDoubleTapping;
276
Adam Powell05a1e1f2012-08-29 13:54:44 -0700277 private float mLastFocusX;
278 private float mLastFocusY;
279 private float mDownFocusX;
280 private float mDownFocusY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281
282 private boolean mIsLongpressEnabled;
283
284 /**
285 * Determines speed during touch scrolling
286 */
287 private VelocityTracker mVelocityTracker;
288
Jeff Brown21bc5c92011-02-28 18:27:14 -0800289 /**
290 * Consistency verifier for debugging purposes.
291 */
292 private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
293 InputEventConsistencyVerifier.isInstrumentationEnabled() ?
294 new InputEventConsistencyVerifier(this, 0) : null;
295
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 private class GestureHandler extends Handler {
297 GestureHandler() {
298 super();
299 }
300
301 GestureHandler(Handler handler) {
302 super(handler.getLooper());
303 }
304
305 @Override
306 public void handleMessage(Message msg) {
307 switch (msg.what) {
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800308 case SHOW_PRESS:
309 mListener.onShowPress(mCurrentDownEvent);
310 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800312 case LONG_PRESS:
Philip Quinn5be044f2019-01-29 12:47:09 -0800313 recordGestureClassification(msg.arg1);
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800314 dispatchLongPress();
315 break;
316
317 case TAP:
318 // If the user's finger is still down, do not count it as a tap
319 if (mDoubleTapListener != null) {
320 if (!mStillDown) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800321 recordGestureClassification(
322 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800323 mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
324 } else {
325 mDeferConfirmSingleTap = true;
326 }
327 }
328 break;
329
330 default:
331 throw new RuntimeException("Unknown message " + msg); //never
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 }
333 }
334 }
335
336 /**
337 * Creates a GestureDetector with the supplied listener.
338 * This variant of the constructor should be used from a non-UI thread
339 * (as it allows specifying the Handler).
340 *
341 * @param listener the listener invoked for all the callbacks, this must
342 * not be null.
343 * @param handler the handler to use
344 *
345 * @throws NullPointerException if either {@code listener} or
346 * {@code handler} is null.
347 *
348 * @deprecated Use {@link #GestureDetector(android.content.Context,
349 * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead.
350 */
351 @Deprecated
352 public GestureDetector(OnGestureListener listener, Handler handler) {
353 this(null, listener, handler);
354 }
355
356 /**
357 * Creates a GestureDetector with the supplied listener.
358 * You may only use this constructor from a UI thread (this is the usual situation).
359 * @see android.os.Handler#Handler()
360 *
361 * @param listener the listener invoked for all the callbacks, this must
362 * not be null.
363 *
364 * @throws NullPointerException if {@code listener} is null.
365 *
366 * @deprecated Use {@link #GestureDetector(android.content.Context,
367 * android.view.GestureDetector.OnGestureListener)} instead.
368 */
369 @Deprecated
370 public GestureDetector(OnGestureListener listener) {
371 this(null, listener, null);
372 }
373
374 /**
375 * Creates a GestureDetector with the supplied listener.
Scott Mainc27ea252013-08-30 13:56:05 -0700376 * You may only use this constructor from a {@link android.os.Looper} thread.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 * @see android.os.Handler#Handler()
378 *
379 * @param context the application's context
380 * @param listener the listener invoked for all the callbacks, this must
Paul Sowdenafe846d2019-10-31 11:01:56 -0700381 * not be null. If the listener implements the {@link OnDoubleTapListener} or
382 * {@link OnContextClickListener} then it will also be set as the listener for
383 * these callbacks (for example when using the {@link SimpleOnGestureListener}).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 *
385 * @throws NullPointerException if {@code listener} is null.
386 */
387 public GestureDetector(Context context, OnGestureListener listener) {
388 this(context, listener, null);
389 }
390
391 /**
Scott Mainc27ea252013-08-30 13:56:05 -0700392 * Creates a GestureDetector with the supplied listener that runs deferred events on the
393 * thread associated with the supplied {@link android.os.Handler}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 * @see android.os.Handler#Handler()
395 *
396 * @param context the application's context
397 * @param listener the listener invoked for all the callbacks, this must
Paul Sowdenafe846d2019-10-31 11:01:56 -0700398 * not be null. If the listener implements the {@link OnDoubleTapListener} or
399 * {@link OnContextClickListener} then it will also be set as the listener for
400 * these callbacks (for example when using the {@link SimpleOnGestureListener}).
Scott Mainc27ea252013-08-30 13:56:05 -0700401 * @param handler the handler to use for running deferred listener events.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402 *
403 * @throws NullPointerException if {@code listener} is null.
404 */
405 public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
Adam Powell05a1e1f2012-08-29 13:54:44 -0700406 if (handler != null) {
407 mHandler = new GestureHandler(handler);
408 } else {
409 mHandler = new GestureHandler();
410 }
411 mListener = listener;
412 if (listener instanceof OnDoubleTapListener) {
413 setOnDoubleTapListener((OnDoubleTapListener) listener);
414 }
Mady Mellor015020e2015-06-05 14:41:22 -0700415 if (listener instanceof OnContextClickListener) {
416 setContextClickListener((OnContextClickListener) listener);
Mady Mellora6b16452015-04-14 18:03:34 -0700417 }
Adam Powell05a1e1f2012-08-29 13:54:44 -0700418 init(context);
Adam Powell216bccf2010-02-01 15:03:17 -0800419 }
420
421 /**
Scott Mainc27ea252013-08-30 13:56:05 -0700422 * Creates a GestureDetector with the supplied listener that runs deferred events on the
423 * thread associated with the supplied {@link android.os.Handler}.
Adam Powell216bccf2010-02-01 15:03:17 -0800424 * @see android.os.Handler#Handler()
425 *
426 * @param context the application's context
427 * @param listener the listener invoked for all the callbacks, this must
428 * not be null.
Scott Mainc27ea252013-08-30 13:56:05 -0700429 * @param handler the handler to use for running deferred listener events.
430 * @param unused currently not used.
Adam Powell216bccf2010-02-01 15:03:17 -0800431 *
432 * @throws NullPointerException if {@code listener} is null.
433 */
434 public GestureDetector(Context context, OnGestureListener listener, Handler handler,
Adam Powell05a1e1f2012-08-29 13:54:44 -0700435 boolean unused) {
436 this(context, listener, handler);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 }
438
Adam Powell05a1e1f2012-08-29 13:54:44 -0700439 private void init(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 if (mListener == null) {
441 throw new NullPointerException("OnGestureListener must not be null");
442 }
443 mIsLongpressEnabled = true;
444
445 // Fallback to support pre-donuts releases
Gilles Debunne006fa482011-10-27 17:23:36 -0700446 int touchSlop, doubleTapSlop, doubleTapTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 if (context == null) {
448 //noinspection deprecation
449 touchSlop = ViewConfiguration.getTouchSlop();
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800450 doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
452 //noinspection deprecation
453 mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
Romain Guy4296fc42009-07-06 11:48:52 -0700454 mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 } else {
456 final ViewConfiguration configuration = ViewConfiguration.get(context);
457 touchSlop = configuration.getScaledTouchSlop();
Gilles Debunne006fa482011-10-27 17:23:36 -0700458 doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800459 doubleTapSlop = configuration.getScaledDoubleTapSlop();
460 mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
Romain Guy4296fc42009-07-06 11:48:52 -0700461 mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 }
463 mTouchSlopSquare = touchSlop * touchSlop;
Gilles Debunne006fa482011-10-27 17:23:36 -0700464 mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
466 }
467
468 /**
469 * Sets the listener which will be called for double-tap and related
470 * gestures.
471 *
472 * @param onDoubleTapListener the listener invoked for all the callbacks, or
473 * null to stop listening for double-tap gestures.
474 */
475 public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
476 mDoubleTapListener = onDoubleTapListener;
477 }
478
479 /**
Mady Mellor015020e2015-06-05 14:41:22 -0700480 * Sets the listener which will be called for context clicks.
Mady Mellora6b16452015-04-14 18:03:34 -0700481 *
Mady Mellor015020e2015-06-05 14:41:22 -0700482 * @param onContextClickListener the listener invoked for all the callbacks, or null to stop
483 * listening for context clicks.
Mady Mellora6b16452015-04-14 18:03:34 -0700484 */
Mady Mellor015020e2015-06-05 14:41:22 -0700485 public void setContextClickListener(OnContextClickListener onContextClickListener) {
486 mContextClickListener = onContextClickListener;
Mady Mellora6b16452015-04-14 18:03:34 -0700487 }
488
489 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 * Set whether longpress is enabled, if this is enabled when a user
491 * presses and holds down you get a longpress event and nothing further.
492 * If it's disabled the user can press and hold down and then later
493 * moved their finger and you will get scroll events. By default
494 * longpress is enabled.
495 *
496 * @param isLongpressEnabled whether longpress should be enabled.
497 */
498 public void setIsLongpressEnabled(boolean isLongpressEnabled) {
499 mIsLongpressEnabled = isLongpressEnabled;
500 }
501
502 /**
503 * @return true if longpress is enabled, else false.
504 */
505 public boolean isLongpressEnabled() {
506 return mIsLongpressEnabled;
507 }
508
509 /**
510 * Analyzes the given motion event and if applicable triggers the
511 * appropriate callbacks on the {@link OnGestureListener} supplied.
512 *
513 * @param ev The current motion event.
514 * @return true if the {@link OnGestureListener} consumed the event,
515 * else false.
516 */
517 public boolean onTouchEvent(MotionEvent ev) {
Jeff Brown21bc5c92011-02-28 18:27:14 -0800518 if (mInputEventConsistencyVerifier != null) {
519 mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
520 }
521
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 final int action = ev.getAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523
Philip Quinn5be044f2019-01-29 12:47:09 -0800524 if (mCurrentMotionEvent != null) {
525 mCurrentMotionEvent.recycle();
526 }
527 mCurrentMotionEvent = MotionEvent.obtain(ev);
528
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 if (mVelocityTracker == null) {
530 mVelocityTracker = VelocityTracker.obtain();
531 }
532 mVelocityTracker.addMovement(ev);
533
Adam Powellf90165a2012-08-31 11:11:39 -0700534 final boolean pointerUp =
535 (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700536 final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
Dennis Kempinac1b31d2016-11-02 17:02:25 -0700537 final boolean isGeneratedGesture =
538 (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700539
540 // Determine focal point
541 float sumX = 0, sumY = 0;
542 final int count = ev.getPointerCount();
543 for (int i = 0; i < count; i++) {
544 if (skipIndex == i) continue;
545 sumX += ev.getX(i);
546 sumY += ev.getY(i);
547 }
548 final int div = pointerUp ? count - 1 : count;
549 final float focusX = sumX / div;
550 final float focusY = sumY / div;
551
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552 boolean handled = false;
553
Adam Powell216bccf2010-02-01 15:03:17 -0800554 switch (action & MotionEvent.ACTION_MASK) {
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800555 case MotionEvent.ACTION_POINTER_DOWN:
556 mDownFocusX = mLastFocusX = focusX;
557 mDownFocusY = mLastFocusY = focusY;
558 // Cancel long press and taps
559 cancelTaps();
560 break;
Adam Powell216bccf2010-02-01 15:03:17 -0800561
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800562 case MotionEvent.ACTION_POINTER_UP:
563 mDownFocusX = mLastFocusX = focusX;
564 mDownFocusY = mLastFocusY = focusY;
Adam Powellcd663592012-09-11 09:52:37 -0700565
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800566 // Check the dot product of current velocities.
567 // If the pointer that left was opposing another velocity vector, clear.
568 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
569 final int upIndex = ev.getActionIndex();
570 final int id1 = ev.getPointerId(upIndex);
571 final float x1 = mVelocityTracker.getXVelocity(id1);
572 final float y1 = mVelocityTracker.getYVelocity(id1);
573 for (int i = 0; i < count; i++) {
574 if (i == upIndex) continue;
Adam Powellcd663592012-09-11 09:52:37 -0700575
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800576 final int id2 = ev.getPointerId(i);
577 final float x = x1 * mVelocityTracker.getXVelocity(id2);
578 final float y = y1 * mVelocityTracker.getYVelocity(id2);
Adam Powellcd663592012-09-11 09:52:37 -0700579
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800580 final float dot = x + y;
581 if (dot < 0) {
582 mVelocityTracker.clear();
583 break;
584 }
585 }
586 break;
587
588 case MotionEvent.ACTION_DOWN:
589 if (mDoubleTapListener != null) {
590 boolean hadTapMessage = mHandler.hasMessages(TAP);
591 if (hadTapMessage) mHandler.removeMessages(TAP);
592 if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
593 && hadTapMessage
594 && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
595 // This is a second tap
596 mIsDoubleTapping = true;
Philip Quinn5be044f2019-01-29 12:47:09 -0800597 recordGestureClassification(
598 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800599 // Give a callback with the first tap of the double-tap
600 handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
601 // Give a callback with down event of the double-tap
602 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
603 } else {
604 // This is a first tap
605 mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
606 }
607 }
608
609 mDownFocusX = mLastFocusX = focusX;
610 mDownFocusY = mLastFocusY = focusY;
611 if (mCurrentDownEvent != null) {
612 mCurrentDownEvent.recycle();
613 }
614 mCurrentDownEvent = MotionEvent.obtain(ev);
615 mAlwaysInTapRegion = true;
616 mAlwaysInBiggerTapRegion = true;
617 mStillDown = true;
618 mInLongPress = false;
619 mDeferConfirmSingleTap = false;
Philip Quinn5be044f2019-01-29 12:47:09 -0800620 mHasRecordedClassification = false;
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800621
622 if (mIsLongpressEnabled) {
623 mHandler.removeMessages(LONG_PRESS);
Philip Quinn5be044f2019-01-29 12:47:09 -0800624 mHandler.sendMessageAtTime(
625 mHandler.obtainMessage(
626 LONG_PRESS,
627 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
628 0 /* arg2 */),
629 mCurrentDownEvent.getDownTime()
630 + ViewConfiguration.getLongPressTimeout());
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800631 }
632 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
633 mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
634 handled |= mListener.onDown(ev);
635 break;
636
637 case MotionEvent.ACTION_MOVE:
638 if (mInLongPress || mInContextClick) {
Adam Powellcd663592012-09-11 09:52:37 -0700639 break;
640 }
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800641
642 final int motionClassification = ev.getClassification();
643 final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);
644
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800645 final float scrollX = mLastFocusX - focusX;
646 final float scrollY = mLastFocusY - focusY;
647 if (mIsDoubleTapping) {
648 // Give the move events of the double-tap
Philip Quinn5be044f2019-01-29 12:47:09 -0800649 recordGestureClassification(
650 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800652 } else if (mAlwaysInTapRegion) {
653 final int deltaX = (int) (focusX - mDownFocusX);
654 final int deltaY = (int) (focusY - mDownFocusY);
655 int distance = (deltaX * deltaX) + (deltaY * deltaY);
656 int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800657
658 final boolean ambiguousGesture =
659 motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
660 final boolean shouldInhibitDefaultAction =
661 hasPendingLongPress && ambiguousGesture;
662 if (shouldInhibitDefaultAction) {
663 // Inhibit default long press
Siarhei Vishniakou8b047dd2019-01-24 16:04:13 -0800664 final float multiplier = ViewConfiguration.getAmbiguousGestureMultiplier();
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800665 if (distance > slopSquare) {
666 // The default action here is to remove long press. But if the touch
667 // slop below gets increased, and we never exceed the modified touch
668 // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*
669 // will happen in response to user input. To prevent this,
670 // reschedule long press with a modified timeout.
671 mHandler.removeMessages(LONG_PRESS);
Siarhei Vishniakou8b047dd2019-01-24 16:04:13 -0800672 final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
Philip Quinn5be044f2019-01-29 12:47:09 -0800673 mHandler.sendMessageAtTime(
674 mHandler.obtainMessage(
675 LONG_PRESS,
676 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
677 0 /* arg2 */),
678 ev.getDownTime() + (long) (longPressTimeout * multiplier));
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800679 }
680 // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
681 // until the gesture is resolved.
682 // However, for safety, simply increase the touch slop in case the
683 // classification is erroneous. Since the value is squared, multiply twice.
Siarhei Vishniakou8b047dd2019-01-24 16:04:13 -0800684 slopSquare *= multiplier * multiplier;
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800685 }
686
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800687 if (distance > slopSquare) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800688 recordGestureClassification(
689 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800690 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
691 mLastFocusX = focusX;
692 mLastFocusY = focusY;
693 mAlwaysInTapRegion = false;
694 mHandler.removeMessages(TAP);
695 mHandler.removeMessages(SHOW_PRESS);
696 mHandler.removeMessages(LONG_PRESS);
697 }
698 int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
699 if (distance > doubleTapSlopSquare) {
700 mAlwaysInBiggerTapRegion = false;
701 }
702 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800703 recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800704 handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
Adam Powell05a1e1f2012-08-29 13:54:44 -0700705 mLastFocusX = focusX;
706 mLastFocusY = focusY;
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800707 }
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800708 final boolean deepPress =
709 motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
710 if (deepPress && hasPendingLongPress) {
711 mHandler.removeMessages(LONG_PRESS);
Philip Quinn5be044f2019-01-29 12:47:09 -0800712 mHandler.sendMessage(
713 mHandler.obtainMessage(
714 LONG_PRESS,
715 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,
716 0 /* arg2 */));
Siarhei Vishniakou07f440a2017-12-12 16:35:19 -0800717 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800718 break;
719
720 case MotionEvent.ACTION_UP:
721 mStillDown = false;
722 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
723 if (mIsDoubleTapping) {
724 // Finally, give the up event of the double-tap
Philip Quinn5be044f2019-01-29 12:47:09 -0800725 recordGestureClassification(
726 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800727 handled |= mDoubleTapListener.onDoubleTapEvent(ev);
728 } else if (mInLongPress) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800729 mHandler.removeMessages(TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800730 mInLongPress = false;
731 } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
Philip Quinn5be044f2019-01-29 12:47:09 -0800732 recordGestureClassification(
733 TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800734 handled = mListener.onSingleTapUp(ev);
735 if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
736 mDoubleTapListener.onSingleTapConfirmed(ev);
737 }
738 } else if (!mIgnoreNextUpEvent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800740 // A fling must travel the minimum tap distance
741 final VelocityTracker velocityTracker = mVelocityTracker;
742 final int pointerId = ev.getPointerId(0);
743 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
744 final float velocityY = velocityTracker.getYVelocity(pointerId);
745 final float velocityX = velocityTracker.getXVelocity(pointerId);
746
747 if ((Math.abs(velocityY) > mMinimumFlingVelocity)
748 || (Math.abs(velocityX) > mMinimumFlingVelocity)) {
749 handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
750 }
Adam Powelleca3e602013-02-15 11:26:45 -0800751 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800752 if (mPreviousUpEvent != null) {
753 mPreviousUpEvent.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 }
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800755 // Hold the event we obtained above - listeners may have changed the original.
756 mPreviousUpEvent = currentUpEvent;
757 if (mVelocityTracker != null) {
758 // This may have been cleared when we called out to the
759 // application above.
760 mVelocityTracker.recycle();
761 mVelocityTracker = null;
762 }
763 mIsDoubleTapping = false;
764 mDeferConfirmSingleTap = false;
765 mIgnoreNextUpEvent = false;
766 mHandler.removeMessages(SHOW_PRESS);
767 mHandler.removeMessages(LONG_PRESS);
768 break;
Jeff Brownbbdc50b2011-04-19 23:46:52 -0700769
Siarhei Vishniakoud60090d2017-12-12 16:24:26 -0800770 case MotionEvent.ACTION_CANCEL:
771 cancel();
772 break;
Jeff Brownbbdc50b2011-04-19 23:46:52 -0700773 }
774
775 if (!handled && mInputEventConsistencyVerifier != null) {
776 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800777 }
778 return handled;
779 }
780
Mady Mellorb0933442015-05-27 14:15:57 -0700781 /**
782 * Analyzes the given generic motion event and if applicable triggers the
783 * appropriate callbacks on the {@link OnGestureListener} supplied.
784 *
785 * @param ev The current motion event.
786 * @return true if the {@link OnGestureListener} consumed the event,
787 * else false.
788 */
789 public boolean onGenericMotionEvent(MotionEvent ev) {
790 if (mInputEventConsistencyVerifier != null) {
791 mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
792 }
793
Mady Mellor015020e2015-06-05 14:41:22 -0700794 final int actionButton = ev.getActionButton();
Mady Mellorb0933442015-05-27 14:15:57 -0700795 switch (ev.getActionMasked()) {
796 case MotionEvent.ACTION_BUTTON_PRESS:
Mady Mellor015020e2015-06-05 14:41:22 -0700797 if (mContextClickListener != null && !mInContextClick && !mInLongPress
798 && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
799 || actionButton == MotionEvent.BUTTON_SECONDARY)) {
800 if (mContextClickListener.onContextClick(ev)) {
801 mInContextClick = true;
Mady Mellorb0933442015-05-27 14:15:57 -0700802 mHandler.removeMessages(LONG_PRESS);
803 mHandler.removeMessages(TAP);
804 return true;
805 }
806 }
807 break;
808
809 case MotionEvent.ACTION_BUTTON_RELEASE:
Mady Mellor015020e2015-06-05 14:41:22 -0700810 if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
811 || actionButton == MotionEvent.BUTTON_SECONDARY)) {
812 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700813 mIgnoreNextUpEvent = true;
814 }
815 break;
816 }
817 return false;
818 }
819
Adam Powell216bccf2010-02-01 15:03:17 -0800820 private void cancel() {
821 mHandler.removeMessages(SHOW_PRESS);
822 mHandler.removeMessages(LONG_PRESS);
823 mHandler.removeMessages(TAP);
824 mVelocityTracker.recycle();
825 mVelocityTracker = null;
826 mIsDoubleTapping = false;
827 mStillDown = false;
Adam Powell17921ee2011-06-09 11:39:21 -0700828 mAlwaysInTapRegion = false;
829 mAlwaysInBiggerTapRegion = false;
Adam Powelleca3e602013-02-15 11:26:45 -0800830 mDeferConfirmSingleTap = false;
Mady Mellora6b16452015-04-14 18:03:34 -0700831 mInLongPress = false;
Mady Mellor015020e2015-06-05 14:41:22 -0700832 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700833 mIgnoreNextUpEvent = false;
Adam Powell216bccf2010-02-01 15:03:17 -0800834 }
835
Adam Powell05a1e1f2012-08-29 13:54:44 -0700836 private void cancelTaps() {
837 mHandler.removeMessages(SHOW_PRESS);
838 mHandler.removeMessages(LONG_PRESS);
839 mHandler.removeMessages(TAP);
840 mIsDoubleTapping = false;
841 mAlwaysInTapRegion = false;
842 mAlwaysInBiggerTapRegion = false;
Adam Powelleca3e602013-02-15 11:26:45 -0800843 mDeferConfirmSingleTap = false;
Mady Mellora6b16452015-04-14 18:03:34 -0700844 mInLongPress = false;
Mady Mellor015020e2015-06-05 14:41:22 -0700845 mInContextClick = false;
Mady Mellorb0933442015-05-27 14:15:57 -0700846 mIgnoreNextUpEvent = false;
Adam Powell05a1e1f2012-08-29 13:54:44 -0700847 }
848
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800849 private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
850 MotionEvent secondDown) {
851 if (!mAlwaysInBiggerTapRegion) {
852 return false;
853 }
854
Adam Powellaf1785f2013-09-05 13:44:45 -0700855 final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
856 if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800857 return false;
858 }
859
860 int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
861 int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
Dennis Kempinac1b31d2016-11-02 17:02:25 -0700862 final boolean isGeneratedGesture =
863 (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
864 int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
865 return (deltaX * deltaX + deltaY * deltaY < slopSquare);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 }
867
868 private void dispatchLongPress() {
869 mHandler.removeMessages(TAP);
Adam Powelleca3e602013-02-15 11:26:45 -0800870 mDeferConfirmSingleTap = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800871 mInLongPress = true;
872 mListener.onLongPress(mCurrentDownEvent);
873 }
Philip Quinn5be044f2019-01-29 12:47:09 -0800874
875 private void recordGestureClassification(int classification) {
876 if (mHasRecordedClassification
877 || classification
878 == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
879 // Only record the first classification for an event stream.
880 return;
881 }
882 if (mCurrentDownEvent == null || mCurrentMotionEvent == null) {
883 // If the complete event stream wasn't seen, don't record anything.
884 mHasRecordedClassification = true;
885 return;
886 }
887 StatsLog.write(
888 StatsLog.TOUCH_GESTURE_CLASSIFIED,
889 getClass().getName(),
890 classification,
891 (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()),
892 (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(),
893 mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY()));
894 mHasRecordedClassification = true;
895 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800896}