EMMA Coverage Report (generated Fri Aug 23 16:39:17 PDT 2013)
[all classes][org.chromium.content.browser]

COVERAGE SUMMARY FOR SOURCE FILE [ContentViewGestureHandler.java]

nameclass, %method, %block, %line, %
ContentViewGestureHandler.java100% (3/3)94%  (100/106)91%  (1924/2124)89%  (369.4/414)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ContentViewGestureHandler$TouchEventTimeoutHandler100% (1/1)100% (9/9)86%  (145/168)91%  (34.6/38)
start (long, TouchPoint []): void 100% (1/1)74%  (23/31)85%  (5.1/6)
<static initializer> 100% (1/1)75%  (6/8)75%  (0.8/1)
mockTimeout (): void 100% (1/1)75%  (12/16)89%  (3.6/4)
confirmTouchEvent (): boolean 100% (1/1)83%  (44/53)87%  (13/15)
ContentViewGestureHandler$TouchEventTimeoutHandler (ContentViewGestureHandler... 100% (1/1)100% (11/11)100% (2/2)
ContentViewGestureHandler$TouchEventTimeoutHandler (ContentViewGestureHandler... 100% (1/1)100% (4/4)100% (1/1)
hasScheduledTimeoutEventForTesting (): boolean 100% (1/1)100% (10/10)100% (1/1)
hasTimeoutEvent (): boolean 100% (1/1)100% (7/7)100% (1/1)
run (): void 100% (1/1)100% (28/28)100% (9/9)
     
class ContentViewGestureHandler100% (1/1)93%  (81/87)90%  (1106/1235)88%  (225.3/256)
cancelLongPress (): void 0%   (0/1)0%   (0/4)0%   (0/2)
getSingleTapX (): int 0%   (0/1)0%   (0/3)0%   (0/1)
getSingleTapY (): int 0%   (0/1)0%   (0/3)0%   (0/1)
isNativePinching (): boolean 0%   (0/1)0%   (0/3)0%   (0/1)
setIgnoreRemainingTouchEvents (): void 0%   (0/1)0%   (0/9)0%   (0/3)
setIgnoreSingleTap (boolean): void 0%   (0/1)0%   (0/4)0%   (0/2)
trySendPendingEventsToNative (): void 100% (1/1)43%  (12/28)50%  (4/8)
endTouchScrollIfNecessary (long, boolean): void 100% (1/1)50%  (9/18)75%  (3.8/5)
hasTouchEventHandlers (boolean): void 100% (1/1)70%  (7/10)89%  (2.7/3)
onTouchEvent (MotionEvent): boolean 100% (1/1)74%  (52/70)80%  (13.6/17)
<static initializer> 100% (1/1)80%  (8/10)90%  (1.8/2)
updateDoubleTapDragSupport (boolean): void 100% (1/1)81%  (17/21)90%  (2.7/3)
fling (long, int, int, int, int): void 100% (1/1)82%  (36/44)90%  (9/10)
sendPendingEventToNative (): int 100% (1/1)84%  (87/103)81%  (17.8/22)
confirmTouchEvent (int): void 100% (1/1)86%  (68/79)88%  (23/26)
endDoubleTapDragMode (MotionEvent): void 100% (1/1)88%  (30/34)88%  (6.2/7)
initGestureDetectors (Context): void 100% (1/1)89%  (33/37)99%  (9.9/10)
sendShowPressedStateGestureForTesting (): void 100% (1/1)90%  (9/10)92%  (2.8/3)
processTouchEvent (MotionEvent): boolean 100% (1/1)91%  (52/57)93%  (14/15)
offerTouchEventToJavaScript (MotionEvent): boolean 100% (1/1)99%  (139/141)96%  (23/24)
ContentViewGestureHandler (Context, ContentViewGestureHandler$MotionEventDele... 100% (1/1)100% (93/93)100% (23/23)
access$000 (ContentViewGestureHandler): Deque 100% (1/1)100% (3/3)100% (1/1)
access$100 (ContentViewGestureHandler, MotionEvent): boolean 100% (1/1)100% (4/4)100% (1/1)
access$1000 (ContentViewGestureHandler): SnapScrollController 100% (1/1)100% (3/3)100% (1/1)
access$1100 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$1102 (ContentViewGestureHandler, float): float 100% (1/1)100% (5/5)100% (1/1)
access$1200 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$1202 (ContentViewGestureHandler, float): float 100% (1/1)100% (5/5)100% (1/1)
access$1300 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$1302 (ContentViewGestureHandler, float): float 100% (1/1)100% (5/5)100% (1/1)
access$1400 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$1402 (ContentViewGestureHandler, float): float 100% (1/1)100% (5/5)100% (1/1)
access$1500 (ContentViewGestureHandler, int, MotionEvent, Bundle): boolean 100% (1/1)100% (6/6)100% (1/1)
access$1600 (ContentViewGestureHandler): Bundle 100% (1/1)100% (3/3)100% (1/1)
access$1700 (ContentViewGestureHandler, int, long, int, int, Bundle): boolean 100% (1/1)100% (8/8)100% (1/1)
access$1800 (ContentViewGestureHandler): LongPressDetector 100% (1/1)100% (3/3)100% (1/1)
access$1900 (): int 100% (1/1)100% (2/2)100% (1/1)
access$200 (ContentViewGestureHandler, MotionEvent): void 100% (1/1)100% (4/4)100% (1/1)
access$2000 (ContentViewGestureHandler, int, int): void 100% (1/1)100% (5/5)100% (1/1)
access$2100 (ContentViewGestureHandler): boolean 100% (1/1)100% (3/3)100% (1/1)
access$2200 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$2202 (ContentViewGestureHandler, float): float 100% (1/1)100% (5/5)100% (1/1)
access$2300 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$2302 (ContentViewGestureHandler, float): float 100% (1/1)100% (5/5)100% (1/1)
access$2400 (ContentViewGestureHandler): int 100% (1/1)100% (3/3)100% (1/1)
access$2402 (ContentViewGestureHandler, int): int 100% (1/1)100% (5/5)100% (1/1)
access$2500 (ContentViewGestureHandler): int 100% (1/1)100% (3/3)100% (1/1)
access$2600 (ContentViewGestureHandler, int, long, int, int, Bundle): boolean 100% (1/1)100% (8/8)100% (1/1)
access$2700 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$2702 (ContentViewGestureHandler, float): float 100% (1/1)100% (5/5)100% (1/1)
access$2800 (ContentViewGestureHandler): float 100% (1/1)100% (3/3)100% (1/1)
access$2900 (ContentViewGestureHandler): ZoomManager 100% (1/1)100% (3/3)100% (1/1)
access$300 (ContentViewGestureHandler): ContentViewGestureHandler$MotionEvent... 100% (1/1)100% (3/3)100% (1/1)
access$400 (ContentViewGestureHandler): void 100% (1/1)100% (3/3)100% (1/1)
access$600 (ContentViewGestureHandler): boolean 100% (1/1)100% (3/3)100% (1/1)
access$602 (ContentViewGestureHandler, boolean): boolean 100% (1/1)100% (5/5)100% (1/1)
access$700 (ContentViewGestureHandler): boolean 100% (1/1)100% (3/3)100% (1/1)
access$702 (ContentViewGestureHandler, boolean): boolean 100% (1/1)100% (5/5)100% (1/1)
access$800 (ContentViewGestureHandler): boolean 100% (1/1)100% (3/3)100% (1/1)
access$802 (ContentViewGestureHandler, boolean): boolean 100% (1/1)100% (5/5)100% (1/1)
access$900 (ContentViewGestureHandler): boolean 100% (1/1)100% (3/3)100% (1/1)
access$902 (ContentViewGestureHandler, boolean): boolean 100% (1/1)100% (5/5)100% (1/1)
canHandle (MotionEvent): boolean 100% (1/1)100% (17/17)100% (1/1)
drainAllPendingEventsUntilNextDown (): void 100% (1/1)100% (36/36)100% (10/10)
endFlingIfNecessary (long): void 100% (1/1)100% (16/16)100% (4/4)
getLongPressDetector (): LongPressDetector 100% (1/1)100% (3/3)100% (1/1)
getNumberOfPendingMotionEventsForTesting (): int 100% (1/1)100% (4/4)100% (1/1)
hasScheduledTouchTimeoutEventForTesting (): boolean 100% (1/1)100% (4/4)100% (1/1)
isDoubleTapDragDisabled (): boolean 100% (1/1)100% (8/8)100% (1/1)
isEventCancelledForTesting (MotionEvent): boolean 100% (1/1)100% (10/10)100% (1/1)
isNativeScrolling (): boolean 100% (1/1)100% (3/3)100% (1/1)
mockTouchEventTimeout (): void 100% (1/1)100% (4/4)100% (2/2)
obtainActionCancelMotionEvent (): MotionEvent 100% (1/1)100% (8/8)100% (1/1)
onLongPress (MotionEvent): void 100% (1/1)100% (5/5)100% (2/2)
peekFirstInPendingMotionEventsForTesting (): MotionEvent 100% (1/1)100% (5/5)100% (1/1)
pinchBegin (long, int, int): void 100% (1/1)100% (9/9)100% (2/2)
pinchBy (long, int, int, float): void 100% (1/1)100% (21/21)100% (5/5)
pinchEnd (long): void 100% (1/1)100% (12/12)100% (3/3)
recycleEvent (MotionEvent): void 100% (1/1)100% (10/10)100% (4/4)
resetGestureHandlers (): void 100% (1/1)100% (24/24)100% (8/8)
sendGesture (int, long, int, int, Bundle): boolean 100% (1/1)100% (10/10)100% (1/1)
sendLastGestureForVSync (int, long, int, int, Bundle): boolean 100% (1/1)100% (11/11)100% (1/1)
sendMotionEventAsGesture (int, MotionEvent, Bundle): boolean 100% (1/1)100% (15/15)100% (1/1)
sendShowPressCancelIfNecessary (MotionEvent): void 100% (1/1)100% (14/14)100% (4/4)
setClickXAndY (int, int): void 100% (1/1)100% (7/7)100% (3/3)
setTestDependencies (LongPressDetector, GestureDetector, GestureDetector$OnGe... 100% (1/1)100% (10/10)100% (4/4)
triggerLongTapIfNeeded (MotionEvent): boolean 100% (1/1)100% (25/25)100% (5/5)
     
class ContentViewGestureHandler$1100% (1/1)100% (10/10)93%  (673/721)91%  (109.5/120)
onSingleTapUp (MotionEvent): boolean 100% (1/1)71%  (82/116)64%  (14/22)
onLongPress (MotionEvent): void 100% (1/1)84%  (21/25)92%  (3.7/4)
isDistanceBetweenDownAndUpTooLong (float, float): boolean 100% (1/1)94%  (29/31)98%  (2.9/3)
onDoubleTapEvent (MotionEvent): boolean 100% (1/1)97%  (182/188)93%  (26/28)
onScroll (MotionEvent, MotionEvent, float, float): boolean 100% (1/1)99%  (208/210)100% (34.9/35)
ContentViewGestureHandler$1 (ContentViewGestureHandler, int): void 100% (1/1)100% (9/9)100% (1/1)
onDown (MotionEvent): boolean 100% (1/1)100% (48/48)100% (10/10)
onFling (MotionEvent, MotionEvent, float, float): boolean 100% (1/1)100% (34/34)100% (6/6)
onShowPress (MotionEvent): void 100% (1/1)100% (13/13)100% (3/3)
onSingleTapConfirmed (MotionEvent): boolean 100% (1/1)100% (47/47)100% (8/8)

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 
5package org.chromium.content.browser;
6 
7import android.content.Context;
8import android.os.Bundle;
9import android.os.Handler;
10import android.os.SystemClock;
11import android.util.Log;
12import android.view.MotionEvent;
13import android.view.ViewConfiguration;
14 
15import org.chromium.content.browser.third_party.GestureDetector;
16import org.chromium.content.browser.third_party.GestureDetector.OnGestureListener;
17import org.chromium.content.browser.LongPressDetector.LongPressDelegate;
18import org.chromium.content.browser.SnapScrollController;
19import org.chromium.content.common.TraceEvent;
20 
21import java.util.ArrayDeque;
22import java.util.Deque;
23 
24/**
25 * This class handles all MotionEvent handling done in ContentViewCore including the gesture
26 * recognition. It sends all related native calls through the interface MotionEventDelegate.
27 */
28class ContentViewGestureHandler implements LongPressDelegate {
29 
30    private static final String TAG = "ContentViewGestureHandler";
31    /**
32     * Used for GESTURE_FLING_START x velocity
33     */
34    static final String VELOCITY_X = "Velocity X";
35    /**
36     * Used for GESTURE_FLING_START y velocity
37     */
38    static final String VELOCITY_Y = "Velocity Y";
39    /**
40     * Used for GESTURE_SCROLL_BY x distance
41     */
42    static final String DISTANCE_X = "Distance X";
43    /**
44     * Used for GESTURE_SCROLL_BY y distance
45     */
46    static final String DISTANCE_Y = "Distance Y";
47    /**
48     * Used in GESTURE_SINGLE_TAP_CONFIRMED to check whether ShowPress has been called before.
49     */
50    static final String SHOW_PRESS = "ShowPress";
51    /**
52     * Used for GESTURE_PINCH_BY delta
53     */
54    static final String DELTA = "Delta";
55 
56    private final Bundle mExtraParamBundle;
57    private GestureDetector mGestureDetector;
58    private final ZoomManager mZoomManager;
59    private LongPressDetector mLongPressDetector;
60    private OnGestureListener mListener;
61    private MotionEvent mCurrentDownEvent;
62    private final MotionEventDelegate mMotionEventDelegate;
63 
64    // Queue of motion events.
65    private final Deque<MotionEvent> mPendingMotionEvents = new ArrayDeque<MotionEvent>();
66 
67    // Has WebKit told us the current page requires touch events.
68    private boolean mHasTouchHandlers = false;
69 
70    // True if the down event for the current gesture was returned back to the browser with
71    // INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
72    private boolean mNoTouchHandlerForGesture = false;
73 
74    // True if JavaScript touch event handlers returned an ACK with
75    // INPUT_EVENT_ACK_STATE_CONSUMED. In this case we should avoid, sending events from
76    // this gesture to the Gesture Detector since it will have already missed at least
77    // one event.
78    private boolean mJavaScriptIsConsumingGesture = false;
79 
80    // Remember whether onShowPress() is called. If it is not, in onSingleTapConfirmed()
81    // we will first show the press state, then trigger the click.
82    private boolean mShowPressIsCalled;
83 
84    // This flag is used for ignoring the remaining touch events, i.e., All the events until the
85    // next ACTION_DOWN. This is automatically set to false on the next ACTION_DOWN.
86    private boolean mIgnoreRemainingTouchEvents;
87 
88    // TODO(klobag): this is to avoid a bug in GestureDetector. With multi-touch,
89    // mAlwaysInTapRegion is not reset. So when the last finger is up, onSingleTapUp()
90    // will be mistakenly fired.
91    private boolean mIgnoreSingleTap;
92 
93    // True from right before we send the first scroll event until the last finger is raised.
94    private boolean mTouchScrolling;
95 
96    // TODO(wangxianzhu): For now it is true after a fling is started until the next
97    // touch. Should reset it to false on end of fling if the UI is able to know when the
98    // fling ends.
99    private boolean mFlingMayBeActive;
100 
101    private boolean mSeenFirstScrollEvent;
102 
103    private boolean mPinchInProgress = false;
104 
105    // Tracks whether a touch cancel event has been sent as a result of switching
106    // into scrolling or pinching mode.
107    private boolean mTouchCancelEventSent = false;
108 
109    // Last cancelled touch event as a result of scrolling or pinching.
110    private MotionEvent mLastCancelledEvent = null;
111 
112    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
113 
114    //On single tap this will store the x, y coordinates of the touch.
115    private int mSingleTapX;
116    private int mSingleTapY;
117 
118    // Indicate current double tap drag mode state.
119    private int mDoubleTapDragMode = DOUBLE_TAP_DRAG_MODE_NONE;
120 
121    // x, y coordinates for an Anchor on double tap drag zoom.
122    private float mDoubleTapDragZoomAnchorX;
123    private float mDoubleTapDragZoomAnchorY;
124 
125    // On double tap this will store the y coordinates of the touch.
126    private float mDoubleTapY;
127 
128    // Double tap drag zoom sensitive (speed).
129    private static final float DOUBLE_TAP_DRAG_ZOOM_SPEED = 0.005f;
130 
131    // Used to track the last rawX/Y coordinates for moves.  This gives absolute scroll distance.
132    // Useful for full screen tracking.
133    private float mLastRawX = 0;
134    private float mLastRawY = 0;
135 
136    // Cache of square of the scaled touch slop so we don't have to calculate it on every touch.
137    private int mScaledTouchSlopSquare;
138 
139    // Object that keeps track of and updates scroll snapping behavior.
140    private SnapScrollController mSnapScrollController;
141 
142    // Used to track the accumulated scroll error over time. This is used to remove the
143    // rounding error we introduced by passing integers to webkit.
144    private float mAccumulatedScrollErrorX = 0;
145    private float mAccumulatedScrollErrorY = 0;
146 
147    // Whether input events are delivered right before vsync.
148    private final boolean mInputEventsDeliveredAtVSync;
149 
150    static final int GESTURE_SHOW_PRESSED_STATE = 0;
151    static final int GESTURE_DOUBLE_TAP = 1;
152    static final int GESTURE_SINGLE_TAP_UP = 2;
153    static final int GESTURE_SINGLE_TAP_CONFIRMED = 3;
154    static final int GESTURE_SINGLE_TAP_UNCONFIRMED = 4;
155    static final int GESTURE_LONG_PRESS = 5;
156    static final int GESTURE_SCROLL_START = 6;
157    static final int GESTURE_SCROLL_BY = 7;
158    static final int GESTURE_SCROLL_END = 8;
159    static final int GESTURE_FLING_START = 9;
160    static final int GESTURE_FLING_CANCEL = 10;
161    static final int GESTURE_PINCH_BEGIN = 11;
162    static final int GESTURE_PINCH_BY = 12;
163    static final int GESTURE_PINCH_END = 13;
164    static final int GESTURE_SHOW_PRESS_CANCEL = 14;
165    static final int GESTURE_LONG_TAP = 15;
166 
167    // These have to be kept in sync with content/port/common/input_event_ack_state.h
168    static final int INPUT_EVENT_ACK_STATE_UNKNOWN = 0;
169    static final int INPUT_EVENT_ACK_STATE_CONSUMED = 1;
170    static final int INPUT_EVENT_ACK_STATE_NOT_CONSUMED = 2;
171    static final int INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS = 3;
172 
173    // Return values of sendPendingEventToNative();
174    static final int EVENT_FORWARDED_TO_NATIVE = 0;
175    static final int EVENT_CONVERTED_TO_CANCEL = 1;
176    static final int EVENT_NOT_FORWARDED = 2;
177 
178    private final float mPxToDp;
179 
180    static final int DOUBLE_TAP_DRAG_MODE_NONE = 0;
181    static final int DOUBLE_TAP_DRAG_MODE_DETECTION_IN_PROGRESS = 1;
182    static final int DOUBLE_TAP_DRAG_MODE_ZOOM = 2;
183    static final int DOUBLE_TAP_DRAG_MODE_DISABLED = 3;
184 
185    private class TouchEventTimeoutHandler implements Runnable {
186        private static final int TOUCH_EVENT_TIMEOUT = 200;
187        private static final int PENDING_ACK_NONE = 0;
188        private static final int PENDING_ACK_ORIGINAL_EVENT = 1;
189        private static final int PENDING_ACK_CANCEL_EVENT = 2;
190 
191        private long mEventTime;
192        private TouchPoint[] mTouchPoints;
193        private Handler mHandler = new Handler();
194        private int mPendingAckState;
195 
196        public void start(long eventTime, TouchPoint[] pts) {
197            assert mTouchPoints == null;
198            assert mPendingAckState == PENDING_ACK_NONE;
199            mEventTime = eventTime;
200            mTouchPoints = pts;
201            mHandler.postDelayed(this, TOUCH_EVENT_TIMEOUT);
202        }
203 
204        @Override
205        public void run() {
206            TraceEvent.begin("TouchEventTimeout");
207            while (!mPendingMotionEvents.isEmpty()) {
208                MotionEvent nextEvent = mPendingMotionEvents.removeFirst();
209                processTouchEvent(nextEvent);
210                recycleEvent(nextEvent);
211            }
212            // We are waiting for 2 ACKs: one for the timed-out event, the other for
213            // the touchcancel event injected when the timed-out event is ACK'ed.
214            mPendingAckState = PENDING_ACK_ORIGINAL_EVENT;
215            TraceEvent.end();
216        }
217 
218        public boolean hasTimeoutEvent() {
219            return mPendingAckState != PENDING_ACK_NONE;
220        }
221 
222        /**
223         * @return Whether the ACK is consumed in this method.
224         */
225        public boolean confirmTouchEvent() {
226            switch (mPendingAckState) {
227                case PENDING_ACK_NONE:
228                    // The ACK to the original event is received before timeout.
229                    mHandler.removeCallbacks(this);
230                    mTouchPoints = null;
231                    return false;
232                case PENDING_ACK_ORIGINAL_EVENT:
233                    TraceEvent.instant("TouchEventTimeout:ConfirmOriginalEvent");
234                    // The ACK to the original event is received after timeout.
235                    // Inject a touchcancel event.
236                    mPendingAckState = PENDING_ACK_CANCEL_EVENT;
237                    mMotionEventDelegate.sendTouchEvent(mEventTime + TOUCH_EVENT_TIMEOUT,
238                            TouchPoint.TOUCH_EVENT_TYPE_CANCEL, mTouchPoints);
239                    mTouchPoints = null;
240                    return true;
241                case PENDING_ACK_CANCEL_EVENT:
242                    TraceEvent.instant("TouchEventTimeout:ConfirmCancelEvent");
243                    // The ACK to the injected touchcancel event is received.
244                    mPendingAckState = PENDING_ACK_NONE;
245                    drainAllPendingEventsUntilNextDown();
246                    return true;
247                default:
248                    assert false : "Never reached";
249                    return false;
250            }
251        }
252 
253        public void mockTimeout() {
254            assert !hasTimeoutEvent();
255            mHandler.removeCallbacks(this);
256            run();
257        }
258 
259        /**
260         * This is for testing only.
261         * @return Whether a timeout event has been scheduled but not yet run.
262         */
263        public boolean hasScheduledTimeoutEventForTesting() {
264            return mTouchPoints != null && mPendingAckState == PENDING_ACK_NONE;
265        }
266    }
267 
268    private TouchEventTimeoutHandler mTouchEventTimeoutHandler = new TouchEventTimeoutHandler();
269 
270    /**
271     * This is an interface to handle MotionEvent related communication with the native side also
272     * access some ContentView specific parameters.
273     */
274    public interface MotionEventDelegate {
275        /**
276         * Send a raw {@link MotionEvent} to the native side
277         * @param timeMs Time of the event in ms.
278         * @param action The action type for the event.
279         * @param pts The TouchPoint array to be sent for the event.
280         * @return Whether the event was sent to the native side successfully or not.
281         */
282        public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts);
283 
284        /**
285         * Send a gesture event to the native side.
286         * @param type The type of the gesture event.
287         * @param timeMs The time the gesture event occurred at.
288         * @param x The x location for the gesture event.
289         * @param y The y location for the gesture event.
290         * @param lastInputEventForVSync Indicates that this gesture event is the last input
291         * to be event sent during the current vsync interval.
292         * @param extraParams A bundle that holds specific extra parameters for certain gestures.
293         * Refer to gesture type definition for more information.
294         * @return Whether the gesture was sent successfully.
295         */
296        boolean sendGesture(
297                int type, long timeMs, int x, int y, boolean lastInputEventForVSync,
298                Bundle extraParams);
299 
300        /**
301         * Gives the UI the chance to override each scroll event.
302         * @param x The amount scrolled in the X direction.
303         * @param y The amount scrolled in the Y direction.
304         * @return Whether or not the UI consumed and handled this event.
305         */
306        boolean didUIStealScroll(float x, float y);
307 
308        /**
309         * Show the zoom picker UI.
310         */
311        public void invokeZoomPicker();
312 
313        /**
314         * @return Whether changing the page scale is not possible on the current page.
315         */
316        public boolean hasFixedPageScale();
317    }
318 
319    ContentViewGestureHandler(
320            Context context, MotionEventDelegate delegate, ZoomManager zoomManager,
321            int inputEventDeliveryMode) {
322        mExtraParamBundle = new Bundle();
323        mLongPressDetector = new LongPressDetector(context, this);
324        mMotionEventDelegate = delegate;
325        mZoomManager = zoomManager;
326        mSnapScrollController = new SnapScrollController(context, mZoomManager);
327        mInputEventsDeliveredAtVSync =
328                inputEventDeliveryMode == ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC;
329        mPxToDp = 1.0f / context.getResources().getDisplayMetrics().density;
330 
331        initGestureDetectors(context);
332    }
333 
334    /**
335     * Used to override the default long press detector, gesture detector and listener.
336     * This is used for testing only.
337     * @param longPressDetector The new LongPressDetector to be assigned.
338     * @param gestureDetector The new GestureDetector to be assigned.
339     * @param listener The new onGestureListener to be assigned.
340     */
341    void setTestDependencies(
342            LongPressDetector longPressDetector, GestureDetector gestureDetector,
343            OnGestureListener listener) {
344        mLongPressDetector = longPressDetector;
345        mGestureDetector = gestureDetector;
346        mListener = listener;
347    }
348 
349    private void initGestureDetectors(final Context context) {
350        final int scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
351        mScaledTouchSlopSquare = scaledTouchSlop * scaledTouchSlop;
352        try {
353            TraceEvent.begin();
354            GestureDetector.SimpleOnGestureListener listener =
355                new GestureDetector.SimpleOnGestureListener() {
356                    @Override
357                    public boolean onDown(MotionEvent e) {
358                        mShowPressIsCalled = false;
359                        mIgnoreSingleTap = false;
360                        mTouchScrolling = false;
361                        mSeenFirstScrollEvent = false;
362                        mSnapScrollController.resetSnapScrollMode();
363                        mLastRawX = e.getRawX();
364                        mLastRawY = e.getRawY();
365                        mAccumulatedScrollErrorX = 0;
366                        mAccumulatedScrollErrorY = 0;
367                        // Return true to indicate that we want to handle touch
368                        return true;
369                    }
370 
371                    @Override
372                    public boolean onScroll(MotionEvent e1, MotionEvent e2,
373                            float distanceX, float distanceY) {
374                        if (!mSeenFirstScrollEvent) {
375                            // Remove the touch slop region from the first scroll event to avoid a
376                            // jump.
377                            mSeenFirstScrollEvent = true;
378                            double distance = Math.sqrt(
379                                    distanceX * distanceX + distanceY * distanceY);
380                            double epsilon = 1e-3;
381                            if (distance > epsilon) {
382                                double ratio = Math.max(0, distance - scaledTouchSlop) / distance;
383                                distanceX *= ratio;
384                                distanceY *= ratio;
385                            }
386                        }
387                        mSnapScrollController.updateSnapScrollMode(distanceX, distanceY);
388                        if (mSnapScrollController.isSnappingScrolls()) {
389                            if (mSnapScrollController.isSnapHorizontal()) {
390                                distanceY = 0;
391                            } else {
392                                distanceX = 0;
393                            }
394                        }
395 
396                        boolean didUIStealScroll = mMotionEventDelegate.didUIStealScroll(
397                                e2.getRawX() - mLastRawX, e2.getRawY() - mLastRawY);
398 
399                        mLastRawX = e2.getRawX();
400                        mLastRawY = e2.getRawY();
401                        if (didUIStealScroll) return true;
402                        if (!mTouchScrolling) {
403                            sendShowPressCancelIfNecessary(e1);
404                            endFlingIfNecessary(e2.getEventTime());
405                            if (sendMotionEventAsGesture(GESTURE_SCROLL_START, e1, null)) {
406                                mTouchScrolling = true;
407                            }
408                        }
409                        // distanceX and distanceY is the scrolling offset since last onScroll.
410                        // Because we are passing integers to webkit, this could introduce
411                        // rounding errors. The rounding errors will accumulate overtime.
412                        // To solve this, we should be adding back the rounding errors each time
413                        // when we calculate the new offset.
414                        int x = (int) e2.getX();
415                        int y = (int) e2.getY();
416                        int dx = (int) (distanceX + mAccumulatedScrollErrorX);
417                        int dy = (int) (distanceY + mAccumulatedScrollErrorY);
418                        mAccumulatedScrollErrorX = distanceX + mAccumulatedScrollErrorX - dx;
419                        mAccumulatedScrollErrorY = distanceY + mAccumulatedScrollErrorY - dy;
420                        mExtraParamBundle.clear();
421                        mExtraParamBundle.putInt(DISTANCE_X, dx);
422                        mExtraParamBundle.putInt(DISTANCE_Y, dy);
423                        if ((dx | dy) != 0) {
424                            sendLastGestureForVSync(GESTURE_SCROLL_BY,
425                                    e2.getEventTime(), x, y, mExtraParamBundle);
426                        }
427 
428                        mMotionEventDelegate.invokeZoomPicker();
429 
430                        return true;
431                    }
432 
433                    @Override
434                    public boolean onFling(MotionEvent e1, MotionEvent e2,
435                            float velocityX, float velocityY) {
436                        if (mSnapScrollController.isSnappingScrolls()) {
437                            if (mSnapScrollController.isSnapHorizontal()) {
438                                velocityY = 0;
439                            } else {
440                                velocityX = 0;
441                            }
442                        }
443 
444                        fling(e1.getEventTime(),(int) e1.getX(0), (int) e1.getY(0),
445                                        (int) velocityX, (int) velocityY);
446                        return true;
447                    }
448 
449                    @Override
450                    public void onShowPress(MotionEvent e) {
451                        mShowPressIsCalled = true;
452                        sendMotionEventAsGesture(GESTURE_SHOW_PRESSED_STATE, e, null);
453                    }
454 
455                    @Override
456                    public boolean onSingleTapUp(MotionEvent e) {
457                        if (isDistanceBetweenDownAndUpTooLong(e.getRawX(), e.getRawY())) {
458                            mIgnoreSingleTap = true;
459                            return true;
460                        }
461                        // This is a hack to address the issue where user hovers
462                        // over a link for longer than DOUBLE_TAP_TIMEOUT, then
463                        // onSingleTapConfirmed() is not triggered. But we still
464                        // want to trigger the tap event at UP. So we override
465                        // onSingleTapUp() in this case. This assumes singleTapUp
466                        // gets always called before singleTapConfirmed.
467                        if (!mIgnoreSingleTap && !mLongPressDetector.isInLongPress()) {
468                            if (e.getEventTime() - e.getDownTime() > DOUBLE_TAP_TIMEOUT) {
469                                float x = e.getX();
470                                float y = e.getY();
471                                if (sendMotionEventAsGesture(GESTURE_SINGLE_TAP_UP, e, null)) {
472                                    mIgnoreSingleTap = true;
473                                }
474                                setClickXAndY((int) x, (int) y);
475                                return true;
476                            } else if (mMotionEventDelegate.hasFixedPageScale()) {
477                                // If page is not user scalable, we don't need to wait
478                                // for double tap timeout.
479                                float x = e.getX();
480                                float y = e.getY();
481                                mExtraParamBundle.clear();
482                                mExtraParamBundle.putBoolean(SHOW_PRESS, mShowPressIsCalled);
483                                if (sendMotionEventAsGesture(GESTURE_SINGLE_TAP_CONFIRMED, e,
484                                        mExtraParamBundle)) {
485                                    mIgnoreSingleTap = true;
486                                }
487                                setClickXAndY((int) x, (int) y);
488                            } else {
489                                // Notify Blink about this tapUp event anyway,
490                                // when none of the above conditions applied.
491                                sendMotionEventAsGesture(GESTURE_SINGLE_TAP_UNCONFIRMED, e, null);
492                            }
493                        }
494 
495                        return triggerLongTapIfNeeded(e);
496                    }
497 
498                    @Override
499                    public boolean onSingleTapConfirmed(MotionEvent e) {
500                        // Long taps in the edges of the screen have their events delayed by
501                        // ContentViewHolder for tab swipe operations. As a consequence of the delay
502                        // this method might be called after receiving the up event.
503                        // These corner cases should be ignored.
504                        if (mLongPressDetector.isInLongPress() || mIgnoreSingleTap) return true;
505 
506                        int x = (int) e.getX();
507                        int y = (int) e.getY();
508                        mExtraParamBundle.clear();
509                        mExtraParamBundle.putBoolean(SHOW_PRESS, mShowPressIsCalled);
510                        sendMotionEventAsGesture(GESTURE_SINGLE_TAP_CONFIRMED, e,
511                            mExtraParamBundle);
512                        setClickXAndY(x, y);
513                        return true;
514                    }
515 
516                    @Override
517                    public boolean onDoubleTapEvent(MotionEvent e) {
518                        if (isDoubleTapDragDisabled()) return false;
519                        switch (e.getActionMasked()) {
520                            case MotionEvent.ACTION_DOWN:
521                                sendShowPressCancelIfNecessary(e);
522                                mDoubleTapDragZoomAnchorX = e.getX();
523                                mDoubleTapDragZoomAnchorY = e.getY();
524                                mDoubleTapDragMode = DOUBLE_TAP_DRAG_MODE_DETECTION_IN_PROGRESS;
525                                break;
526                            case MotionEvent.ACTION_MOVE:
527                                if (mDoubleTapDragMode
528                                        == DOUBLE_TAP_DRAG_MODE_DETECTION_IN_PROGRESS) {
529                                    float distanceX = mDoubleTapDragZoomAnchorX - e.getX();
530                                    float distanceY = mDoubleTapDragZoomAnchorY - e.getY();
531 
532                                    // Begin double tap drag zoom mode if the move distance is
533                                    // further than the threshold.
534                                    if (distanceX * distanceX + distanceY * distanceY >
535                                            mScaledTouchSlopSquare) {
536                                        sendGesture(GESTURE_SCROLL_START, e.getEventTime(),
537                                                (int) e.getX(), (int) e.getY(), null);
538                                        pinchBegin(e.getEventTime(),
539                                                Math.round(mDoubleTapDragZoomAnchorX),
540                                                Math.round(mDoubleTapDragZoomAnchorY));
541                                        mDoubleTapDragMode = DOUBLE_TAP_DRAG_MODE_ZOOM;
542                                    }
543                                } else if (mDoubleTapDragMode == DOUBLE_TAP_DRAG_MODE_ZOOM) {
544                                    mExtraParamBundle.clear();
545                                    sendGesture(GESTURE_SCROLL_BY, e.getEventTime(),
546                                            (int) e.getX(), (int) e.getY(), mExtraParamBundle);
547 
548                                    float dy = mDoubleTapY - e.getY();
549                                    pinchBy(e.getEventTime(),
550                                            Math.round(mDoubleTapDragZoomAnchorX),
551                                            Math.round(mDoubleTapDragZoomAnchorY),
552                                            (float) Math.pow(dy < 0 ?
553                                                    1.0f - DOUBLE_TAP_DRAG_ZOOM_SPEED :
554                                                    1.0f + DOUBLE_TAP_DRAG_ZOOM_SPEED,
555                                                    Math.abs(dy * mPxToDp)));
556                                }
557                                break;
558                            case MotionEvent.ACTION_UP:
559                                if (mDoubleTapDragMode != DOUBLE_TAP_DRAG_MODE_ZOOM) {
560                                    // Normal double tap gesture.
561                                    sendMotionEventAsGesture(GESTURE_DOUBLE_TAP, e, null);
562                                }
563                                endDoubleTapDragMode(e);
564                                break;
565                            case MotionEvent.ACTION_CANCEL:
566                                endDoubleTapDragMode(e);
567                                break;
568                            default:
569                                break;
570                        }
571                        mDoubleTapY = e.getY();
572                        return true;
573                    }
574 
575                    @Override
576                    public void onLongPress(MotionEvent e) {
577                        if (!mZoomManager.isScaleGestureDetectionInProgress() &&
578                                (mDoubleTapDragMode == DOUBLE_TAP_DRAG_MODE_NONE ||
579                                 isDoubleTapDragDisabled())) {
580                            sendShowPressCancelIfNecessary(e);
581                            sendMotionEventAsGesture(GESTURE_LONG_PRESS, e, null);
582                        }
583                    }
584 
585                    /**
586                     * This method inspects the distance between where the user started touching
587                     * the surface, and where she released. If the points are too far apart, we
588                     * should assume that the web page has consumed the scroll-events in-between,
589                     * and as such, this should not be considered a single-tap.
590                     *
591                     * We use the Android frameworks notion of how far a touch can wander before
592                     * we think the user is scrolling.
593                     *
594                     * @param x the new x coordinate
595                     * @param y the new y coordinate
596                     * @return true if the distance is too long to be considered a single tap
597                     */
598                    private boolean isDistanceBetweenDownAndUpTooLong(float x, float y) {
599                        double deltaX = mLastRawX - x;
600                        double deltaY = mLastRawY - y;
601                        return deltaX * deltaX + deltaY * deltaY > mScaledTouchSlopSquare;
602                    }
603                };
604                mListener = listener;
605                mGestureDetector = new GestureDetector(context, listener);
606                mGestureDetector.setIsLongpressEnabled(false);
607        } finally {
608            TraceEvent.end();
609        }
610    }
611 
612    /**
613     * @return LongPressDetector handling setting up timers for and canceling LongPress gestures.
614     */
615    LongPressDetector getLongPressDetector() {
616        return mLongPressDetector;
617    }
618 
619    /**
620     * @param event Start a LongPress gesture event from the listener.
621     */
622    @Override
623    public void onLongPress(MotionEvent event) {
624        mListener.onLongPress(event);
625    }
626 
627    /**
628     * Cancels any ongoing LongPress timers.
629     */
630    void cancelLongPress() {
631        mLongPressDetector.cancelLongPress();
632    }
633 
634    /**
635     * Fling the ContentView from the current position.
636     * @param x Fling touch starting position
637     * @param y Fling touch starting position
638     * @param velocityX Initial velocity of the fling (X) measured in pixels per second.
639     * @param velocityY Initial velocity of the fling (Y) measured in pixels per second.
640     */
641    void fling(long timeMs, int x, int y, int velocityX, int velocityY) {
642        endFlingIfNecessary(timeMs);
643        if (!mTouchScrolling) {
644            // The native side needs a GESTURE_SCROLL_BEGIN before GESTURE_FLING_START
645            // to send the fling to the correct target. Send if it has not sent.
646            sendGesture(GESTURE_SCROLL_START, timeMs, x, y, null);
647        }
648        endTouchScrollIfNecessary(timeMs, false);
649 
650        mFlingMayBeActive = true;
651 
652        mExtraParamBundle.clear();
653        mExtraParamBundle.putInt(VELOCITY_X, velocityX);
654        mExtraParamBundle.putInt(VELOCITY_Y, velocityY);
655        sendGesture(GESTURE_FLING_START, timeMs, x, y, mExtraParamBundle);
656    }
657 
658    /**
659     * Send a GESTURE_FLING_CANCEL event if necessary.
660     * @param timeMs The time in ms for the event initiating this gesture.
661     */
662    void endFlingIfNecessary(long timeMs) {
663        if (!mFlingMayBeActive) return;
664        mFlingMayBeActive = false;
665        sendGesture(GESTURE_FLING_CANCEL, timeMs, 0, 0, null);
666    }
667 
668    /**
669     * End DOUBLE_TAP_DRAG_MODE_ZOOM by sending GESTURE_SCROLL_END and GESTURE_PINCH_END events.
670     * @param event A hint event that its x, y, and eventTime will be used for the ending events
671     *              to send. This argument is an optional and can be null.
672     */
673    void endDoubleTapDragMode(MotionEvent event) {
674        if (isDoubleTapDragDisabled()) return;
675        if (mDoubleTapDragMode == DOUBLE_TAP_DRAG_MODE_ZOOM) {
676            if (event == null) event = obtainActionCancelMotionEvent();
677            pinchEnd(event.getEventTime());
678            sendGesture(GESTURE_SCROLL_END, event.getEventTime(),
679                    (int) event.getX(), (int) event.getY(), null);
680        }
681        mDoubleTapDragMode = DOUBLE_TAP_DRAG_MODE_NONE;
682    }
683 
684    /**
685     * Reset touch scroll flag and optionally send a GESTURE_SCROLL_END event if necessary.
686     * @param timeMs The time in ms for the event initiating this gesture.
687     * @param sendScrollEndEvent Whether to send GESTURE_SCROLL_END event.
688     */
689    private void endTouchScrollIfNecessary(long timeMs, boolean sendScrollEndEvent) {
690        if (!mTouchScrolling) return;
691        mTouchScrolling = false;
692        if (sendScrollEndEvent) {
693            sendGesture(GESTURE_SCROLL_END, timeMs, 0, 0, null);
694        }
695    }
696 
697    /**
698     * @return Whether native is tracking a scroll.
699     */
700    boolean isNativeScrolling() {
701        // TODO(wangxianzhu): Also return true when fling is active once the UI knows exactly when
702        // the fling ends.
703        return mTouchScrolling;
704    }
705 
706    /**
707     * @return Whether native is tracking a pinch (i.e. between sending GESTURE_PINCH_BEGIN and
708     *         GESTURE_PINCH_END).
709     */
710    boolean isNativePinching() {
711        return mPinchInProgress;
712    }
713 
714    /**
715     * Starts a pinch gesture.
716     * @param timeMs The time in ms for the event initiating this gesture.
717     * @param x The x coordinate for the event initiating this gesture.
718     * @param y The x coordinate for the event initiating this gesture.
719     */
720    void pinchBegin(long timeMs, int x, int y) {
721        sendGesture(GESTURE_PINCH_BEGIN, timeMs, x, y, null);
722    }
723 
724    /**
725     * Pinch by a given percentage.
726     * @param timeMs The time in ms for the event initiating this gesture.
727     * @param anchorX The x coordinate for the anchor point to be used in pinch.
728     * @param anchorY The y coordinate for the anchor point to be used in pinch.
729     * @param delta The percentage to pinch by.
730     */
731    void pinchBy(long timeMs, int anchorX, int anchorY, float delta) {
732        mExtraParamBundle.clear();
733        mExtraParamBundle.putFloat(DELTA, delta);
734        sendLastGestureForVSync(GESTURE_PINCH_BY, timeMs, anchorX, anchorY, mExtraParamBundle);
735        mPinchInProgress = true;
736    }
737 
738    /**
739     * End a pinch gesture.
740     * @param timeMs The time in ms for the event initiating this gesture.
741     */
742    void pinchEnd(long timeMs) {
743        sendGesture(GESTURE_PINCH_END, timeMs, 0, 0, null);
744        mPinchInProgress = false;
745    }
746 
747    /**
748     * Ignore singleTap gestures.
749     */
750    void setIgnoreSingleTap(boolean value) {
751        mIgnoreSingleTap = value;
752    }
753 
754    private void setClickXAndY(int x, int y) {
755        mSingleTapX = x;
756        mSingleTapY = y;
757    }
758 
759    /**
760     * @return The x coordinate for the last point that a singleTap gesture was initiated from.
761     */
762    public int getSingleTapX()  {
763        return mSingleTapX;
764    }
765 
766    /**
767     * @return The y coordinate for the last point that a singleTap gesture was initiated from.
768     */
769    public int getSingleTapY()  {
770        return mSingleTapY;
771    }
772 
773    /**
774     * Cancel the current touch event sequence by sending ACTION_CANCEL and ignore all the
775     * subsequent events until the next ACTION_DOWN.
776     *
777     * One example usecase is stop processing the touch events when showing context popup menu.
778     */
779    public void setIgnoreRemainingTouchEvents() {
780        onTouchEvent(obtainActionCancelMotionEvent());
781        mIgnoreRemainingTouchEvents = true;
782    }
783 
784    /**
785     * Handle the incoming MotionEvent.
786     * @return Whether the event was handled.
787     */
788    boolean onTouchEvent(MotionEvent event) {
789        try {
790            TraceEvent.begin("onTouchEvent");
791 
792            if (mIgnoreRemainingTouchEvents) {
793                if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
794                    mIgnoreRemainingTouchEvents = false;
795                } else {
796                    return false;
797                }
798            }
799 
800            mLongPressDetector.cancelLongPressIfNeeded(event);
801            mSnapScrollController.setSnapScrollingMode(event);
802            // Notify native that scrolling has stopped whenever a down action is processed prior to
803            // passing the event to native as it will drop them as an optimization if scrolling is
804            // enabled.  Ending the fling ensures scrolling has stopped as well as terminating the
805            // current fling if applicable.
806            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
807                mNoTouchHandlerForGesture = false;
808                mJavaScriptIsConsumingGesture = false;
809                endFlingIfNecessary(event.getEventTime());
810            } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
811                endDoubleTapDragMode(null);
812            }
813 
814            if (offerTouchEventToJavaScript(event)) {
815                // offerTouchEventToJavaScript returns true to indicate the event was sent
816                // to the render process. If it is not subsequently handled, it will
817                // be returned via confirmTouchEvent(false) and eventually passed to
818                // processTouchEvent asynchronously.
819                return true;
820            }
821            return processTouchEvent(event);
822        } finally {
823            TraceEvent.end("onTouchEvent");
824        }
825    }
826 
827    private MotionEvent obtainActionCancelMotionEvent() {
828        return MotionEvent.obtain(
829                SystemClock.uptimeMillis(),
830                SystemClock.uptimeMillis(),
831                MotionEvent.ACTION_CANCEL, 0.0f,  0.0f,  0);
832    }
833 
834    /**
835     * Resets gesture handlers state; called on didStartLoading().
836     * Note that this does NOT clear the pending motion events queue;
837     * it gets cleared in hasTouchEventHandlers() called from WebKit
838     * FrameLoader::transitionToCommitted iff the page ever had touch handlers.
839     */
840    void resetGestureHandlers() {
841        {
842            MotionEvent me = obtainActionCancelMotionEvent();
843            mGestureDetector.onTouchEvent(me);
844            me.recycle();
845        }
846        {
847            MotionEvent me = obtainActionCancelMotionEvent();
848            mZoomManager.processTouchEvent(me);
849            me.recycle();
850        }
851        mLongPressDetector.cancelLongPress();
852    }
853 
854    /**
855     * Sets the flag indicating that the content has registered listeners for touch events.
856     */
857    void hasTouchEventHandlers(boolean hasTouchHandlers) {
858        mHasTouchHandlers = hasTouchHandlers;
859        // When mainframe is loading, FrameLoader::transitionToCommitted will
860        // call this method to set mHasTouchHandlers to false. We use this as
861        // an indicator to clear the pending motion events so that events from
862        // the previous page will not be carried over to the new page.
863        if (!mHasTouchHandlers) mPendingMotionEvents.clear();
864    }
865 
866    private boolean offerTouchEventToJavaScript(MotionEvent event) {
867        mLongPressDetector.onOfferTouchEventToJavaScript(event);
868 
869        if (!mHasTouchHandlers || mNoTouchHandlerForGesture) return false;
870 
871        if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
872            // Only send move events if the move has exceeded the slop threshold.
873            if (!mLongPressDetector.confirmOfferMoveEventToJavaScript(event)) {
874                return true;
875            }
876            // Avoid flooding the renderer process with move events: if the previous pending
877            // command is also a move (common case) that has not yet been forwarded, skip sending
878            //  this event to the webkit side and collapse it into the pending event.
879            MotionEvent previousEvent = mPendingMotionEvents.peekLast();
880            if (previousEvent != null
881                    && previousEvent != mPendingMotionEvents.peekFirst()
882                    && previousEvent.getActionMasked() == MotionEvent.ACTION_MOVE
883                    && previousEvent.getPointerCount() == event.getPointerCount()) {
884                TraceEvent.instant("offerTouchEventToJavaScript:EventCoalesced",
885                                   "QueueSize = " + mPendingMotionEvents.size());
886                MotionEvent.PointerCoords[] coords =
887                        new MotionEvent.PointerCoords[event.getPointerCount()];
888                for (int i = 0; i < coords.length; ++i) {
889                    coords[i] = new MotionEvent.PointerCoords();
890                    event.getPointerCoords(i, coords[i]);
891                }
892                previousEvent.addBatch(event.getEventTime(), coords, event.getMetaState());
893                return true;
894            }
895        }
896        if (mPendingMotionEvents.isEmpty()) {
897            // Add the event to the pending queue prior to calling sendPendingEventToNative.
898            // When sending an event to native, the callback to confirmTouchEvent can be
899            // synchronous or asynchronous and confirmTouchEvent expects the event to be
900            // in the queue when it is called.
901            MotionEvent clone = MotionEvent.obtain(event);
902            mPendingMotionEvents.add(clone);
903 
904            int forward = sendPendingEventToNative();
905            if (forward == EVENT_NOT_FORWARDED) mPendingMotionEvents.remove(clone);
906            return forward != EVENT_NOT_FORWARDED;
907        } else {
908            TraceEvent.instant("offerTouchEventToJavaScript:EventQueued",
909                               "QueueSize = " + mPendingMotionEvents.size());
910            // Copy the event, as the original may get mutated after this method returns.
911            MotionEvent clone = MotionEvent.obtain(event);
912            mPendingMotionEvents.add(clone);
913            return true;
914        }
915    }
916 
917    private int sendPendingEventToNative() {
918        MotionEvent event = mPendingMotionEvents.peekFirst();
919        if (event == null) {
920            assert false : "Cannot send from an empty pending event queue";
921            return EVENT_NOT_FORWARDED;
922        }
923 
924        if (mTouchEventTimeoutHandler.hasTimeoutEvent()) return EVENT_NOT_FORWARDED;
925 
926        TouchPoint[] pts = new TouchPoint[event.getPointerCount()];
927        int type = TouchPoint.createTouchPoints(event, pts);
928 
929        if (type == TouchPoint.CONVERSION_ERROR) return EVENT_NOT_FORWARDED;
930 
931        if (!mTouchScrolling && !mPinchInProgress) {
932            mTouchCancelEventSent = false;
933 
934            if (mMotionEventDelegate.sendTouchEvent(event.getEventTime(), type, pts)) {
935                // If confirmTouchEvent() is called synchronously with respect to sendTouchEvent(),
936                // then |event| will have been recycled. Only start the timer if the sent event has
937                // not yet been confirmed.
938                if (!mJavaScriptIsConsumingGesture
939                        && event == mPendingMotionEvents.peekFirst()
940                        && event.getAction() != MotionEvent.ACTION_UP
941                        && event.getAction() != MotionEvent.ACTION_CANCEL) {
942                    mTouchEventTimeoutHandler.start(event.getEventTime(), pts);
943                }
944                return EVENT_FORWARDED_TO_NATIVE;
945            }
946        } else if (!mTouchCancelEventSent) {
947            mTouchCancelEventSent = true;
948 
949            MotionEvent previousCancelEvent = mLastCancelledEvent;
950            mLastCancelledEvent = event;
951 
952            if (mMotionEventDelegate.sendTouchEvent(event.getEventTime(),
953                    TouchPoint.TOUCH_EVENT_TYPE_CANCEL, pts)) {
954                return EVENT_CONVERTED_TO_CANCEL;
955            } else {
956                mLastCancelledEvent = previousCancelEvent;
957            }
958        }
959        return EVENT_NOT_FORWARDED;
960    }
961 
962    private boolean processTouchEvent(MotionEvent event) {
963        boolean handled = false;
964        // The last "finger up" is an end to scrolling but may not be
965        // an end to movement (e.g. fling scroll).  We do not tell
966        // native code to end scrolling until we are sure we did not
967        // fling.
968        boolean possiblyEndMovement = false;
969        // "Last finger raised" could be an end to movement.  However,
970        // give the mSimpleTouchDetector a chance to continue
971        // scrolling with a fling.
972        if (event.getAction() == MotionEvent.ACTION_UP) {
973            if (mTouchScrolling) {
974                possiblyEndMovement = true;
975            }
976        }
977 
978        mLongPressDetector.cancelLongPressIfNeeded(event);
979        mLongPressDetector.startLongPressTimerIfNeeded(event);
980 
981        // Use the framework's GestureDetector to detect pans and zooms not already
982        // handled by the WebKit touch events gesture manager.
983        if (canHandle(event)) {
984            handled |= mGestureDetector.onTouchEvent(event);
985            if (event.getAction() == MotionEvent.ACTION_DOWN) {
986                mCurrentDownEvent = MotionEvent.obtain(event);
987            }
988        }
989 
990        handled |= mZoomManager.processTouchEvent(event);
991 
992        if (possiblyEndMovement && !handled) {
993            endTouchScrollIfNecessary(event.getEventTime(), true);
994        }
995 
996        return handled;
997    }
998 
999    /**
1000     * For testing to simulate a timeout of a touch event handler.
1001     */
1002    void mockTouchEventTimeout() {
1003        mTouchEventTimeoutHandler.mockTimeout();
1004    }
1005 
1006    /**
1007     * Respond to a MotionEvent being returned from the native side.
1008     * @param ackResult The status acknowledgment code.
1009     */
1010    void confirmTouchEvent(int ackResult) {
1011        if (mTouchEventTimeoutHandler.confirmTouchEvent()) return;
1012        if (mPendingMotionEvents.isEmpty()) {
1013            Log.w(TAG, "confirmTouchEvent with Empty pending list!");
1014            return;
1015        }
1016        TraceEvent.begin("confirmTouchEvent");
1017        MotionEvent ackedEvent = mPendingMotionEvents.removeFirst();
1018        if (ackedEvent == mLastCancelledEvent) {
1019            // The event is canceled, just drain all the pending events until next
1020            // touch down.
1021            ackResult = INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
1022            TraceEvent.instant("confirmTouchEvent:CanceledEvent");
1023        }
1024        switch (ackResult) {
1025            case INPUT_EVENT_ACK_STATE_UNKNOWN:
1026                // This should never get sent.
1027                assert(false);
1028                break;
1029            case INPUT_EVENT_ACK_STATE_CONSUMED:
1030                mJavaScriptIsConsumingGesture = true;
1031                mZoomManager.passTouchEventThrough(ackedEvent);
1032                trySendPendingEventsToNative();
1033                break;
1034            case INPUT_EVENT_ACK_STATE_NOT_CONSUMED:
1035                if (!mJavaScriptIsConsumingGesture) processTouchEvent(ackedEvent);
1036                trySendPendingEventsToNative();
1037                break;
1038            case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS:
1039                mNoTouchHandlerForGesture = true;
1040                processTouchEvent(ackedEvent);
1041                drainAllPendingEventsUntilNextDown();
1042                break;
1043            default:
1044                break;
1045        }
1046 
1047        mLongPressDetector.cancelLongPressIfNeeded(mPendingMotionEvents.iterator());
1048 
1049        recycleEvent(ackedEvent);
1050        TraceEvent.end("confirmTouchEvent");
1051    }
1052 
1053    private void trySendPendingEventsToNative() {
1054        while (!mPendingMotionEvents.isEmpty()) {
1055            int forward = sendPendingEventToNative();
1056            if (forward != EVENT_NOT_FORWARDED) break;
1057 
1058            // Even though we missed sending one event to native, as long as we haven't
1059            // received INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, we should keep sending
1060            // events on the queue to native.
1061            MotionEvent event = mPendingMotionEvents.removeFirst();
1062            if (!mJavaScriptIsConsumingGesture) processTouchEvent(event);
1063            recycleEvent(event);
1064        }
1065    }
1066 
1067    private void drainAllPendingEventsUntilNextDown() {
1068        // Now process all events that are in the queue until the next down event.
1069        MotionEvent nextEvent = mPendingMotionEvents.peekFirst();
1070        while (nextEvent != null && nextEvent.getActionMasked() != MotionEvent.ACTION_DOWN) {
1071            processTouchEvent(nextEvent);
1072            mPendingMotionEvents.removeFirst();
1073            recycleEvent(nextEvent);
1074            nextEvent = mPendingMotionEvents.peekFirst();
1075        }
1076 
1077        if (nextEvent == null) return;
1078 
1079        mNoTouchHandlerForGesture = false;
1080        trySendPendingEventsToNative();
1081    }
1082 
1083    private void recycleEvent(MotionEvent event) {
1084        if (event == mLastCancelledEvent) {
1085            mLastCancelledEvent = null;
1086        }
1087        event.recycle();
1088    }
1089 
1090    private boolean sendMotionEventAsGesture(
1091            int type, MotionEvent event, Bundle extraParams) {
1092        return mMotionEventDelegate.sendGesture(type, event.getEventTime(),
1093            (int) event.getX(), (int) event.getY(), false, extraParams);
1094    }
1095 
1096    private boolean sendGesture(
1097            int type, long timeMs, int x, int y, Bundle extraParams) {
1098        return mMotionEventDelegate.sendGesture(type, timeMs, x, y, false, extraParams);
1099    }
1100 
1101    private boolean sendLastGestureForVSync(
1102            int type, long timeMs, int x, int y, Bundle extraParams) {
1103        return mMotionEventDelegate.sendGesture(
1104            type, timeMs, x, y, mInputEventsDeliveredAtVSync, extraParams);
1105    }
1106 
1107    void sendShowPressCancelIfNecessary(MotionEvent e) {
1108        if (!mShowPressIsCalled) return;
1109 
1110        if (sendMotionEventAsGesture(GESTURE_SHOW_PRESS_CANCEL, e, null)) {
1111            mShowPressIsCalled = false;
1112        }
1113    }
1114 
1115    /**
1116     * @return Whether the ContentViewGestureHandler can handle a MotionEvent right now. True only
1117     * if it's the start of a new stream (ACTION_DOWN), or a continuation of the current stream.
1118     */
1119    boolean canHandle(MotionEvent ev) {
1120        return ev.getAction() == MotionEvent.ACTION_DOWN ||
1121                (mCurrentDownEvent != null && mCurrentDownEvent.getDownTime() == ev.getDownTime());
1122    }
1123 
1124    /**
1125     * @return Whether the event can trigger a LONG_TAP gesture. True when it can and the event
1126     * will be consumed.
1127     */
1128    boolean triggerLongTapIfNeeded(MotionEvent ev) {
1129        if (mLongPressDetector.isInLongPress() && ev.getAction() == MotionEvent.ACTION_UP &&
1130                !mZoomManager.isScaleGestureDetectionInProgress()) {
1131            sendShowPressCancelIfNecessary(ev);
1132            sendMotionEventAsGesture(GESTURE_LONG_TAP, ev, null);
1133            return true;
1134        }
1135        return false;
1136    }
1137 
1138    /**
1139     * This is for testing only.
1140     * @return The first motion event on the pending motion events queue.
1141     */
1142    MotionEvent peekFirstInPendingMotionEventsForTesting() {
1143        return mPendingMotionEvents.peekFirst();
1144    }
1145 
1146    /**
1147     * This is for testing only.
1148     * @return Whether the motion event is cancelled.
1149     */
1150    boolean isEventCancelledForTesting(MotionEvent event) {
1151        return event != null && event == mLastCancelledEvent;
1152    }
1153 
1154    /**
1155     * This is for testing only.
1156     * @return The number of motion events on the pending motion events queue.
1157     */
1158    int getNumberOfPendingMotionEventsForTesting() {
1159        return mPendingMotionEvents.size();
1160    }
1161 
1162    /**
1163     * This is for testing only.
1164     * Sends a show pressed state gesture through mListener. This should always be called after
1165     * a down event;
1166     */
1167    void sendShowPressedStateGestureForTesting() {
1168        if (mCurrentDownEvent == null) return;
1169        mListener.onShowPress(mCurrentDownEvent);
1170    }
1171 
1172    /**
1173     * This is for testing only.
1174     * @return Whether a touch timeout event has been scheduled.
1175     */
1176    boolean hasScheduledTouchTimeoutEventForTesting() {
1177        return mTouchEventTimeoutHandler.hasScheduledTimeoutEventForTesting();
1178    }
1179 
1180    public void updateDoubleTapDragSupport(boolean supportDoubleTapDrag) {
1181        assert (mDoubleTapDragMode == DOUBLE_TAP_DRAG_MODE_DISABLED ||
1182                mDoubleTapDragMode == DOUBLE_TAP_DRAG_MODE_NONE);
1183        mDoubleTapDragMode = supportDoubleTapDrag ?
1184                DOUBLE_TAP_DRAG_MODE_NONE : DOUBLE_TAP_DRAG_MODE_DISABLED;
1185    }
1186 
1187    private boolean isDoubleTapDragDisabled() {
1188        return mDoubleTapDragMode == DOUBLE_TAP_DRAG_MODE_DISABLED;
1189    }
1190}

[all classes][org.chromium.content.browser]
EMMA 2.0.5312 (C) Vladimir Roubtsov