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