| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.content.browser; |
| |
| import android.content.Context; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.SystemClock; |
| import android.test.InstrumentationTestCase; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.MotionEvent.PointerCoords; |
| import android.view.MotionEvent.PointerProperties; |
| import android.view.ViewConfiguration; |
| |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate; |
| import org.chromium.content.browser.third_party.GestureDetector; |
| |
| import java.util.ArrayList; |
| import java.util.concurrent.CountDownLatch; |
| |
| /** |
| * Test suite for ContentViewGestureHandler. |
| */ |
| public class ContentViewGestureHandlerTest extends InstrumentationTestCase { |
| private static final int FAKE_COORD_X = 42; |
| private static final int FAKE_COORD_Y = 24; |
| |
| private static final String TAG = "ContentViewGestureHandler"; |
| private MockListener mMockListener; |
| private MockMotionEventDelegate mMockMotionEventDelegate; |
| private MockGestureDetector mMockGestureDetector; |
| private ContentViewGestureHandler mGestureHandler; |
| private LongPressDetector mLongPressDetector; |
| |
| static class MockListener extends GestureDetector.SimpleOnGestureListener { |
| MotionEvent mLastLongPress; |
| MotionEvent mLastSingleTap; |
| MotionEvent mLastFling1; |
| CountDownLatch mLongPressCalled; |
| |
| public MockListener() { |
| mLongPressCalled = new CountDownLatch(1); |
| } |
| |
| @Override |
| public void onLongPress(MotionEvent e) { |
| mLastLongPress = MotionEvent.obtain(e); |
| mLongPressCalled.countDown(); |
| } |
| |
| @Override |
| public boolean onSingleTapConfirmed(MotionEvent e) { |
| mLastSingleTap = e; |
| return true; |
| } |
| |
| @Override |
| public boolean onSingleTapUp(MotionEvent e) { |
| mLastSingleTap = e; |
| return true; |
| } |
| |
| @Override |
| public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { |
| return true; |
| } |
| |
| @Override |
| public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { |
| mLastFling1 = e1; |
| return true; |
| } |
| } |
| |
| static class MockGestureDetector extends GestureDetector { |
| MotionEvent mLastEvent; |
| public MockGestureDetector(Context context, OnGestureListener listener) { |
| super(context, listener); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| mLastEvent = MotionEvent.obtain(ev); |
| return super.onTouchEvent(ev); |
| } |
| } |
| |
| static class MockMotionEventDelegate implements MotionEventDelegate { |
| private ContentViewGestureHandler mSynchronousConfirmTarget; |
| private int mSynchronousConfirmAckResult; |
| |
| public int mLastTouchAction; |
| public int mLastGestureType; |
| |
| @Override |
| public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) { |
| mLastTouchAction = action; |
| if (mSynchronousConfirmTarget != null) { |
| mSynchronousConfirmTarget.confirmTouchEvent(mSynchronousConfirmAckResult); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean sendGesture(int type, long timeMs, int x, int y, |
| boolean lastInputEventForVSync, Bundle extraParams) { |
| Log.i(TAG,"Gesture event received with type id " + type); |
| mLastGestureType = type; |
| return true; |
| } |
| |
| @Override |
| public boolean didUIStealScroll(float x, float y) { |
| // Not implemented. |
| return false; |
| } |
| |
| @Override |
| public void invokeZoomPicker() { |
| // Not implemented. |
| } |
| |
| @Override |
| public boolean hasFixedPageScale() { |
| return false; |
| } |
| |
| public void enableSynchronousConfirmTouchEvent( |
| ContentViewGestureHandler handler, int ackResult) { |
| mSynchronousConfirmTarget = handler; |
| mSynchronousConfirmAckResult = ackResult; |
| } |
| |
| public void disableSynchronousConfirmTouchEvent() { |
| mSynchronousConfirmTarget = null; |
| } |
| } |
| |
| static class MockZoomManager extends ZoomManager { |
| MockZoomManager(Context context, ContentViewCore contentViewCore) { |
| super(context, contentViewCore); |
| } |
| |
| @Override |
| public boolean processTouchEvent(MotionEvent event) { |
| return false; |
| } |
| } |
| |
| static private MotionEvent motionEvent(int action, long downTime, long eventTime) { |
| return MotionEvent.obtain(downTime, eventTime, action, FAKE_COORD_X, FAKE_COORD_Y, 0); |
| } |
| |
| @Override |
| public void setUp() { |
| mMockListener = new MockListener(); |
| mMockGestureDetector = new MockGestureDetector( |
| getInstrumentation().getTargetContext(), mMockListener); |
| mMockMotionEventDelegate = new MockMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mMockMotionEventDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = new LongPressDetector( |
| getInstrumentation().getTargetContext(), mGestureHandler); |
| mGestureHandler.setTestDependencies( |
| mLongPressDetector, mMockGestureDetector, mMockListener); |
| TouchPoint.initializeConstantsForTesting(); |
| } |
| |
| /** |
| * Verify that a DOWN followed shortly by an UP will trigger a single tap. |
| * |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testGestureSingleClick() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| assertFalse(mGestureHandler.onTouchEvent(event)); |
| assertTrue("Should have a pending gesture", mMockGestureDetector.mLastEvent != null); |
| assertTrue("Should have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| event = motionEvent(MotionEvent.ACTION_UP, downTime, eventTime + 10); |
| mLongPressDetector.cancelLongPressIfNeeded(event); |
| assertTrue("Should not have a pending LONG_PRESS", !mLongPressDetector.hasPendingMessage()); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| // Synchronous, no need to wait. |
| assertTrue("Should have a single tap", mMockListener.mLastSingleTap != null); |
| } |
| |
| /** |
| * Verify that when a touch event handler is registered the touch events are queued |
| * and sent in order. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testFlingOnTouchHandler() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertTrue("Should not have a pending gesture", mMockGestureDetector.mLastEvent == null); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("We should have coalesced move events into one" |
| , 2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(3, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals(MotionEvent.ACTION_MOVE, |
| mGestureHandler.peekFirstInPendingMotionEventsForTesting().getActionMasked()); |
| assertFalse("Pending LONG_PRESS should have been canceled", |
| mLongPressDetector.hasPendingMessage()); |
| |
| mGestureHandler.confirmTouchEvent(ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals(MotionEvent.ACTION_UP, |
| mGestureHandler.peekFirstInPendingMotionEventsForTesting().getActionMasked()); |
| |
| mGestureHandler.confirmTouchEvent(ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| // Synchronous, no need to wait. |
| assertTrue("Should not have a fling", mMockListener.mLastFling1 == null); |
| assertTrue("Should not have a long press", mMockListener.mLastLongPress == null); |
| } |
| |
| /** |
| * Verify the behavior of touch event timeout handler. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testTouchEventTimeoutHandler() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| // Queue a touch move event. |
| event = MotionEvent.obtain( |
| downTime, eventTime + 50, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| mGestureHandler.mockTouchEventTimeout(); |
| // On timeout, the pending queue should be cleared. |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| // No new touch events should be sent to the touch handler before the timed-out event |
| // gets ACK'ed. |
| event = MotionEvent.obtain( |
| downTime, eventTime + 200, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| mGestureHandler.onTouchEvent(event); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| // When the timed-out event gets ACK'ed, a cancel event should be sent. |
| mGestureHandler.confirmTouchEvent(ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals(TouchPoint.TOUCH_EVENT_TYPE_CANCEL, mMockMotionEventDelegate.mLastTouchAction); |
| |
| // No new touch events should be sent to the touch handler before the cancel event |
| // gets ACK'ed. |
| event = MotionEvent.obtain( |
| downTime, eventTime + 300, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 20, FAKE_COORD_Y * 20, 0); |
| mGestureHandler.onTouchEvent(event); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| // After cancel event is ACK'ed, the handler should return to normal state. |
| event = MotionEvent.obtain( |
| downTime + 400, eventTime + 400, MotionEvent.ACTION_DOWN, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| mGestureHandler.confirmTouchEvent(ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| } |
| |
| /** |
| * Verify that after a touch event handlers starts handling a gesture, even though some event |
| * in the middle of the gesture returns with NOT_CONSUMED, we don't send that to the gesture |
| * detector to avoid falling to a faulty state. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testFlingOnTouchHandlerWithOneEventNotConsumed() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertTrue("Should not have a pending gesture", mMockGestureDetector.mLastEvent == null); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("We should have coalesced move events into one" |
| , 2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(3, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals(MotionEvent.ACTION_MOVE, |
| mGestureHandler.peekFirstInPendingMotionEventsForTesting().getActionMasked()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals(MotionEvent.ACTION_UP, |
| mGestureHandler.peekFirstInPendingMotionEventsForTesting().getActionMasked()); |
| assertTrue("Even though the last event was not consumed by JavaScript," + |
| "it shouldn't have been sent to the Gesture Detector", |
| mMockGestureDetector.mLastEvent == null); |
| |
| mGestureHandler.confirmTouchEvent(ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| // Synchronous, no need to wait. |
| assertTrue("Should not have a fling", mMockListener.mLastFling1 == null); |
| assertTrue("Should not have a long press", mMockListener.mLastLongPress == null); |
| } |
| |
| /** |
| * Verify that when a registered touch event handler return NO_CONSUMER_EXISTS for down event |
| * all queue is drained until next down. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testDrainWithFlingAndClickOutofTouchHandler() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), new MockMotionEventDelegate(), |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = new LongPressDetector( |
| getInstrumentation().getTargetContext(), mGestureHandler); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(3, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = motionEvent(MotionEvent.ACTION_DOWN, eventTime + 20, eventTime + 20); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(4, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 20, MotionEvent.ACTION_UP, |
| FAKE_COORD_X, FAKE_COORD_Y, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(5, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); |
| assertEquals("The queue should have been drained until first down since no consumer exists", |
| 2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals(MotionEvent.ACTION_DOWN, |
| mGestureHandler.peekFirstInPendingMotionEventsForTesting().getActionMasked()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); |
| assertEquals("The queue should have been drained", |
| 0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| } |
| |
| /** |
| * Verify that when a touch event handler is registered the touch events stop getting queued |
| * after we received INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testFlingOutOfTouchHandler() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertTrue("Should not have a pending gesture", mMockGestureDetector.mLastEvent == null); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals("The down touch event should have been sent to the Gesture Detector", |
| event.getEventTime(), mMockGestureDetector.mLastEvent.getEventTime()); |
| assertEquals("The down touch event should have been sent to the Gesture Detector", |
| MotionEvent.ACTION_DOWN, mMockGestureDetector.mLastEvent.getActionMasked()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals("Motion events should be going to the Gesture Detector directly", |
| event.getEventTime(), mMockGestureDetector.mLastEvent.getEventTime()); |
| assertEquals("Motion events should be going to the Gesture Detector directly", |
| MotionEvent.ACTION_MOVE, mMockGestureDetector.mLastEvent.getActionMasked()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals("Motion events should be going to the Gesture Detector directly", |
| event.getEventTime(), mMockGestureDetector.mLastEvent.getEventTime()); |
| assertEquals("Motion events should be going to the Gesture Detector directly", |
| MotionEvent.ACTION_UP, mMockGestureDetector.mLastEvent.getActionMasked()); |
| |
| // Synchronous, no need to wait. |
| assertTrue("Should have a fling", mMockListener.mLastFling1 != null); |
| assertTrue("Should not have a long press", mMockListener.mLastLongPress == null); |
| } |
| |
| /** |
| * Verifies that a single tap doesn't cause a long press event to be sent. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testNoLongPressIsSentForSingleTapOutOfTouchHandler() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertTrue("Should not have a pending gesture", mMockGestureDetector.mLastEvent == null); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| event = motionEvent(MotionEvent.ACTION_UP, downTime, eventTime + 5); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals("The down touch event should have been sent to the Gesture Detector", |
| event.getDownTime(), mMockGestureDetector.mLastEvent.getEventTime()); |
| assertEquals("The next event should be ACTION_UP", |
| MotionEvent.ACTION_UP, |
| mGestureHandler.peekFirstInPendingMotionEventsForTesting().getActionMasked()); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals("The up touch event should have been sent to the Gesture Detector", |
| event.getEventTime(), mMockGestureDetector.mLastEvent.getEventTime()); |
| |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| } |
| |
| /** |
| * Verify that a DOWN followed by a MOVE will trigger fling (but not LONG). |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testGestureFlingAndCancelLongClick() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| assertFalse(mGestureHandler.onTouchEvent(event)); |
| assertTrue("Should have a pending gesture", mMockGestureDetector.mLastEvent != null); |
| assertTrue("Should have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| mLongPressDetector.cancelLongPressIfNeeded(event); |
| assertTrue("Should not have a pending LONG_PRESS", !mLongPressDetector.hasPendingMessage()); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| // Synchronous, no need to wait. |
| assertTrue("Should have a fling", mMockListener.mLastFling1 != null); |
| assertTrue("Should not have a long press", mMockListener.mLastLongPress == null); |
| } |
| |
| /** |
| * Verify that for a normal scroll the following events are sent: |
| * - GESTURE_SCROLL_START |
| * - GESTURE_SCROLL_BY |
| * - GESTURE_SCROLL_END |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testScrollEventActionUpSequence() throws Exception { |
| checkScrollEventSequenceForEndActionType(MotionEvent.ACTION_UP); |
| } |
| |
| /** |
| * Verify that for a cancelled scroll the following events are sent: |
| * - GESTURE_SCROLL_START |
| * - GESTURE_SCROLL_BY |
| * - GESTURE_SCROLL_END |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testScrollEventActionCancelSequence() throws Exception { |
| checkScrollEventSequenceForEndActionType(MotionEvent.ACTION_CANCEL); |
| } |
| |
| private void checkScrollEventSequenceForEndActionType(int endActionType) throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| final int scrollToX = FAKE_COORD_X + 100; |
| final int scrollToY = FAKE_COORD_Y + 100; |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, mMockZoomManager, |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 1000, MotionEvent.ACTION_MOVE, scrollToX, scrollToY, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue(mGestureHandler.isNativeScrolling()); |
| assertTrue("A scrollStart event should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_START)); |
| assertEquals("We should have started scrolling", |
| ContentViewGestureHandler.GESTURE_SCROLL_BY, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only scrollBegin and scrollBy should have been sent", |
| 2, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 1000, endActionType, scrollToX, scrollToY, 0); |
| mGestureHandler.onTouchEvent(event); |
| assertFalse(mGestureHandler.isNativeScrolling()); |
| assertTrue("A scrollEnd event should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_END)); |
| assertEquals("We should have stopped scrolling", |
| ContentViewGestureHandler.GESTURE_SCROLL_END, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only scrollBegin and scrollBy and scrollEnd should have been sent", |
| 3, mockDelegate.mGestureTypeList.size()); |
| } |
| |
| /** |
| * Verify that for a normal fling (fling after scroll) the following events are sent: |
| * - GESTURE_SCROLL_BEGIN |
| * - GESTURE_FLING_START |
| * and GESTURE_FLING_CANCEL is sent on the next touch. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testFlingEventSequence() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue(mGestureHandler.isNativeScrolling()); |
| assertTrue("A scrollStart event should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_START)); |
| assertEquals("We should have started scrolling", |
| ContentViewGestureHandler.GESTURE_SCROLL_BY, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only scrollBegin and scrollBy should have been sent", |
| 2, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertFalse(mGestureHandler.isNativeScrolling()); |
| assertEquals("We should have started flinging", |
| ContentViewGestureHandler.GESTURE_FLING_START, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertTrue("A scroll end event should not have been sent", |
| !mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_END)); |
| assertEquals("The last up should have caused flingStart to be sent", |
| 3, mockDelegate.mGestureTypeList.size()); |
| |
| event = motionEvent(MotionEvent.ACTION_DOWN, downTime + 50, downTime + 50); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue("A flingCancel should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_FLING_CANCEL)); |
| assertEquals("Only flingCancel should have been sent", |
| 4, mockDelegate.mGestureTypeList.size()); |
| } |
| |
| /** |
| * Verify that a show pressed state gesture followed by a long press followed by |
| * the focus |
| * loss in the window due to context menu cancels show pressed. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testShowPressCancelOnWindowFocusLost() throws Exception { |
| final long time = SystemClock.uptimeMillis(); |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = new LongPressDetector( |
| getInstrumentation().getTargetContext(), mGestureHandler); |
| mGestureHandler.setTestDependencies(mLongPressDetector, null, null); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, time, time); |
| mGestureHandler.onTouchEvent(event); |
| |
| mGestureHandler.sendShowPressedStateGestureForTesting(); |
| assertEquals("A show pressed state event should have been sent", |
| ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only showPressedState should have been sent", |
| 1, mockDelegate.mGestureTypeList.size()); |
| |
| mLongPressDetector.startLongPressTimerIfNeeded(event); |
| mLongPressDetector.sendLongPressGestureForTest(); |
| |
| assertEquals("Only should have sent only LONG_PRESS event", |
| 2, mockDelegate.mGestureTypeList.size()); |
| assertEquals("Should have a long press event next", |
| ContentViewGestureHandler.GESTURE_LONG_PRESS, |
| mockDelegate.mGestureTypeList.get(1).intValue()); |
| |
| // The long press triggers window focus loss by opening a context menu |
| mGestureHandler.onWindowFocusLost(); |
| |
| assertEquals("Only should have sent only GESTURE_SHOW_PRESS_CANCEL event", |
| 3, mockDelegate.mGestureTypeList.size()); |
| assertEquals("Should have a long press event next", |
| ContentViewGestureHandler.GESTURE_SHOW_PRESS_CANCEL, |
| mockDelegate.mGestureTypeList.get(2).intValue()); |
| } |
| |
| /** |
| * Verify that a recent show pressed state gesture is canceled when scrolling begins. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testShowPressCancelWhenScrollBegins() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = new LongPressDetector( |
| getInstrumentation().getTargetContext(), mGestureHandler); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| mGestureHandler.sendShowPressedStateGestureForTesting(); |
| |
| assertEquals("A show pressed state event should have been sent", |
| ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only showPressedState should have been sent", |
| 1, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| assertEquals("We should have started scrolling", |
| ContentViewGestureHandler.GESTURE_SCROLL_BY, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertTrue("A show press cancel event should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SHOW_PRESS_CANCEL)); |
| assertEquals("Only showPressedState, showPressCancel, scrollBegin and scrollBy" + |
| " should have been sent", |
| 4, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("We should have started flinging", |
| ContentViewGestureHandler.GESTURE_FLING_START, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertTrue("A scroll end event should not have been sent", |
| !mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_END)); |
| assertEquals("The last up should have caused flingStart to be sent", |
| 5, mockDelegate.mGestureTypeList.size()); |
| } |
| |
| /** |
| * Verify that double tap is correctly handled including the recent show pressed state gesture |
| * cancellation. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testDoubleTap() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = new LongPressDetector( |
| getInstrumentation().getTargetContext(), mGestureHandler); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| mGestureHandler.sendShowPressedStateGestureForTesting(); |
| assertEquals("GESTURE_SHOW_PRESSED_STATE should have been sent", |
| ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE should have been sent", |
| 1, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_UP, |
| FAKE_COORD_X, FAKE_COORD_Y, 0); |
| mGestureHandler.onTouchEvent(event); |
| assertEquals("A GESTURE_SINGLE_TAP_UNCONFIRMED event should have been sent", |
| ContentViewGestureHandler.GESTURE_SINGLE_TAP_UNCONFIRMED, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE and " + |
| "GESTURE_SINGLE_TAP_UNCONFIRMED should have been sent", |
| 2, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| eventTime + 10, eventTime + 10, MotionEvent.ACTION_DOWN, |
| FAKE_COORD_X, FAKE_COORD_Y, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("A GESTURE_SINGLE_TAP_UNCONFIRMED event should have been sent ", |
| ContentViewGestureHandler.GESTURE_SHOW_PRESS_CANCEL, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE, " + |
| "GESTURE_SINGLE_TAP_UNCONFIRMED, and " + |
| "GESTURE_SHOW_PRESS_CANCEL should have been sent", |
| 3, mockDelegate.mGestureTypeList.size()); |
| |
| // Moving a very small amount of distance should not trigger the double tap drag zoom mode. |
| event = MotionEvent.obtain( |
| eventTime + 10, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y + 1, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE, " + |
| "GESTURE_SINGLE_TAP_UNCONFIRMED, and " + |
| "GESTURE_SHOW_PRESS_CANCEL should have been sent", |
| 3, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| eventTime + 10, eventTime + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X, FAKE_COORD_Y + 1, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("A double tap should have occurred", |
| ContentViewGestureHandler.GESTURE_DOUBLE_TAP, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE, " + |
| "GESTURE_SINGLE_TAP_UNCONFIRMED, " + |
| "GESTURE_SHOW_PRESS_CANCEL, and " + |
| "GESTURE_DOUBLE_TAP should have been sent", |
| 4, mockDelegate.mGestureTypeList.size()); |
| } |
| |
| /** |
| * Verify that double tap drag zoom feature is correctly executed. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testDoubleTapDragZoom() throws Exception { |
| final long downTime1 = SystemClock.uptimeMillis(); |
| final long downTime2 = downTime1 + 100; |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = new LongPressDetector( |
| getInstrumentation().getTargetContext(), mGestureHandler); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime1, downTime1); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| mGestureHandler.sendShowPressedStateGestureForTesting(); |
| assertEquals("GESTURE_SHOW_PRESSED_STATE should have been sent", |
| ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE should have been sent", |
| 1, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime1, downTime1 + 5, MotionEvent.ACTION_UP, |
| FAKE_COORD_X, FAKE_COORD_Y, 0); |
| mGestureHandler.onTouchEvent(event); |
| assertEquals("A GESTURE_SINGLE_TAP_UNCONFIRMED event should have been sent", |
| ContentViewGestureHandler.GESTURE_SINGLE_TAP_UNCONFIRMED, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE and " + |
| "GESTURE_TAB_UNCONFIRMED " + |
| "should have been sent", |
| 2, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2, MotionEvent.ACTION_DOWN, |
| FAKE_COORD_X, FAKE_COORD_Y, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("GESTURE_SHOW_PRESS_CANCEL should have been sent", |
| ContentViewGestureHandler.GESTURE_SHOW_PRESS_CANCEL, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE, " + |
| "GESTURE_TAB_UNCONFIRMED, and " + |
| "GESTURE_SHOW_PRESS_CANCEL should have been sent", |
| 3, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2 + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y + 100, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue("GESTURE_SCROLL_START should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_START)); |
| assertEquals("GESTURE_PINCH_BEGIN should have been sent", |
| ContentViewGestureHandler.GESTURE_PINCH_BEGIN, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE, " + |
| "GESTURE_TAB_UNCONFIRMED," + |
| "GESTURE_SHOW_PRESS_CANCEL, " + |
| "GESTURE_SCROLL_START, and " + |
| "GESTURE_PINCH_BEGIN should have been sent", |
| 5, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2 + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y + 200, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue("GESTURE_SCROLL_BY should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_BY)); |
| assertEquals("GESTURE_PINCH_BY should have been sent", |
| ContentViewGestureHandler.GESTURE_PINCH_BY, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE, " + |
| "GESTURE_TAB_UNCONFIRMED," + |
| "GESTURE_SHOW_PRESS_CANCEL, " + |
| "GESTURE_SCROLL_START," + |
| "GESTURE_PINCH_BEGIN, " + |
| "GESTURE_SCROLL_BY, and " + |
| "GESTURE_PINCH_BY should have been sent", |
| 7, mockDelegate.mGestureTypeList.size()); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2 + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X, FAKE_COORD_Y + 200, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue("GESTURE_PINCH_END should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_PINCH_END)); |
| assertEquals("GESTURE_SCROLL_END should have been sent", |
| ContentViewGestureHandler.GESTURE_SCROLL_END, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| assertEquals("Only GESTURE_SHOW_PRESSED_STATE, " + |
| "GESTURE_TAB_UNCONFIRMED," + |
| "GESTURE_SHOW_PRESS_CANCEL, " + |
| "GESTURE_SCROLL_START," + |
| "GESTURE_PINCH_BEGIN, " + |
| "GESTURE_SCROLL_BY," + |
| "GESTURE_PINCH_BY, " + |
| "GESTURE_PINCH_END, and " + |
| "GESTURE_SCROLL_END should have been sent", |
| 9, mockDelegate.mGestureTypeList.size()); |
| } |
| |
| /** |
| * Verify that double tap drag zoom feature is not invoked |
| * when it is disabled.. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testDoubleTapDragZoomNothingWhenDisabled() throws Exception { |
| final long downTime1 = SystemClock.uptimeMillis(); |
| final long downTime2 = downTime1 + 100; |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| |
| mGestureHandler.updateDoubleTapDragSupport(false); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime1, downTime1); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| event = MotionEvent.obtain( |
| downTime1, downTime1 + 5, MotionEvent.ACTION_UP, |
| FAKE_COORD_X, FAKE_COORD_Y, 0); |
| mGestureHandler.onTouchEvent(event); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2, MotionEvent.ACTION_DOWN, |
| FAKE_COORD_X, FAKE_COORD_Y, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2 + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y + 100, 0); |
| // As double tap and drag to zoom is disabled, we won't handle |
| // the move event. |
| assertFalse(mGestureHandler.onTouchEvent(event)); |
| |
| assertFalse("No GESTURE_SCROLL_START should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_START)); |
| assertTrue("No GESTURE_PINCH_BEGIN should have been sent", |
| ContentViewGestureHandler.GESTURE_PINCH_BEGIN != |
| mockDelegate.mMostRecentGestureEvent.mType); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2 + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y + 200, 0); |
| assertFalse(mGestureHandler.onTouchEvent(event)); |
| assertFalse("No GESTURE_SCROLL_BY should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_SCROLL_BY)); |
| assertTrue("No GESTURE_PINCH_BY should have been sent", |
| ContentViewGestureHandler.GESTURE_PINCH_BY != |
| mockDelegate.mMostRecentGestureEvent.mType); |
| |
| event = MotionEvent.obtain( |
| downTime2, downTime2 + 15, MotionEvent.ACTION_UP, |
| FAKE_COORD_X, FAKE_COORD_Y + 200, 0); |
| assertFalse(mGestureHandler.onTouchEvent(event)); |
| assertFalse("No GESTURE_PINCH_END should have been sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_PINCH_END)); |
| } |
| |
| /** |
| * Mock MotionEventDelegate that remembers the most recent gesture event. |
| */ |
| static class GestureRecordingMotionEventDelegate implements MotionEventDelegate { |
| static class GestureEvent { |
| private final int mType; |
| private final long mTimeMs; |
| private final int mX; |
| private final int mY; |
| private final Bundle mExtraParams; |
| |
| public GestureEvent(int type, long timeMs, int x, int y, Bundle extraParams) { |
| mType = type; |
| mTimeMs = timeMs; |
| mX = x; |
| mY = y; |
| mExtraParams = extraParams; |
| } |
| |
| public int getType() { |
| return mType; |
| } |
| |
| public long getTimeMs() { |
| return mTimeMs; |
| } |
| |
| public int getX() { |
| return mX; |
| } |
| |
| public int getY() { |
| return mY; |
| } |
| |
| public Bundle getExtraParams() { |
| return mExtraParams; |
| } |
| }; |
| private GestureEvent mMostRecentGestureEvent; |
| private boolean mMostRecentGestureEventWasLastForVSync; |
| private final ArrayList<Integer> mGestureTypeList = new ArrayList<Integer>(); |
| |
| @Override |
| public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) { |
| // Not implemented. |
| return false; |
| } |
| |
| @Override |
| public boolean sendGesture(int type, long timeMs, int x, int y, |
| boolean lastInputEventForVSync, Bundle extraParams) { |
| mMostRecentGestureEvent = new GestureEvent(type, timeMs, x, y, extraParams); |
| mMostRecentGestureEventWasLastForVSync = lastInputEventForVSync; |
| mGestureTypeList.add(mMostRecentGestureEvent.mType); |
| return true; |
| } |
| |
| @Override |
| public boolean didUIStealScroll(float x, float y) { |
| // Not implemented. |
| return false; |
| } |
| |
| @Override |
| public void invokeZoomPicker() { |
| // Not implemented. |
| } |
| |
| @Override |
| public boolean hasFixedPageScale() { |
| return false; |
| } |
| |
| public GestureEvent getMostRecentGestureEvent() { |
| return mMostRecentGestureEvent; |
| } |
| |
| public boolean mostRecentGestureEventForLastForVSync() { |
| return mMostRecentGestureEventWasLastForVSync; |
| } |
| } |
| |
| /** |
| * Verify that the first event sent while the page is scrolling will be |
| * converted to a touchcancel. The touchcancel event should stay in the |
| * pending queue. Acking the touchcancel event will consume all the touch |
| * events of the current session. |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testTouchEventsCanceledWhileScrolling() { |
| final int deltaY = 84; |
| final long downTime = SystemClock.uptimeMillis(); |
| |
| MockMotionEventDelegate delegate = new MockMotionEventDelegate(); |
| ContentViewGestureHandler gestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), delegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| gestureHandler.hasTouchEventHandlers(true); |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(gestureHandler.onTouchEvent(event)); |
| gestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| event = MotionEvent.obtain( |
| downTime, downTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y - deltaY / 2, 0); |
| assertTrue(gestureHandler.onTouchEvent(event)); |
| gestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| |
| // This event will be converted to touchcancel and put into the pending |
| // queue. |
| event = MotionEvent.obtain( |
| downTime, downTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y - deltaY, 0); |
| assertTrue(gestureHandler.onTouchEvent(event)); |
| assertEquals(1, gestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertTrue(gestureHandler.isEventCancelledForTesting( |
| gestureHandler.peekFirstInPendingMotionEventsForTesting())); |
| MotionEvent canceledEvent = gestureHandler.peekFirstInPendingMotionEventsForTesting(); |
| |
| event = motionEvent(MotionEvent.ACTION_POINTER_DOWN, downTime + 15, downTime + 15); |
| assertTrue(gestureHandler.onTouchEvent(event)); |
| assertEquals(2, gestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| // Acking the touchcancel will drain all the events, and clear the event previously marked |
| // for cancellation. |
| gestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NOT_CONSUMED); |
| assertEquals(0, gestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertFalse(gestureHandler.isEventCancelledForTesting(canceledEvent)); |
| |
| // Note: This check relies on an implementation detail of MotionEvent, namely, that the last |
| // event recycled is the the first returned by MotionEvent.obtain(). Should |
| // MotioEvent's implementation change, this assumption will need to be rebased. |
| MotionEvent recycledCanceledEvent = |
| motionEvent(MotionEvent.ACTION_DOWN, downTime + 20 , downTime + 20); |
| assertSame("The canceled event should have been recycled.", |
| canceledEvent, recycledCanceledEvent); |
| } |
| |
| /** |
| * Generate a scroll gesture and verify that the resulting scroll motion event has both absolute |
| * and relative position information. |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testScrollUpdateCoordinates() { |
| final int deltaX = 16; |
| final int deltaY = 84; |
| final long downTime = SystemClock.uptimeMillis(); |
| |
| GestureRecordingMotionEventDelegate delegate = new GestureRecordingMotionEventDelegate(); |
| ContentViewGestureHandler gestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), delegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(gestureHandler.onTouchEvent(event)); |
| |
| // Move twice, because the first move gesture is discarded. |
| event = MotionEvent.obtain( |
| downTime, downTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X - deltaX / 2, FAKE_COORD_Y - deltaY / 2, 0); |
| assertTrue(gestureHandler.onTouchEvent(event)); |
| |
| event = MotionEvent.obtain( |
| downTime, downTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X - deltaX, FAKE_COORD_Y - deltaY, 0); |
| assertTrue(gestureHandler.onTouchEvent(event)); |
| |
| // Make sure the reported gesture event has all the expected data. |
| GestureRecordingMotionEventDelegate.GestureEvent gestureEvent = |
| delegate.getMostRecentGestureEvent(); |
| assertNotNull(gestureEvent); |
| assertEquals(ContentViewGestureHandler.GESTURE_SCROLL_BY, gestureEvent.getType()); |
| assertEquals(downTime + 10, gestureEvent.getTimeMs()); |
| assertEquals(FAKE_COORD_X - deltaX, gestureEvent.getX()); |
| assertEquals(FAKE_COORD_Y - deltaY, gestureEvent.getY()); |
| |
| Bundle extraParams = gestureEvent.getExtraParams(); |
| assertNotNull(extraParams); |
| // No horizontal delta because of snapping. |
| assertEquals(0, extraParams.getInt(ContentViewGestureHandler.DISTANCE_X)); |
| assertEquals(deltaY / 2, extraParams.getInt(ContentViewGestureHandler.DISTANCE_Y)); |
| } |
| |
| /** |
| * Verify that the timer of LONG_PRESS will be cancelled when scrolling begins so |
| * LONG_PRESS and LONG_TAP won't be triggered. |
| * |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testLongPressAndTapCancelWhenScrollBegins() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = mGestureHandler.getLongPressDetector(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue("Should have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue("Should not have a pending LONG_PRESS", !mLongPressDetector.hasPendingMessage()); |
| |
| // No LONG_TAP because LONG_PRESS timer is cancelled. |
| assertFalse("No LONG_PRESS should be sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_LONG_PRESS)); |
| assertFalse("No LONG_TAP should be sent", |
| mockDelegate.mGestureTypeList.contains( |
| ContentViewGestureHandler.GESTURE_LONG_TAP)); |
| } |
| |
| /** |
| * Verifies that when hasTouchEventHandlers changes while in a gesture, that the pending |
| * queue does not grow continually. |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testHasTouchEventHandlersChangesInGesture() { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| MockMotionEventDelegate eventDelegate = new MockMotionEventDelegate() { |
| @Override |
| public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) { |
| if (action == TouchPoint.TOUCH_EVENT_TYPE_CANCEL) { |
| // Ensure the event to be cancelled is already in the pending queue. |
| MotionEvent canceledEvent = |
| mGestureHandler.peekFirstInPendingMotionEventsForTesting(); |
| assertTrue(mGestureHandler.isEventCancelledForTesting(canceledEvent)); |
| |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS); |
| |
| // Ensure that the canceled event has been removed from the event queue and is |
| // no longer scheduled for cancellation. |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertFalse(mGestureHandler.isEventCancelledForTesting(canceledEvent)); |
| } |
| return super.sendTouchEvent(timeMs, action, pts); |
| } |
| }; |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), eventDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = new LongPressDetector( |
| getInstrumentation().getTargetContext(), mGestureHandler); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 15, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 15, FAKE_COORD_Y * 15, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| } |
| |
| /** |
| * Verify that LONG_TAP is triggered after LongPress followed by an UP. |
| * |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testGestureLongTap() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| mLongPressDetector = mGestureHandler.getLongPressDetector(); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertTrue("Should have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| |
| mLongPressDetector.sendLongPressGestureForTest(); |
| |
| assertEquals("A LONG_PRESS gesture should have been sent", |
| ContentViewGestureHandler.GESTURE_LONG_PRESS, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| |
| event = motionEvent(MotionEvent.ACTION_UP, downTime, eventTime + 1000); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("A LONG_TAP gesture should have been sent", |
| ContentViewGestureHandler.GESTURE_LONG_TAP, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| } |
| |
| /** |
| * Verify that the touch slop region is removed from the first scroll delta to avoid a jump when |
| * starting to scroll. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testTouchSlopRemovedFromScroll() throws Exception { |
| Context context = getInstrumentation().getTargetContext(); |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| final int scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); |
| final int scrollDelta = 5; |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| context, mockDelegate, |
| new MockZoomManager(context, null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y + scaledTouchSlop + scrollDelta, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| |
| assertEquals("We should have started scrolling", |
| ContentViewGestureHandler.GESTURE_SCROLL_BY, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| |
| GestureRecordingMotionEventDelegate.GestureEvent gestureEvent = |
| mockDelegate.getMostRecentGestureEvent(); |
| assertNotNull(gestureEvent); |
| Bundle extraParams = gestureEvent.getExtraParams(); |
| assertEquals(0, extraParams.getInt(ContentViewGestureHandler.DISTANCE_X)); |
| assertEquals(-scrollDelta, extraParams.getInt(ContentViewGestureHandler.DISTANCE_Y)); |
| } |
| |
| static private void sendLastScrollByEvent(ContentViewGestureHandler handler) { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(handler.onTouchEvent(event)); |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X, FAKE_COORD_Y + 30, 0); |
| assertTrue(handler.onTouchEvent(event)); |
| } |
| |
| static private void sendLastPinchEvent(ContentViewGestureHandler handler) { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| handler.pinchBegin(downTime, FAKE_COORD_X, FAKE_COORD_Y); |
| handler.pinchBy(eventTime + 10, FAKE_COORD_X, FAKE_COORD_Y, 2); |
| } |
| |
| /** |
| * Verify that certain gesture events are sent with the "last for this vsync" flag set. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testFinalInputEventsForVSyncInterval() throws Exception { |
| Context context = getInstrumentation().getTargetContext(); |
| final boolean inputEventsDeliveredAtVSync = |
| Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| context, mockDelegate, |
| new MockZoomManager(context, null), |
| inputEventsDeliveredAtVSync ? ContentViewCore.INPUT_EVENTS_DELIVERED_AT_VSYNC : |
| ContentViewCore.INPUT_EVENTS_DELIVERED_IMMEDIATELY); |
| |
| sendLastScrollByEvent(mGestureHandler); |
| assertEquals("We should have started scrolling", |
| ContentViewGestureHandler.GESTURE_SCROLL_BY, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| if (inputEventsDeliveredAtVSync) { |
| assertEquals("Gesture should be last for vsync", |
| true, |
| mockDelegate.mostRecentGestureEventForLastForVSync()); |
| } else { |
| assertEquals("Gesture should not be last for vsync", |
| false, |
| mockDelegate.mostRecentGestureEventForLastForVSync()); |
| } |
| |
| sendLastPinchEvent(mGestureHandler); |
| assertEquals("We should have started pinch-zooming", |
| ContentViewGestureHandler.GESTURE_PINCH_BY, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| if (inputEventsDeliveredAtVSync) { |
| assertEquals("Gesture should be last for vsync", |
| true, |
| mockDelegate.mostRecentGestureEventForLastForVSync()); |
| } else { |
| assertEquals("Gesture should not be last for vsync", |
| false, |
| mockDelegate.mostRecentGestureEventForLastForVSync()); |
| } |
| } |
| |
| /** |
| * Verify that no gesture is set with "last for this vsync" flag if vsync is not enabled for |
| * gesture handler |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testFinalInputEventsForVSyncIntervalWithVsyncDisabled() throws Exception { |
| Context context = getInstrumentation().getTargetContext(); |
| final boolean inputEventsDeliveredAtVSync = |
| Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; |
| |
| // Nothing to test on OS version that does not have input batched before vsync. |
| if (!inputEventsDeliveredAtVSync) { |
| return; |
| } |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| context, mockDelegate, |
| new MockZoomManager(context, null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_IMMEDIATELY); |
| |
| sendLastScrollByEvent(mGestureHandler); |
| assertEquals("Gesture should not be last for vsync", |
| false, |
| mockDelegate.mostRecentGestureEventForLastForVSync()); |
| |
| sendLastPinchEvent(mGestureHandler); |
| assertEquals("Gesture should not be last for vsync", |
| false, |
| mockDelegate.mostRecentGestureEventForLastForVSync()); |
| } |
| |
| /** |
| * Verify that a DOWN followed shortly by an UP will trigger |
| * a GESTURE_SINGLE_TAP_UNCONFIRMED event immediately. |
| * |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testGestureEventsSingleTapUnconfirmed() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| GestureRecordingMotionEventDelegate mockDelegate = |
| new GestureRecordingMotionEventDelegate(); |
| mGestureHandler = new ContentViewGestureHandler( |
| getInstrumentation().getTargetContext(), mockDelegate, |
| new MockZoomManager(getInstrumentation().getTargetContext(), null), |
| ContentViewCore.INPUT_EVENTS_DELIVERED_IMMEDIATELY); |
| |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertNull(mockDelegate.getMostRecentGestureEvent()); |
| |
| event = motionEvent(MotionEvent.ACTION_UP, downTime, eventTime + 10); |
| assertFalse(mGestureHandler.onTouchEvent(event)); |
| assertEquals("A GESTURE_SINGLE_TAP_UNCONFIRMED gesture should have been sent", |
| ContentViewGestureHandler.GESTURE_SINGLE_TAP_UNCONFIRMED, |
| mockDelegate.mMostRecentGestureEvent.mType); |
| |
| assertTrue("Should not have confirmed a single tap yet", |
| mMockListener.mLastSingleTap == null); |
| } |
| |
| /** |
| * Verify that touch move events are properly coalesced. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testTouchMoveCoalescing() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| MotionEvent event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertFalse("Should not have a pending LONG_PRESS", mLongPressDetector.hasPendingMessage()); |
| assertEquals("Initial move events should offered to javascript and added to the queue", |
| 1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertEquals(TouchPoint.TOUCH_EVENT_TYPE_MOVE, mMockMotionEventDelegate.mLastTouchAction); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("Move events already sent to javascript should not be coalesced", |
| 2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 15, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 15, FAKE_COORD_Y * 15, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("Similar pending move events should be coalesced", |
| 2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| PointerProperties pp1 = new PointerProperties(); |
| pp1.id = 0; |
| pp1.toolType = MotionEvent.TOOL_TYPE_FINGER; |
| PointerProperties pp2 = new PointerProperties(); |
| pp2.id = 1; |
| pp2.toolType = MotionEvent.TOOL_TYPE_FINGER; |
| PointerProperties[] properties = new PointerProperties[] { pp1, pp2 }; |
| |
| PointerCoords pc1 = new PointerCoords(); |
| pc1.x = FAKE_COORD_X * 10; |
| pc1.y = FAKE_COORD_Y * 10; |
| pc1.pressure = 1; |
| pc1.size = 1; |
| PointerCoords pc2 = new PointerCoords(); |
| pc2.x = FAKE_COORD_X * 15; |
| pc2.y = FAKE_COORD_Y * 15; |
| pc2.pressure = 1; |
| pc2.size = 1; |
| PointerCoords[] coords = new PointerCoords[] { pc1, pc2 }; |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 20, MotionEvent.ACTION_MOVE, |
| 2, properties, coords, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("Move events with different pointer counts should not be coalesced", |
| 3, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = MotionEvent.obtain( |
| downTime, eventTime + 25, MotionEvent.ACTION_MOVE, |
| 2, properties, coords, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("Move events with similar pointer counts should be coalesced", |
| 3, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals("Move events should not be coalesced with other events", |
| 4, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| } |
| |
| /** |
| * Verify that synchronous confirmTouchEvent() calls made from the MotionEventDelegate behave |
| * properly. |
| * @throws Exception |
| */ |
| @SmallTest |
| @Feature({"Gestures"}) |
| public void testSynchronousConfirmTouchEvent() throws Exception { |
| final long downTime = SystemClock.uptimeMillis(); |
| final long eventTime = SystemClock.uptimeMillis(); |
| |
| mGestureHandler.hasTouchEventHandlers(true); |
| |
| mMockMotionEventDelegate.disableSynchronousConfirmTouchEvent(); |
| |
| // Queue an asynchronously handled event; this should schedule a touch timeout. |
| MotionEvent event = motionEvent(MotionEvent.ACTION_DOWN, downTime, downTime); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(1, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertTrue(mGestureHandler.hasScheduledTouchTimeoutEventForTesting()); |
| |
| // Queue another event; this will remain in the queue until the first event is confirmed. |
| event = MotionEvent.obtain( |
| downTime, eventTime + 5, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 10, FAKE_COORD_Y * 10, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(2, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| |
| // Enable synchronous event confirmation upon dispatch. |
| mMockMotionEventDelegate.enableSynchronousConfirmTouchEvent( |
| mGestureHandler, ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| |
| // Confirm the original event; this should dispatch the second event and confirm it |
| // synchronously, without scheduling a touch timeout. |
| mGestureHandler.confirmTouchEvent( |
| ContentViewGestureHandler.INPUT_EVENT_ACK_STATE_CONSUMED); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertFalse(mGestureHandler.hasScheduledTouchTimeoutEventForTesting()); |
| assertEquals(TouchPoint.TOUCH_EVENT_TYPE_MOVE, mMockMotionEventDelegate.mLastTouchAction); |
| |
| // Adding events to any empty queue will trigger synchronous dispatch and confirmation. |
| event = MotionEvent.obtain( |
| downTime, eventTime + 10, MotionEvent.ACTION_MOVE, |
| FAKE_COORD_X * 5, FAKE_COORD_Y * 5, 0); |
| assertTrue(mGestureHandler.onTouchEvent(event)); |
| assertEquals(0, mGestureHandler.getNumberOfPendingMotionEventsForTesting()); |
| assertFalse(mGestureHandler.hasScheduledTouchTimeoutEventForTesting()); |
| } |
| } |