OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.content.browser; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.os.Bundle; |
| 9 import android.view.GestureDetector; |
| 10 import android.view.ViewConfiguration; |
| 11 import android.view.GestureDetector.OnGestureListener; |
| 12 import android.view.MotionEvent; |
| 13 import android.util.Log; |
| 14 import android.util.Pair; |
| 15 |
| 16 import org.chromium.content.browser.LongPressDetector.LongPressDelegate; |
| 17 import org.chromium.content.common.TraceEvent; |
| 18 |
| 19 import java.util.ArrayDeque; |
| 20 import java.util.Deque; |
| 21 |
| 22 /** |
| 23 * This class handles all MotionEvent handling done in ContentViewCore including
the gesture |
| 24 * recognition. It sends all related native calls through the interface MotionEv
entDelegate. |
| 25 */ |
| 26 class ContentViewGestureHandler implements LongPressDelegate { |
| 27 |
| 28 private static final String TAG = ContentViewGestureHandler.class.toString()
; |
| 29 /** |
| 30 * Used for GESTURE_FLING_START x velocity |
| 31 */ |
| 32 static final String VELOCITY_X = "Velocity X"; |
| 33 /** |
| 34 * Used for GESTURE_FLING_START y velocity |
| 35 */ |
| 36 static final String VELOCITY_Y = "Velocity Y"; |
| 37 /** |
| 38 * Used in GESTURE_SINGLE_TAP_CONFIRMED to check whether ShowPress has been
called before. |
| 39 */ |
| 40 static final String SHOW_PRESS = "ShowPress"; |
| 41 /** |
| 42 * Used for GESTURE_PINCH_BY delta |
| 43 */ |
| 44 static final String DELTA = "Delta"; |
| 45 |
| 46 private Bundle mExtraParamBundle; |
| 47 private GestureDetector mGestureDetector; |
| 48 private ZoomManager mZoomManager; |
| 49 private LongPressDetector mLongPressDetector; |
| 50 private OnGestureListener mListener; |
| 51 private MotionEvent mCurrentDownEvent; |
| 52 private MotionEventDelegate mMotionEventDelegate; |
| 53 |
| 54 // Queue of motion events. If the boolean value is true, it means |
| 55 // that the event has been offered to the native side but not yet acknowledg
ed. If the |
| 56 // value is false, it means the touch event has not been offered |
| 57 // to the native side and can be immediately processed. |
| 58 private final Deque<Pair<MotionEvent, Boolean>> mPendingMotionEvents = |
| 59 new ArrayDeque<Pair<MotionEvent, Boolean>>(); |
| 60 |
| 61 // Has WebKit told us the current page requires touch events. |
| 62 private boolean mNeedTouchEvents = false; |
| 63 |
| 64 // Remember whether onShowPress() is called. If it is not, in onSingleTapCon
firmed() |
| 65 // we will first show the press state, then trigger the click. |
| 66 private boolean mShowPressIsCalled; |
| 67 |
| 68 // TODO(klobag): this is to avoid a bug in GestureDetector. With multi-touch
, |
| 69 // mAlwaysInTapRegion is not reset. So when the last finger is up, onSingleT
apUp() |
| 70 // will be mistakenly fired. |
| 71 private boolean mIgnoreSingleTap; |
| 72 |
| 73 // Does native think we are scrolling? True from right before we |
| 74 // send the first scroll event until the last finger is raised, or |
| 75 // until after the follow-up fling has finished. Call |
| 76 // nativeScrollBegin() when setting this to true, and use |
| 77 // tellNativeScrollingHasEnded() to set it to false. |
| 78 private boolean mNativeScrolling; |
| 79 |
| 80 private boolean mPinchInProgress = false; |
| 81 |
| 82 // Tracks whether a touch cancel event has been sent as a result of switchin
g |
| 83 // into scrolling or pinching mode. |
| 84 private boolean mTouchCancelEventSent = false; |
| 85 |
| 86 private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTap
Timeout(); |
| 87 |
| 88 //On single tap this will store the x, y coordinates of the touch. |
| 89 private int mSingleTapX; |
| 90 private int mSingleTapY; |
| 91 |
| 92 // Used to track the last rawX/Y coordinates for moves. This gives absolute
scroll distance. |
| 93 // Useful for full screen tracking. |
| 94 private float mLastRawX = 0; |
| 95 private float mLastRawY = 0; |
| 96 |
| 97 // Cache of square of the scaled touch slop so we don't have to calculate it
on every touch. |
| 98 private int mScaledTouchSlopSquare; |
| 99 |
| 100 // Used to track the accumulated scroll error over time. This is used to rem
ove the |
| 101 // rounding error we introduced by passing integers to webkit. |
| 102 private float mAccumulatedScrollErrorX = 0; |
| 103 private float mAccumulatedScrollErrorY = 0; |
| 104 |
| 105 private static final int SNAP_NONE = 0; |
| 106 private static final int SNAP_HORIZ = 1; |
| 107 private static final int SNAP_VERT = 2; |
| 108 private int mSnapScrollMode = SNAP_NONE; |
| 109 private float mAverageAngle; |
| 110 private boolean mSeenFirstScroll; |
| 111 |
| 112 /* |
| 113 * Here is the snap align logic: |
| 114 * 1. If it starts nearly horizontally or vertically, snap align; |
| 115 * 2. If there is a dramatic direction change, let it go; |
| 116 * |
| 117 * Adjustable parameters. Angle is the radians on a unit circle, limited |
| 118 * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical) |
| 119 */ |
| 120 private static final float HSLOPE_TO_START_SNAP = .25f; |
| 121 private static final float HSLOPE_TO_BREAK_SNAP = .6f; |
| 122 private static final float VSLOPE_TO_START_SNAP = 1.25f; |
| 123 private static final float VSLOPE_TO_BREAK_SNAP = .6f; |
| 124 |
| 125 /* |
| 126 * These values are used to influence the average angle when entering |
| 127 * snap mode. If it is the first movement entering snap, we set the average |
| 128 * to the appropriate ideal. If the user is entering into snap after the |
| 129 * first movement, then we average the average angle with these values. |
| 130 */ |
| 131 private static final float ANGLE_VERT = (float)(Math.PI / 2.0); |
| 132 private static final float ANGLE_HORIZ = 0f; |
| 133 |
| 134 /* |
| 135 * The modified moving average weight. |
| 136 * Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n |
| 137 */ |
| 138 private static final float MMA_WEIGHT_N = 5; |
| 139 |
| 140 static final int GESTURE_SHOW_PRESSED_STATE = 0; |
| 141 static final int GESTURE_DOUBLE_TAP = 1; |
| 142 static final int GESTURE_SINGLE_TAP_UP = 2; |
| 143 static final int GESTURE_SINGLE_TAP_CONFIRMED = 3; |
| 144 static final int GESTURE_LONG_PRESS = 4; |
| 145 static final int GESTURE_SCROLL_START = 5; |
| 146 static final int GESTURE_SCROLL_BY = 6; |
| 147 static final int GESTURE_SCROLL_END = 7; |
| 148 static final int GESTURE_FLING_START = 8; |
| 149 static final int GESTURE_FLING_CANCEL = 9; |
| 150 static final int GESTURE_PINCH_BEGIN = 10; |
| 151 static final int GESTURE_PINCH_BY = 11; |
| 152 static final int GESTURE_PINCH_END = 12; |
| 153 |
| 154 /** |
| 155 * This is an interface to handle MotionEvent related communication with the
native side also |
| 156 * access some ContentView specific parameters. |
| 157 */ |
| 158 public interface MotionEventDelegate { |
| 159 /** |
| 160 * Send a raw {@link MotionEvent} to the native side |
| 161 * @param timeMs Time of the event in ms. |
| 162 * @param action The action type for the event. |
| 163 * @param pts The TouchPoint array to be sent for the event. |
| 164 * @return Whether the event was sent to the native side successfully or
not. |
| 165 */ |
| 166 public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts)
; |
| 167 |
| 168 /** |
| 169 * Send a gesture event to the native side. |
| 170 * @param type The type of the gesture event. |
| 171 * @param timeMs The time the gesture event occurred at. |
| 172 * @param x The x location for the gesture event. |
| 173 * @param y The y location for the gesture event. |
| 174 * @param extraParams A bundle that holds specific extra parameters for
certain gestures. |
| 175 * Refer to gesture type definition for more information. |
| 176 * @return Whether the gesture was sent successfully. |
| 177 */ |
| 178 boolean sendGesture( |
| 179 int type, long timeMs, int x, int y, Bundle extraParams); |
| 180 |
| 181 /** |
| 182 * Gives the UI the chance to override each scroll event. |
| 183 * @param x The amount scrolled in the X direction. |
| 184 * @param y The amount scrolled in the Y direction. |
| 185 * @return Whether or not the UI consumed and handled this event. |
| 186 */ |
| 187 boolean didUIStealScroll(float x, float y); |
| 188 |
| 189 /** |
| 190 * Show the zoom picker UI. |
| 191 */ |
| 192 public void invokeZoomPicker(); |
| 193 } |
| 194 |
| 195 ContentViewGestureHandler( |
| 196 Context context, MotionEventDelegate delegate, ZoomManager zoomManag
er) { |
| 197 mExtraParamBundle = new Bundle(); |
| 198 mLongPressDetector = new LongPressDetector(context, this); |
| 199 mMotionEventDelegate = delegate; |
| 200 mZoomManager = zoomManager; |
| 201 initGestureDetectors(context); |
| 202 } |
| 203 |
| 204 /** |
| 205 * Used to override the default gesture detector and listener. This is used
for testing only. |
| 206 * @param detector The new GestureDetector to be assigned. |
| 207 * @param listener The new onGestureListener to be assigned. |
| 208 */ |
| 209 public void setGestureDetectorAndListener( |
| 210 GestureDetector detector, OnGestureListener listener) { |
| 211 mGestureDetector = detector; |
| 212 mListener = listener; |
| 213 } |
| 214 |
| 215 private void initGestureDetectors(final Context context) { |
| 216 int scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(
); |
| 217 mScaledTouchSlopSquare = scaledTouchSlop * scaledTouchSlop; |
| 218 try { |
| 219 TraceEvent.begin(); |
| 220 GestureDetector.SimpleOnGestureListener listener = |
| 221 new GestureDetector.SimpleOnGestureListener() { |
| 222 @Override |
| 223 public boolean onDown(MotionEvent e) { |
| 224 mShowPressIsCalled = false; |
| 225 mIgnoreSingleTap = false; |
| 226 mSeenFirstScroll = false; |
| 227 mNativeScrolling = false; |
| 228 mSnapScrollMode = SNAP_NONE; |
| 229 mLastRawX = e.getRawX(); |
| 230 mLastRawY = e.getRawY(); |
| 231 mAccumulatedScrollErrorX = 0; |
| 232 mAccumulatedScrollErrorY = 0; |
| 233 // Return true to indicate that we want to handle touch |
| 234 return true; |
| 235 } |
| 236 |
| 237 @Override |
| 238 public boolean onScroll(MotionEvent e1, MotionEvent e2, |
| 239 float distanceX, float distanceY) { |
| 240 // Scroll snapping |
| 241 if (!mSeenFirstScroll) { |
| 242 mAverageAngle = calculateDragAngle(distanceX, distan
ceY); |
| 243 // Initial scroll event |
| 244 if (!mZoomManager.isScaleGestureDetectionInProgress(
)) { |
| 245 // if it starts nearly horizontal or vertical, e
nforce it |
| 246 if (mAverageAngle < HSLOPE_TO_START_SNAP) { |
| 247 mSnapScrollMode = SNAP_HORIZ; |
| 248 mAverageAngle = ANGLE_HORIZ; |
| 249 } else if (mAverageAngle > VSLOPE_TO_START_SNAP)
{ |
| 250 mSnapScrollMode = SNAP_VERT; |
| 251 mAverageAngle = ANGLE_VERT; |
| 252 } |
| 253 } |
| 254 mSeenFirstScroll = true; |
| 255 // Ignore the first scroll delta to avoid a visible
jump. |
| 256 return true; |
| 257 } else { |
| 258 mAverageAngle += |
| 259 (calculateDragAngle(distanceX, distanceY) - mAve
rageAngle) |
| 260 / MMA_WEIGHT_N; |
| 261 if (mSnapScrollMode != SNAP_NONE) { |
| 262 if ((mSnapScrollMode == SNAP_VERT |
| 263 && mAverageAngle < VSLOPE_TO_BREAK_SNAP) |
| 264 || (mSnapScrollMode == SNAP_HORIZ |
| 265 && mAverageAngle > HSLOPE_TO_BRE
AK_SNAP)) { |
| 266 // radical change means getting out of snap
mode |
| 267 mSnapScrollMode = SNAP_NONE; |
| 268 } |
| 269 } else { |
| 270 if (!mZoomManager.isScaleGestureDetectionInProgr
ess()) { |
| 271 if (mAverageAngle < HSLOPE_TO_START_SNAP) { |
| 272 mSnapScrollMode = SNAP_HORIZ; |
| 273 mAverageAngle = (mAverageAngle + ANGLE_H
ORIZ) / 2; |
| 274 } else if (mAverageAngle > VSLOPE_TO_START_S
NAP) { |
| 275 mSnapScrollMode = SNAP_VERT; |
| 276 mAverageAngle = (mAverageAngle + ANGLE_V
ERT) / 2; |
| 277 } |
| 278 } |
| 279 } |
| 280 } |
| 281 |
| 282 if (mSnapScrollMode != SNAP_NONE) { |
| 283 if (mSnapScrollMode == SNAP_HORIZ) { |
| 284 distanceY = 0; |
| 285 } else { |
| 286 distanceX = 0; |
| 287 } |
| 288 } |
| 289 |
| 290 boolean didUIStealScroll = mMotionEventDelegate.didUISte
alScroll( |
| 291 e2.getRawX() - mLastRawX, e2.getRawY() - mLastRa
wY); |
| 292 |
| 293 mLastRawX = e2.getRawX(); |
| 294 mLastRawY = e2.getRawY(); |
| 295 if (didUIStealScroll) return true; |
| 296 if (!mNativeScrolling && mMotionEventDelegate.sendGestur
e( |
| 297 GESTURE_SCROLL_START, e1.getEventTime(), |
| 298 (int) e1.getX(), (int) e1.getY(), null))
{ |
| 299 mNativeScrolling = true; |
| 300 |
| 301 } |
| 302 // distanceX and distanceY is the scrolling offset since
last onScroll. |
| 303 // Because we are passing integers to webkit, this could
introduce |
| 304 // rounding errors. The rounding errors will accumulate
overtime. |
| 305 // To solve this, we should adding back the rounding err
ors each time |
| 306 // when we calculate the new offset. |
| 307 int dx = (int) (distanceX + mAccumulatedScrollErrorX); |
| 308 int dy = (int) (distanceY + mAccumulatedScrollErrorY); |
| 309 mAccumulatedScrollErrorX = distanceX + mAccumulatedScrol
lErrorX - dx; |
| 310 mAccumulatedScrollErrorY = distanceY + mAccumulatedScrol
lErrorY - dy; |
| 311 if ((dx | dy) != 0) { |
| 312 mMotionEventDelegate.sendGesture(GESTURE_SCROLL_BY, |
| 313 e2.getEventTime(), dx, dy, null); |
| 314 } |
| 315 |
| 316 mMotionEventDelegate.invokeZoomPicker(); |
| 317 |
| 318 return true; |
| 319 } |
| 320 |
| 321 @Override |
| 322 public boolean onFling(MotionEvent e1, MotionEvent e2, |
| 323 float velocityX, float velocityY) { |
| 324 if (mSnapScrollMode == SNAP_NONE) { |
| 325 float flingAngle = calculateDragAngle(velocityX, vel
ocityY); |
| 326 if (flingAngle < HSLOPE_TO_START_SNAP) { |
| 327 mSnapScrollMode = SNAP_HORIZ; |
| 328 mAverageAngle = ANGLE_HORIZ; |
| 329 } else if (flingAngle > VSLOPE_TO_START_SNAP) { |
| 330 mSnapScrollMode = SNAP_VERT; |
| 331 mAverageAngle = ANGLE_VERT; |
| 332 } |
| 333 } |
| 334 |
| 335 if (mSnapScrollMode != SNAP_NONE) { |
| 336 if (mSnapScrollMode == SNAP_HORIZ) { |
| 337 velocityY = 0; |
| 338 } else { |
| 339 velocityX = 0; |
| 340 } |
| 341 } |
| 342 |
| 343 fling(e1.getEventTime(),(int) e1.getX(0), (int) e1.getY(
0), |
| 344 (int) velocityX, (int) velocityY); |
| 345 return true; |
| 346 } |
| 347 |
| 348 @Override |
| 349 public void onShowPress(MotionEvent e) { |
| 350 mShowPressIsCalled = true; |
| 351 mMotionEventDelegate.sendGesture(GESTURE_SHOW_PRESSED_ST
ATE, |
| 352 e.getEventTime(), (int) e.getX(), (int) e.getY()
, null); |
| 353 } |
| 354 |
| 355 @Override |
| 356 public boolean onSingleTapUp(MotionEvent e) { |
| 357 if (isDistanceBetweenDownAndUpTooLong(e.getRawX(), e.get
RawY())) { |
| 358 mIgnoreSingleTap = true; |
| 359 return true; |
| 360 } |
| 361 // This is a hack to address the issue where user hovers |
| 362 // over a link for longer than DOUBLE_TAP_TIMEOUT, then |
| 363 // onSingleTapConfirmed() is not triggered. But we still |
| 364 // want to trigger the tap event at UP. So we override |
| 365 // onSingleTapUp() in this case. This assumes singleTapU
p |
| 366 // gets always called before singleTapConfirmed. |
| 367 if (!mIgnoreSingleTap && !mLongPressDetector.isInLongPre
ss() && |
| 368 (e.getEventTime() - e.getDownTime() > DOUBLE_TAP
_TIMEOUT)) { |
| 369 float x = e.getX(); |
| 370 float y = e.getY(); |
| 371 if (mMotionEventDelegate.sendGesture(GESTURE_SINGLE_
TAP_UP, |
| 372 e.getEventTime(), (int) x, (int) y, null)) { |
| 373 mIgnoreSingleTap = true; |
| 374 } |
| 375 setClickXAndY((int) x, (int) y); |
| 376 return true; |
| 377 } |
| 378 return false; |
| 379 } |
| 380 |
| 381 @Override |
| 382 public boolean onSingleTapConfirmed(MotionEvent e) { |
| 383 // Long taps in the edges of the screen have their event
s delayed by |
| 384 // ChromeViewHolder for tab swipe operations. As a conse
quence of the delay |
| 385 // this method might be called after receiving the up ev
ent. |
| 386 // These corner cases should be ignored. |
| 387 if (mLongPressDetector.isInLongPress() || mIgnoreSingleT
ap) return true; |
| 388 |
| 389 int x = (int) e.getX(); |
| 390 int y = (int) e.getY(); |
| 391 mExtraParamBundle.clear(); |
| 392 mExtraParamBundle.putBoolean(SHOW_PRESS, mShowPressIsCal
led); |
| 393 mMotionEventDelegate.sendGesture(GESTURE_SINGLE_TAP_CONF
IRMED, |
| 394 e.getEventTime(), x, y, mExtraParamBundle); |
| 395 setClickXAndY(x, y); |
| 396 return true; |
| 397 } |
| 398 |
| 399 @Override |
| 400 public boolean onDoubleTap(MotionEvent e) { |
| 401 mMotionEventDelegate.sendGesture(GESTURE_DOUBLE_TAP, |
| 402 e.getEventTime(), (int) e.getX(), (int) e.getY()
, null); |
| 403 return true; |
| 404 } |
| 405 |
| 406 @Override |
| 407 public void onLongPress(MotionEvent e) { |
| 408 if (!mZoomManager.isScaleGestureDetectionInProgress()) { |
| 409 mMotionEventDelegate.sendGesture(GESTURE_LONG_PRESS, |
| 410 e.getEventTime(), (int) e.getX(), (int) e.ge
tY(), null); |
| 411 } |
| 412 } |
| 413 |
| 414 /** |
| 415 * This method inspects the distance between where the user
started touching |
| 416 * the surface, and where she released. If the points are to
o far apart, we |
| 417 * should assume that the web page has consumed the scroll-e
vents in-between, |
| 418 * and as such, this should not be considered a single-tap. |
| 419 * |
| 420 * We use the Android frameworks notion of how far a touch c
an wander before |
| 421 * we think the user is scrolling. |
| 422 * |
| 423 * @param x the new x coordinate |
| 424 * @param y the new y coordinate |
| 425 * @return true if the distance is too long to be considered
a single tap |
| 426 */ |
| 427 private boolean isDistanceBetweenDownAndUpTooLong(float x, f
loat y) { |
| 428 double deltaX = mLastRawX - x; |
| 429 double deltaY = mLastRawY - y; |
| 430 return deltaX * deltaX + deltaY * deltaY > mScaledTouchS
lopSquare; |
| 431 } |
| 432 }; |
| 433 mListener = listener; |
| 434 mGestureDetector = new GestureDetector(context, listener); |
| 435 mGestureDetector.setIsLongpressEnabled(false); |
| 436 } finally { |
| 437 TraceEvent.end(); |
| 438 } |
| 439 } |
| 440 |
| 441 /** |
| 442 * @return LongPressDetector handling setting up timers for and canceling Lo
ngPress gestures. |
| 443 */ |
| 444 LongPressDetector getLongPressDetector() { |
| 445 return mLongPressDetector; |
| 446 } |
| 447 |
| 448 /** |
| 449 * @param event Start a LongPress gesture event from the listener. |
| 450 */ |
| 451 @Override |
| 452 public void onLongPress(MotionEvent event) { |
| 453 mListener.onLongPress(event); |
| 454 } |
| 455 |
| 456 /** |
| 457 * Cancels any ongoing LongPress timers. |
| 458 */ |
| 459 void cancelLongPress() { |
| 460 mLongPressDetector.cancelLongPress(); |
| 461 } |
| 462 |
| 463 /** |
| 464 * Fling the ContentView from the current position. |
| 465 * @param x Fling touch starting position |
| 466 * @param y Fling touch starting position |
| 467 * @param velocityX Initial velocity of the fling (X) measured in pixels per
second. |
| 468 * @param velocityY Initial velocity of the fling (Y) measured in pixels per
second. |
| 469 */ |
| 470 void fling(long timeMs, int x, int y, int velocityX, int velocityY) { |
| 471 endFling(timeMs); |
| 472 mExtraParamBundle.clear(); |
| 473 mExtraParamBundle.putInt(VELOCITY_X, velocityX); |
| 474 mExtraParamBundle.putInt(VELOCITY_Y, velocityY); |
| 475 mMotionEventDelegate.sendGesture(GESTURE_FLING_START, |
| 476 timeMs, x, y, mExtraParamBundle); |
| 477 } |
| 478 |
| 479 /** |
| 480 * Send a FlingCancel gesture event and also cancel scrolling if it is activ
e. |
| 481 * @param timeMs The time in ms for the event initiating this gesture. |
| 482 */ |
| 483 void endFling(long timeMs) { |
| 484 mMotionEventDelegate.sendGesture(GESTURE_FLING_CANCEL, timeMs, 0, 0, nul
l); |
| 485 tellNativeScrollingHasEnded(timeMs); |
| 486 } |
| 487 |
| 488 // If native thinks scrolling (or fling-scrolling) is going on, tell native |
| 489 // it has ended. |
| 490 private void tellNativeScrollingHasEnded(long timeMs) { |
| 491 if (mNativeScrolling) { |
| 492 mNativeScrolling = false; |
| 493 mMotionEventDelegate.sendGesture(GESTURE_SCROLL_END, timeMs, 0, 0, n
ull); |
| 494 } |
| 495 } |
| 496 |
| 497 /** |
| 498 * Starts a pinch gesture. |
| 499 * @param timeMs The time in ms for the event initiating this gesture. |
| 500 * @param x The x coordinate for the event initiating this gesture. |
| 501 * @param y The x coordinate for the event initiating this gesture. |
| 502 */ |
| 503 void pinchBegin(long timeMs, int x, int y) { |
| 504 mMotionEventDelegate.sendGesture(GESTURE_PINCH_BEGIN, timeMs, x, y, null
); |
| 505 } |
| 506 |
| 507 /** |
| 508 * Pinch by a given percentage. |
| 509 * @param timeMs The time in ms for the event initiating this gesture. |
| 510 * @param anchorX The x coordinate for the anchor point to be used in pinch. |
| 511 * @param anchorY The y coordinate for the anchor point to be used in pinch. |
| 512 * @param delta The percentage to pinch by. |
| 513 */ |
| 514 void pinchBy(long timeMs, int anchorX, int anchorY, float delta) { |
| 515 mExtraParamBundle.clear(); |
| 516 mExtraParamBundle.putFloat(DELTA, delta); |
| 517 mMotionEventDelegate.sendGesture(GESTURE_PINCH_BY, |
| 518 timeMs, anchorX, anchorY, mExtraParamBundle); |
| 519 mPinchInProgress = true; |
| 520 } |
| 521 |
| 522 /** |
| 523 * End a pinch gesture. |
| 524 * @param timeMs The time in ms for the event initiating this gesture. |
| 525 */ |
| 526 void pinchEnd(long timeMs) { |
| 527 mMotionEventDelegate.sendGesture(GESTURE_PINCH_END, timeMs, 0, 0, null); |
| 528 mPinchInProgress = false; |
| 529 } |
| 530 |
| 531 /** |
| 532 * Ignore singleTap gestures. |
| 533 */ |
| 534 void setIgnoreSingleTap(boolean value) { |
| 535 mIgnoreSingleTap = value; |
| 536 } |
| 537 |
| 538 private float calculateDragAngle(float dx, float dy) { |
| 539 dx = Math.abs(dx); |
| 540 dy = Math.abs(dy); |
| 541 return (float) Math.atan2(dy, dx); |
| 542 } |
| 543 |
| 544 private void setClickXAndY(int x, int y) { |
| 545 mSingleTapX = x; |
| 546 mSingleTapY = y; |
| 547 } |
| 548 |
| 549 /** |
| 550 * @return The x coordinate for the last point that a singleTap gesture was
initiated from. |
| 551 */ |
| 552 public int getSingleTapX() { |
| 553 return mSingleTapX; |
| 554 } |
| 555 |
| 556 /** |
| 557 * @return The y coordinate for the last point that a singleTap gesture was
initiated from. |
| 558 */ |
| 559 public int getSingleTapY() { |
| 560 return mSingleTapY; |
| 561 } |
| 562 |
| 563 /** |
| 564 * Handle the incoming MotionEvent. |
| 565 * @return Whether the event was handled. |
| 566 */ |
| 567 boolean onTouchEvent(MotionEvent event) { |
| 568 TraceEvent.begin("onTouchEvent"); |
| 569 mLongPressDetector.cancelLongPressIfNeeded(event); |
| 570 // Notify native that scrolling has stopped whenever a down action is pr
ocessed prior to |
| 571 // passing the event to native as it will drop them as an optimization i
f scrolling is |
| 572 // enabled. Ending the fling ensures scrolling has stopped as well as t
erminating the |
| 573 // current fling if applicable. |
| 574 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { |
| 575 endFling(event.getEventTime()); |
| 576 } |
| 577 |
| 578 if (offerTouchEventToJavaScript(event)) { |
| 579 // offerTouchEventToJavaScript returns true to indicate the event wa
s sent |
| 580 // to the render process. If it is not subsequently handled, it will |
| 581 // be returned via confirmTouchEvent(false) and eventually passed to |
| 582 // processTouchEvent asynchronously. |
| 583 TraceEvent.end("onTouchEvent"); |
| 584 return true; |
| 585 } |
| 586 return processTouchEvent(event); |
| 587 } |
| 588 |
| 589 /** |
| 590 * Sets the flag indicating that the content has registered listeners for to
uch events. |
| 591 */ |
| 592 void didSetNeedTouchEvents(boolean needTouchEvents) { |
| 593 mNeedTouchEvents = needTouchEvents; |
| 594 // When mainframe is loading, FrameLoader::transitionToCommitted will |
| 595 // call this method to set mNeedTouchEvents to false. We use this as |
| 596 // an indicator to clear the pending motion events so that events from |
| 597 // the previous page will not be carried over to the new page. |
| 598 if (!mNeedTouchEvents) mPendingMotionEvents.clear(); |
| 599 } |
| 600 |
| 601 private boolean offerTouchEventToJavaScript(MotionEvent event) { |
| 602 mLongPressDetector.onOfferTouchEventToJavaScript(event); |
| 603 |
| 604 if (!mNeedTouchEvents) return false; |
| 605 |
| 606 if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { |
| 607 // Only send move events if the move has exceeded the slop threshold
. |
| 608 if (!mLongPressDetector.confirmOfferMoveEventToJavaScript(event)) { |
| 609 return true; |
| 610 } |
| 611 // Avoid flooding the renderer process with move events: if the prev
ious pending |
| 612 // command is also a move (common case), skip sending this event to
the webkit |
| 613 // side and collapse it into the pending event. |
| 614 Pair<MotionEvent, Boolean> previousEvent = mPendingMotionEvents.peek
Last(); |
| 615 if (previousEvent != null && previousEvent.second == true |
| 616 && previousEvent.first.getActionMasked() == MotionEvent.ACTI
ON_MOVE |
| 617 && previousEvent.first.getPointerCount() == event.getPointer
Count()) { |
| 618 MotionEvent.PointerCoords[] coords = |
| 619 new MotionEvent.PointerCoords[event.getPointerCount()]; |
| 620 for (int i = 0; i < coords.length; ++i) { |
| 621 coords[i] = new MotionEvent.PointerCoords(); |
| 622 event.getPointerCoords(i, coords[i]); |
| 623 } |
| 624 previousEvent.first.addBatch(event.getEventTime(), coords, event
.getMetaState()); |
| 625 return true; |
| 626 } |
| 627 } |
| 628 |
| 629 TouchPoint[] pts = new TouchPoint[event.getPointerCount()]; |
| 630 int type = TouchPoint.createTouchPoints(event, pts); |
| 631 |
| 632 boolean forwarded = false; |
| 633 if (type != TouchPoint.CONVERSION_ERROR && !mNativeScrolling && !mPinchI
nProgress) { |
| 634 mTouchCancelEventSent = false; |
| 635 forwarded = mMotionEventDelegate.sendTouchEvent(event.getEventTime()
, type, pts); |
| 636 } else if ((mNativeScrolling || mPinchInProgress) && !mTouchCancelEventS
ent) { |
| 637 forwarded = mMotionEventDelegate.sendTouchEvent(event.getEventTime()
, |
| 638 TouchPoint.TOUCH_EVENT_TYPE_CANCEL, pts); |
| 639 mTouchCancelEventSent = true; |
| 640 } |
| 641 if (forwarded || !mPendingMotionEvents.isEmpty()) { |
| 642 // Copy the event, as the original may get mutated after this method
returns. |
| 643 mPendingMotionEvents.add(Pair.create(MotionEvent.obtain(event), forw
arded)); |
| 644 // TODO(joth): If needed, start a watchdog timer to pump mPendingMot
ionEvents |
| 645 // in the case of the WebKit renderer / JS being unresponsive. |
| 646 return true; |
| 647 } |
| 648 return false; |
| 649 } |
| 650 |
| 651 private boolean processTouchEvent(MotionEvent event) { |
| 652 boolean handled = false; |
| 653 // The last "finger up" is an end to scrolling but may not be |
| 654 // an end to movement (e.g. fling scroll). We do not tell |
| 655 // native code to end scrolling until we are sure we did not |
| 656 // fling. |
| 657 boolean possiblyEndMovement = false; |
| 658 |
| 659 // "Last finger raised" could be an end to movement. However, |
| 660 // give the mSimpleTouchDetector a chance to continue |
| 661 // scrolling with a fling. |
| 662 if ((event.getAction() == MotionEvent.ACTION_UP) && |
| 663 (event.getPointerCount() == 1)) { |
| 664 if (mNativeScrolling) { |
| 665 possiblyEndMovement = true; |
| 666 } |
| 667 } |
| 668 |
| 669 if (mLongPressDetector.canHandle(event)) { |
| 670 mLongPressDetector.startLongPressTimerIfNeeded(event); |
| 671 } |
| 672 // Use the framework's GestureDetector to detect pans and zooms not alre
ady |
| 673 // handled by the WebKit touch events gesture manager. |
| 674 if (canHandle(event)) { |
| 675 handled |= mGestureDetector.onTouchEvent(event); |
| 676 if (event.getAction() == MotionEvent.ACTION_DOWN) mCurrentDownEvent
= event; |
| 677 } |
| 678 |
| 679 handled |= mZoomManager.processTouchEvent(event); |
| 680 |
| 681 if (possiblyEndMovement && !handled) { |
| 682 tellNativeScrollingHasEnded(event.getEventTime()); |
| 683 } |
| 684 |
| 685 return handled; |
| 686 } |
| 687 |
| 688 /** |
| 689 * Respond to a MotionEvent being returned from the native side. |
| 690 * @param handled Whether the MotionEvent was handled on the native side. |
| 691 */ |
| 692 void confirmTouchEvent(boolean handled) { |
| 693 MotionEvent eventToPassThrough = null; |
| 694 if (mPendingMotionEvents.isEmpty()) { |
| 695 Log.w(TAG, "confirmTouchEvent with Empty pending list!"); |
| 696 return; |
| 697 } |
| 698 TraceEvent.begin(); |
| 699 Pair<MotionEvent, Boolean> event = mPendingMotionEvents.removeFirst(); |
| 700 if (!handled) { |
| 701 if (!processTouchEvent(event.first)) { |
| 702 // TODO(joth): If the Java side gesture handler also fails to co
nsume |
| 703 // this deferred event, should it be bubbled up to the parent vi
ew? |
| 704 Log.w(TAG, "Unhandled deferred touch event"); |
| 705 } |
| 706 } else { |
| 707 mZoomManager.passTouchEventThrough(event.first); |
| 708 } |
| 709 |
| 710 // Now process all events that are in the queue but not sent to the nati
ve. |
| 711 Pair<MotionEvent, Boolean> nextEvent = mPendingMotionEvents.peekFirst(); |
| 712 while (nextEvent != null && nextEvent.second == false) { |
| 713 processTouchEvent(nextEvent.first); |
| 714 mPendingMotionEvents.removeFirst(); |
| 715 nextEvent.first.recycle(); |
| 716 nextEvent = mPendingMotionEvents.peekFirst(); |
| 717 } |
| 718 |
| 719 // We may have pending events that could cancel the timers: |
| 720 // For instance, if we received an UP before the DOWN completed |
| 721 // its roundtrip (so it didn't cancel the timer during onTouchEvent()). |
| 722 mLongPressDetector.cancelLongPressIfNeeded(mPendingMotionEvents.iterator
()); |
| 723 event.first.recycle(); |
| 724 TraceEvent.end(); |
| 725 } |
| 726 |
| 727 /** |
| 728 * @return Whether the ContentViewGestureHandler can handle a MotionEvent ri
ght now. True only |
| 729 * if it's the start of a new stream (ACTION_DOWN), or a continuation of the
current stream. |
| 730 */ |
| 731 boolean canHandle(MotionEvent ev) { |
| 732 return ev.getAction() == MotionEvent.ACTION_DOWN || |
| 733 (mCurrentDownEvent != null && mCurrentDownEvent.getDownTime() ==
ev.getDownTime()); |
| 734 } |
| 735 |
| 736 } |
OLD | NEW |