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

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 remaining flags and removed unnecessary comments Created 8 years, 5 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.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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698