Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1022)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java

Issue 10790066: Enable gesture events handling on Android. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Added comments to avoid confusion when merging downstream Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698