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.util.Log; |
| 10 import android.util.Pair; |
| 11 import android.view.GestureDetector; |
| 12 import android.view.GestureDetector.OnGestureListener; |
| 13 import android.view.MotionEvent; |
| 14 import android.view.ViewConfiguration; |
| 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 final Bundle mExtraParamBundle; |
| 47 private GestureDetector mGestureDetector; |
| 48 private final ZoomManager mZoomManager; |
| 49 private LongPressDetector mLongPressDetector; |
| 50 private OnGestureListener mListener; |
| 51 private MotionEvent mCurrentDownEvent; |
| 52 private final 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 long press detector, gesture detector and li
stener. |
| 206 * This is used for testing only. |
| 207 * @param longPressDetector The new LongPressDetector to be assigned. |
| 208 * @param gestureDetector The new GestureDetector to be assigned. |
| 209 * @param listener The new onGestureListener to be assigned. |
| 210 */ |
| 211 void setTestDependencies( |
| 212 LongPressDetector longPressDetector, GestureDetector gestureDetector
, |
| 213 OnGestureListener listener) { |
| 214 mLongPressDetector = longPressDetector; |
| 215 mGestureDetector = gestureDetector; |
| 216 mListener = listener; |
| 217 } |
| 218 |
| 219 private void initGestureDetectors(final Context context) { |
| 220 int scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(
); |
| 221 mScaledTouchSlopSquare = scaledTouchSlop * scaledTouchSlop; |
| 222 try { |
| 223 TraceEvent.begin(); |
| 224 GestureDetector.SimpleOnGestureListener listener = |
| 225 new GestureDetector.SimpleOnGestureListener() { |
| 226 @Override |
| 227 public boolean onDown(MotionEvent e) { |
| 228 mShowPressIsCalled = false; |
| 229 mIgnoreSingleTap = false; |
| 230 mSeenFirstScroll = false; |
| 231 mNativeScrolling = false; |
| 232 mSnapScrollMode = SNAP_NONE; |
| 233 mLastRawX = e.getRawX(); |
| 234 mLastRawY = e.getRawY(); |
| 235 mAccumulatedScrollErrorX = 0; |
| 236 mAccumulatedScrollErrorY = 0; |
| 237 // Return true to indicate that we want to handle touch |
| 238 return true; |
| 239 } |
| 240 |
| 241 @Override |
| 242 public boolean onScroll(MotionEvent e1, MotionEvent e2, |
| 243 float distanceX, float distanceY) { |
| 244 // Scroll snapping |
| 245 if (!mSeenFirstScroll) { |
| 246 mAverageAngle = calculateDragAngle(distanceX, distan
ceY); |
| 247 // Initial scroll event |
| 248 if (!mZoomManager.isScaleGestureDetectionInProgress(
)) { |
| 249 // if it starts nearly horizontal or vertical, e
nforce it |
| 250 if (mAverageAngle < HSLOPE_TO_START_SNAP) { |
| 251 mSnapScrollMode = SNAP_HORIZ; |
| 252 mAverageAngle = ANGLE_HORIZ; |
| 253 } else if (mAverageAngle > VSLOPE_TO_START_SNAP)
{ |
| 254 mSnapScrollMode = SNAP_VERT; |
| 255 mAverageAngle = ANGLE_VERT; |
| 256 } |
| 257 } |
| 258 mSeenFirstScroll = true; |
| 259 // Ignore the first scroll delta to avoid a visible
jump. |
| 260 return true; |
| 261 } else { |
| 262 mAverageAngle += |
| 263 (calculateDragAngle(distanceX, distanceY) - mAve
rageAngle) |
| 264 / MMA_WEIGHT_N; |
| 265 if (mSnapScrollMode != SNAP_NONE) { |
| 266 if ((mSnapScrollMode == SNAP_VERT |
| 267 && mAverageAngle < VSLOPE_TO_BREAK_SNAP) |
| 268 || (mSnapScrollMode == SNAP_HORIZ |
| 269 && mAverageAngle > HSLOPE_TO_BRE
AK_SNAP)) { |
| 270 // radical change means getting out of snap
mode |
| 271 mSnapScrollMode = SNAP_NONE; |
| 272 } |
| 273 } else { |
| 274 if (!mZoomManager.isScaleGestureDetectionInProgr
ess()) { |
| 275 if (mAverageAngle < HSLOPE_TO_START_SNAP) { |
| 276 mSnapScrollMode = SNAP_HORIZ; |
| 277 mAverageAngle = (mAverageAngle + ANGLE_H
ORIZ) / 2; |
| 278 } else if (mAverageAngle > VSLOPE_TO_START_S
NAP) { |
| 279 mSnapScrollMode = SNAP_VERT; |
| 280 mAverageAngle = (mAverageAngle + ANGLE_V
ERT) / 2; |
| 281 } |
| 282 } |
| 283 } |
| 284 } |
| 285 |
| 286 if (mSnapScrollMode != SNAP_NONE) { |
| 287 if (mSnapScrollMode == SNAP_HORIZ) { |
| 288 distanceY = 0; |
| 289 } else { |
| 290 distanceX = 0; |
| 291 } |
| 292 } |
| 293 |
| 294 boolean didUIStealScroll = mMotionEventDelegate.didUISte
alScroll( |
| 295 e2.getRawX() - mLastRawX, e2.getRawY() - mLastRa
wY); |
| 296 |
| 297 mLastRawX = e2.getRawX(); |
| 298 mLastRawY = e2.getRawY(); |
| 299 if (didUIStealScroll) return true; |
| 300 if (!mNativeScrolling && mMotionEventDelegate.sendGestur
e( |
| 301 GESTURE_SCROLL_START, e1.getEventTime(), |
| 302 (int) e1.getX(), (int) e1.getY(), null))
{ |
| 303 mNativeScrolling = true; |
| 304 |
| 305 } |
| 306 // distanceX and distanceY is the scrolling offset since
last onScroll. |
| 307 // Because we are passing integers to webkit, this could
introduce |
| 308 // rounding errors. The rounding errors will accumulate
overtime. |
| 309 // To solve this, we should adding back the rounding err
ors each time |
| 310 // when we calculate the new offset. |
| 311 int dx = (int) (distanceX + mAccumulatedScrollErrorX); |
| 312 int dy = (int) (distanceY + mAccumulatedScrollErrorY); |
| 313 mAccumulatedScrollErrorX = distanceX + mAccumulatedScrol
lErrorX - dx; |
| 314 mAccumulatedScrollErrorY = distanceY + mAccumulatedScrol
lErrorY - dy; |
| 315 if ((dx | dy) != 0) { |
| 316 mMotionEventDelegate.sendGesture(GESTURE_SCROLL_BY, |
| 317 e2.getEventTime(), dx, dy, null); |
| 318 } |
| 319 |
| 320 mMotionEventDelegate.invokeZoomPicker(); |
| 321 |
| 322 return true; |
| 323 } |
| 324 |
| 325 @Override |
| 326 public boolean onFling(MotionEvent e1, MotionEvent e2, |
| 327 float velocityX, float velocityY) { |
| 328 if (mSnapScrollMode == SNAP_NONE) { |
| 329 float flingAngle = calculateDragAngle(velocityX, vel
ocityY); |
| 330 if (flingAngle < HSLOPE_TO_START_SNAP) { |
| 331 mSnapScrollMode = SNAP_HORIZ; |
| 332 mAverageAngle = ANGLE_HORIZ; |
| 333 } else if (flingAngle > VSLOPE_TO_START_SNAP) { |
| 334 mSnapScrollMode = SNAP_VERT; |
| 335 mAverageAngle = ANGLE_VERT; |
| 336 } |
| 337 } |
| 338 |
| 339 if (mSnapScrollMode != SNAP_NONE) { |
| 340 if (mSnapScrollMode == SNAP_HORIZ) { |
| 341 velocityY = 0; |
| 342 } else { |
| 343 velocityX = 0; |
| 344 } |
| 345 } |
| 346 |
| 347 fling(e1.getEventTime(),(int) e1.getX(0), (int) e1.getY(
0), |
| 348 (int) velocityX, (int) velocityY); |
| 349 return true; |
| 350 } |
| 351 |
| 352 @Override |
| 353 public void onShowPress(MotionEvent e) { |
| 354 mShowPressIsCalled = true; |
| 355 mMotionEventDelegate.sendGesture(GESTURE_SHOW_PRESSED_ST
ATE, |
| 356 e.getEventTime(), (int) e.getX(), (int) e.getY()
, null); |
| 357 } |
| 358 |
| 359 @Override |
| 360 public boolean onSingleTapUp(MotionEvent e) { |
| 361 if (isDistanceBetweenDownAndUpTooLong(e.getRawX(), e.get
RawY())) { |
| 362 mIgnoreSingleTap = true; |
| 363 return true; |
| 364 } |
| 365 // This is a hack to address the issue where user hovers |
| 366 // over a link for longer than DOUBLE_TAP_TIMEOUT, then |
| 367 // onSingleTapConfirmed() is not triggered. But we still |
| 368 // want to trigger the tap event at UP. So we override |
| 369 // onSingleTapUp() in this case. This assumes singleTapU
p |
| 370 // gets always called before singleTapConfirmed. |
| 371 if (!mIgnoreSingleTap && !mLongPressDetector.isInLongPre
ss() && |
| 372 (e.getEventTime() - e.getDownTime() > DOUBLE_TAP
_TIMEOUT)) { |
| 373 float x = e.getX(); |
| 374 float y = e.getY(); |
| 375 if (mMotionEventDelegate.sendGesture(GESTURE_SINGLE_
TAP_UP, |
| 376 e.getEventTime(), (int) x, (int) y, null)) { |
| 377 mIgnoreSingleTap = true; |
| 378 } |
| 379 setClickXAndY((int) x, (int) y); |
| 380 return true; |
| 381 } |
| 382 return false; |
| 383 } |
| 384 |
| 385 @Override |
| 386 public boolean onSingleTapConfirmed(MotionEvent e) { |
| 387 // Long taps in the edges of the screen have their event
s delayed by |
| 388 // ChromeViewHolder for tab swipe operations. As a conse
quence of the delay |
| 389 // this method might be called after receiving the up ev
ent. |
| 390 // These corner cases should be ignored. |
| 391 if (mLongPressDetector.isInLongPress() || mIgnoreSingleT
ap) return true; |
| 392 |
| 393 int x = (int) e.getX(); |
| 394 int y = (int) e.getY(); |
| 395 mExtraParamBundle.clear(); |
| 396 mExtraParamBundle.putBoolean(SHOW_PRESS, mShowPressIsCal
led); |
| 397 mMotionEventDelegate.sendGesture(GESTURE_SINGLE_TAP_CONF
IRMED, |
| 398 e.getEventTime(), x, y, mExtraParamBundle); |
| 399 setClickXAndY(x, y); |
| 400 return true; |
| 401 } |
| 402 |
| 403 @Override |
| 404 public boolean onDoubleTap(MotionEvent e) { |
| 405 mMotionEventDelegate.sendGesture(GESTURE_DOUBLE_TAP, |
| 406 e.getEventTime(), (int) e.getX(), (int) e.getY()
, null); |
| 407 return true; |
| 408 } |
| 409 |
| 410 @Override |
| 411 public void onLongPress(MotionEvent e) { |
| 412 if (!mZoomManager.isScaleGestureDetectionInProgress()) { |
| 413 mMotionEventDelegate.sendGesture(GESTURE_LONG_PRESS, |
| 414 e.getEventTime(), (int) e.getX(), (int) e.ge
tY(), null); |
| 415 } |
| 416 } |
| 417 |
| 418 /** |
| 419 * This method inspects the distance between where the user
started touching |
| 420 * the surface, and where she released. If the points are to
o far apart, we |
| 421 * should assume that the web page has consumed the scroll-e
vents in-between, |
| 422 * and as such, this should not be considered a single-tap. |
| 423 * |
| 424 * We use the Android frameworks notion of how far a touch c
an wander before |
| 425 * we think the user is scrolling. |
| 426 * |
| 427 * @param x the new x coordinate |
| 428 * @param y the new y coordinate |
| 429 * @return true if the distance is too long to be considered
a single tap |
| 430 */ |
| 431 private boolean isDistanceBetweenDownAndUpTooLong(float x, f
loat y) { |
| 432 double deltaX = mLastRawX - x; |
| 433 double deltaY = mLastRawY - y; |
| 434 return deltaX * deltaX + deltaY * deltaY > mScaledTouchS
lopSquare; |
| 435 } |
| 436 }; |
| 437 mListener = listener; |
| 438 mGestureDetector = new GestureDetector(context, listener); |
| 439 mGestureDetector.setIsLongpressEnabled(false); |
| 440 } finally { |
| 441 TraceEvent.end(); |
| 442 } |
| 443 } |
| 444 |
| 445 /** |
| 446 * @return LongPressDetector handling setting up timers for and canceling Lo
ngPress gestures. |
| 447 */ |
| 448 LongPressDetector getLongPressDetector() { |
| 449 return mLongPressDetector; |
| 450 } |
| 451 |
| 452 /** |
| 453 * @param event Start a LongPress gesture event from the listener. |
| 454 */ |
| 455 @Override |
| 456 public void onLongPress(MotionEvent event) { |
| 457 mListener.onLongPress(event); |
| 458 } |
| 459 |
| 460 /** |
| 461 * Cancels any ongoing LongPress timers. |
| 462 */ |
| 463 void cancelLongPress() { |
| 464 mLongPressDetector.cancelLongPress(); |
| 465 } |
| 466 |
| 467 /** |
| 468 * Fling the ContentView from the current position. |
| 469 * @param x Fling touch starting position |
| 470 * @param y Fling touch starting position |
| 471 * @param velocityX Initial velocity of the fling (X) measured in pixels per
second. |
| 472 * @param velocityY Initial velocity of the fling (Y) measured in pixels per
second. |
| 473 */ |
| 474 void fling(long timeMs, int x, int y, int velocityX, int velocityY) { |
| 475 endFling(timeMs); |
| 476 mExtraParamBundle.clear(); |
| 477 mExtraParamBundle.putInt(VELOCITY_X, velocityX); |
| 478 mExtraParamBundle.putInt(VELOCITY_Y, velocityY); |
| 479 mMotionEventDelegate.sendGesture(GESTURE_FLING_START, |
| 480 timeMs, x, y, mExtraParamBundle); |
| 481 } |
| 482 |
| 483 /** |
| 484 * Send a FlingCancel gesture event and also cancel scrolling if it is activ
e. |
| 485 * @param timeMs The time in ms for the event initiating this gesture. |
| 486 */ |
| 487 void endFling(long timeMs) { |
| 488 mMotionEventDelegate.sendGesture(GESTURE_FLING_CANCEL, timeMs, 0, 0, nul
l); |
| 489 tellNativeScrollingHasEnded(timeMs); |
| 490 } |
| 491 |
| 492 // If native thinks scrolling (or fling-scrolling) is going on, tell native |
| 493 // it has ended. |
| 494 private void tellNativeScrollingHasEnded(long timeMs) { |
| 495 if (mNativeScrolling) { |
| 496 mNativeScrolling = false; |
| 497 mMotionEventDelegate.sendGesture(GESTURE_SCROLL_END, timeMs, 0, 0, n
ull); |
| 498 } |
| 499 } |
| 500 |
| 501 /** |
| 502 * Starts a pinch gesture. |
| 503 * @param timeMs The time in ms for the event initiating this gesture. |
| 504 * @param x The x coordinate for the event initiating this gesture. |
| 505 * @param y The x coordinate for the event initiating this gesture. |
| 506 */ |
| 507 void pinchBegin(long timeMs, int x, int y) { |
| 508 mMotionEventDelegate.sendGesture(GESTURE_PINCH_BEGIN, timeMs, x, y, null
); |
| 509 } |
| 510 |
| 511 /** |
| 512 * Pinch by a given percentage. |
| 513 * @param timeMs The time in ms for the event initiating this gesture. |
| 514 * @param anchorX The x coordinate for the anchor point to be used in pinch. |
| 515 * @param anchorY The y coordinate for the anchor point to be used in pinch. |
| 516 * @param delta The percentage to pinch by. |
| 517 */ |
| 518 void pinchBy(long timeMs, int anchorX, int anchorY, float delta) { |
| 519 mExtraParamBundle.clear(); |
| 520 mExtraParamBundle.putFloat(DELTA, delta); |
| 521 mMotionEventDelegate.sendGesture(GESTURE_PINCH_BY, |
| 522 timeMs, anchorX, anchorY, mExtraParamBundle); |
| 523 mPinchInProgress = true; |
| 524 } |
| 525 |
| 526 /** |
| 527 * End a pinch gesture. |
| 528 * @param timeMs The time in ms for the event initiating this gesture. |
| 529 */ |
| 530 void pinchEnd(long timeMs) { |
| 531 mMotionEventDelegate.sendGesture(GESTURE_PINCH_END, timeMs, 0, 0, null); |
| 532 mPinchInProgress = false; |
| 533 } |
| 534 |
| 535 /** |
| 536 * Ignore singleTap gestures. |
| 537 */ |
| 538 void setIgnoreSingleTap(boolean value) { |
| 539 mIgnoreSingleTap = value; |
| 540 } |
| 541 |
| 542 private float calculateDragAngle(float dx, float dy) { |
| 543 dx = Math.abs(dx); |
| 544 dy = Math.abs(dy); |
| 545 return (float) Math.atan2(dy, dx); |
| 546 } |
| 547 |
| 548 private void setClickXAndY(int x, int y) { |
| 549 mSingleTapX = x; |
| 550 mSingleTapY = y; |
| 551 } |
| 552 |
| 553 /** |
| 554 * @return The x coordinate for the last point that a singleTap gesture was
initiated from. |
| 555 */ |
| 556 public int getSingleTapX() { |
| 557 return mSingleTapX; |
| 558 } |
| 559 |
| 560 /** |
| 561 * @return The y coordinate for the last point that a singleTap gesture was
initiated from. |
| 562 */ |
| 563 public int getSingleTapY() { |
| 564 return mSingleTapY; |
| 565 } |
| 566 |
| 567 /** |
| 568 * Handle the incoming MotionEvent. |
| 569 * @return Whether the event was handled. |
| 570 */ |
| 571 boolean onTouchEvent(MotionEvent event) { |
| 572 TraceEvent.begin("onTouchEvent"); |
| 573 mLongPressDetector.cancelLongPressIfNeeded(event); |
| 574 // Notify native that scrolling has stopped whenever a down action is pr
ocessed prior to |
| 575 // passing the event to native as it will drop them as an optimization i
f scrolling is |
| 576 // enabled. Ending the fling ensures scrolling has stopped as well as t
erminating the |
| 577 // current fling if applicable. |
| 578 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { |
| 579 endFling(event.getEventTime()); |
| 580 } |
| 581 |
| 582 if (offerTouchEventToJavaScript(event)) { |
| 583 // offerTouchEventToJavaScript returns true to indicate the event wa
s sent |
| 584 // to the render process. If it is not subsequently handled, it will |
| 585 // be returned via confirmTouchEvent(false) and eventually passed to |
| 586 // processTouchEvent asynchronously. |
| 587 TraceEvent.end("onTouchEvent"); |
| 588 return true; |
| 589 } |
| 590 return processTouchEvent(event); |
| 591 } |
| 592 |
| 593 /** |
| 594 * Sets the flag indicating that the content has registered listeners for to
uch events. |
| 595 */ |
| 596 void didSetNeedTouchEvents(boolean needTouchEvents) { |
| 597 mNeedTouchEvents = needTouchEvents; |
| 598 // When mainframe is loading, FrameLoader::transitionToCommitted will |
| 599 // call this method to set mNeedTouchEvents to false. We use this as |
| 600 // an indicator to clear the pending motion events so that events from |
| 601 // the previous page will not be carried over to the new page. |
| 602 if (!mNeedTouchEvents) mPendingMotionEvents.clear(); |
| 603 } |
| 604 |
| 605 private boolean offerTouchEventToJavaScript(MotionEvent event) { |
| 606 mLongPressDetector.onOfferTouchEventToJavaScript(event); |
| 607 |
| 608 if (!mNeedTouchEvents) return false; |
| 609 |
| 610 if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { |
| 611 // Only send move events if the move has exceeded the slop threshold
. |
| 612 if (!mLongPressDetector.confirmOfferMoveEventToJavaScript(event)) { |
| 613 return true; |
| 614 } |
| 615 // Avoid flooding the renderer process with move events: if the prev
ious pending |
| 616 // command is also a move (common case), skip sending this event to
the webkit |
| 617 // side and collapse it into the pending event. |
| 618 Pair<MotionEvent, Boolean> previousEvent = mPendingMotionEvents.peek
Last(); |
| 619 if (previousEvent != null && previousEvent.second == true |
| 620 && previousEvent.first.getActionMasked() == MotionEvent.ACTI
ON_MOVE |
| 621 && previousEvent.first.getPointerCount() == event.getPointer
Count()) { |
| 622 MotionEvent.PointerCoords[] coords = |
| 623 new MotionEvent.PointerCoords[event.getPointerCount()]; |
| 624 for (int i = 0; i < coords.length; ++i) { |
| 625 coords[i] = new MotionEvent.PointerCoords(); |
| 626 event.getPointerCoords(i, coords[i]); |
| 627 } |
| 628 previousEvent.first.addBatch(event.getEventTime(), coords, event
.getMetaState()); |
| 629 return true; |
| 630 } |
| 631 } |
| 632 |
| 633 TouchPoint[] pts = new TouchPoint[event.getPointerCount()]; |
| 634 int type = TouchPoint.createTouchPoints(event, pts); |
| 635 |
| 636 boolean forwarded = false; |
| 637 if (type != TouchPoint.CONVERSION_ERROR && !mNativeScrolling && !mPinchI
nProgress) { |
| 638 mTouchCancelEventSent = false; |
| 639 forwarded = mMotionEventDelegate.sendTouchEvent(event.getEventTime()
, type, pts); |
| 640 } else if ((mNativeScrolling || mPinchInProgress) && !mTouchCancelEventS
ent) { |
| 641 forwarded = mMotionEventDelegate.sendTouchEvent(event.getEventTime()
, |
| 642 TouchPoint.TOUCH_EVENT_TYPE_CANCEL, pts); |
| 643 mTouchCancelEventSent = true; |
| 644 } |
| 645 if (forwarded || !mPendingMotionEvents.isEmpty()) { |
| 646 // Copy the event, as the original may get mutated after this method
returns. |
| 647 mPendingMotionEvents.add(Pair.create(MotionEvent.obtain(event), forw
arded)); |
| 648 // TODO(joth): If needed, start a watchdog timer to pump mPendingMot
ionEvents |
| 649 // in the case of the WebKit renderer / JS being unresponsive. |
| 650 return true; |
| 651 } |
| 652 return false; |
| 653 } |
| 654 |
| 655 private boolean processTouchEvent(MotionEvent event) { |
| 656 boolean handled = false; |
| 657 // The last "finger up" is an end to scrolling but may not be |
| 658 // an end to movement (e.g. fling scroll). We do not tell |
| 659 // native code to end scrolling until we are sure we did not |
| 660 // fling. |
| 661 boolean possiblyEndMovement = false; |
| 662 |
| 663 // "Last finger raised" could be an end to movement. However, |
| 664 // give the mSimpleTouchDetector a chance to continue |
| 665 // scrolling with a fling. |
| 666 if ((event.getAction() == MotionEvent.ACTION_UP) && |
| 667 (event.getPointerCount() == 1)) { |
| 668 if (mNativeScrolling) { |
| 669 possiblyEndMovement = true; |
| 670 } |
| 671 } |
| 672 |
| 673 mLongPressDetector.startLongPressTimerIfNeeded(event); |
| 674 |
| 675 // Use the framework's GestureDetector to detect pans and zooms not alre
ady |
| 676 // handled by the WebKit touch events gesture manager. |
| 677 if (canHandle(event)) { |
| 678 handled |= mGestureDetector.onTouchEvent(event); |
| 679 if (event.getAction() == MotionEvent.ACTION_DOWN) mCurrentDownEvent
= event; |
| 680 } |
| 681 |
| 682 handled |= mZoomManager.processTouchEvent(event); |
| 683 |
| 684 if (possiblyEndMovement && !handled) { |
| 685 tellNativeScrollingHasEnded(event.getEventTime()); |
| 686 } |
| 687 |
| 688 return handled; |
| 689 } |
| 690 |
| 691 /** |
| 692 * Respond to a MotionEvent being returned from the native side. |
| 693 * @param handled Whether the MotionEvent was handled on the native side. |
| 694 */ |
| 695 void confirmTouchEvent(boolean handled) { |
| 696 MotionEvent eventToPassThrough = null; |
| 697 if (mPendingMotionEvents.isEmpty()) { |
| 698 Log.w(TAG, "confirmTouchEvent with Empty pending list!"); |
| 699 return; |
| 700 } |
| 701 TraceEvent.begin(); |
| 702 Pair<MotionEvent, Boolean> event = mPendingMotionEvents.removeFirst(); |
| 703 if (!handled) { |
| 704 if (!processTouchEvent(event.first)) { |
| 705 // TODO(joth): If the Java side gesture handler also fails to co
nsume |
| 706 // this deferred event, should it be bubbled up to the parent vi
ew? |
| 707 Log.w(TAG, "Unhandled deferred touch event"); |
| 708 } |
| 709 } else { |
| 710 mZoomManager.passTouchEventThrough(event.first); |
| 711 } |
| 712 |
| 713 // Now process all events that are in the queue but not sent to the nati
ve. |
| 714 Pair<MotionEvent, Boolean> nextEvent = mPendingMotionEvents.peekFirst(); |
| 715 while (nextEvent != null && nextEvent.second == false) { |
| 716 processTouchEvent(nextEvent.first); |
| 717 mPendingMotionEvents.removeFirst(); |
| 718 nextEvent.first.recycle(); |
| 719 nextEvent = mPendingMotionEvents.peekFirst(); |
| 720 } |
| 721 |
| 722 // We may have pending events that could cancel the timers: |
| 723 // For instance, if we received an UP before the DOWN completed |
| 724 // its roundtrip (so it didn't cancel the timer during onTouchEvent()). |
| 725 mLongPressDetector.cancelLongPressIfNeeded(mPendingMotionEvents.iterator
()); |
| 726 event.first.recycle(); |
| 727 TraceEvent.end(); |
| 728 } |
| 729 |
| 730 /** |
| 731 * @return Whether the ContentViewGestureHandler can handle a MotionEvent ri
ght now. True only |
| 732 * if it's the start of a new stream (ACTION_DOWN), or a continuation of the
current stream. |
| 733 */ |
| 734 boolean canHandle(MotionEvent ev) { |
| 735 return ev.getAction() == MotionEvent.ACTION_DOWN || |
| 736 (mCurrentDownEvent != null && mCurrentDownEvent.getDownTime() ==
ev.getDownTime()); |
| 737 } |
| 738 |
| 739 } |
OLD | NEW |