diff options
author | wangxianzhu@chromium.org <wangxianzhu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-07 21:46:09 +0000 |
---|---|---|
committer | wangxianzhu@chromium.org <wangxianzhu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-07 21:46:09 +0000 |
commit | af96692733281c4c98ae76c1173e5ce428cfa750 (patch) | |
tree | 9967f710d2f1b27c63b437c879776eb8a7c46a60 | |
parent | 0e455397dcd9b3de1b6a27c3ee02f08c42986500 (diff) | |
download | chromium_src-af96692733281c4c98ae76c1173e5ce428cfa750.zip chromium_src-af96692733281c4c98ae76c1173e5ce428cfa750.tar.gz chromium_src-af96692733281c4c98ae76c1173e5ce428cfa750.tar.bz2 |
Merge 204575 "Add timeout for touch handler"
> Add timeout for touch handler
>
> This is a short term solution for the scrolling performance issue.
>
> BUG=244740
>
> Review URL: https://chromiumcodereview.appspot.com/15772017
TBR=wangxianzhu@chromium.org
Review URL: https://codereview.chromium.org/16667007
git-svn-id: svn://svn.chromium.org/chrome/branches/1500/src@204940 0039d316-1c4b-4281-b951-d872f2087c98
3 files changed, 169 insertions, 2 deletions
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java index 7ce32da..13919ec 100644 --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java @@ -7,6 +7,7 @@ package org.chromium.content.browser; import android.content.Context; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; @@ -155,6 +156,81 @@ class ContentViewGestureHandler implements LongPressDelegate { static final int EVENT_CONVERTED_TO_CANCEL = 1; static final int EVENT_NOT_FORWARDED = 2; + private class TouchEventTimeoutHandler implements Runnable { + private static final int TOUCH_EVENT_TIMEOUT = 200; + private static final int PENDING_ACK_NONE = 0; + private static final int PENDING_ACK_ORIGINAL_EVENT = 1; + private static final int PENDING_ACK_CANCEL_EVENT = 2; + + private long mEventTime; + private TouchPoint[] mTouchPoints; + private Handler mHandler = new Handler(); + private int mPendingAckState; + + public void start(long eventTime, TouchPoint[] pts) { + assert mTouchPoints == null; + assert mPendingAckState == PENDING_ACK_NONE; + mEventTime = eventTime; + mTouchPoints = pts; + mHandler.postDelayed(this, TOUCH_EVENT_TIMEOUT); + } + + @Override + public void run() { + TraceEvent.begin("TouchEventTimeout"); + while (!mPendingMotionEvents.isEmpty()) { + MotionEvent nextEvent = mPendingMotionEvents.removeFirst(); + processTouchEvent(nextEvent); + nextEvent.recycle(); + } + // We are waiting for 2 ACKs: one for the timed-out event, the other for + // the touchcancel event injected when the timed-out event is ACK'ed. + mPendingAckState = PENDING_ACK_ORIGINAL_EVENT; + TraceEvent.end(); + } + + public boolean hasTimeoutEvent() { + return mPendingAckState != PENDING_ACK_NONE; + } + + /** + * @return Whether the ACK is consumed in this method. + */ + public boolean confirmTouchEvent() { + switch (mPendingAckState) { + case PENDING_ACK_NONE: + // The ACK to the original event is received before timeout. + mHandler.removeCallbacks(this); + mTouchPoints = null; + return false; + case PENDING_ACK_ORIGINAL_EVENT: + // The ACK to the original event is received after timeout. + // Inject a touchcancel event. + mMotionEventDelegate.sendTouchEvent(mEventTime + TOUCH_EVENT_TIMEOUT, + TouchPoint.TOUCH_EVENT_TYPE_CANCEL, mTouchPoints); + mTouchPoints = null; + mPendingAckState = PENDING_ACK_CANCEL_EVENT; + return true; + case PENDING_ACK_CANCEL_EVENT: + // The ACK to the injected touchcancel event is received. + drainAllPendingEventsUntilNextDown(); + mPendingAckState = PENDING_ACK_NONE; + return true; + default: + assert false : "Never reached"; + return false; + } + } + + public void mockTimeout() { + assert !hasTimeoutEvent(); + mHandler.removeCallbacks(this); + run(); + } + } + + private TouchEventTimeoutHandler mTouchEventTimeoutHandler = new TouchEventTimeoutHandler(); + /** * This is an interface to handle MotionEvent related communication with the native side also * access some ContentView specific parameters. @@ -697,6 +773,8 @@ class ContentViewGestureHandler implements LongPressDelegate { } private int sendTouchEventToNative(MotionEvent event) { + if (mTouchEventTimeoutHandler.hasTimeoutEvent()) return EVENT_NOT_FORWARDED; + TouchPoint[] pts = new TouchPoint[event.getPointerCount()]; int type = TouchPoint.createTouchPoints(event, pts); @@ -704,6 +782,7 @@ class ContentViewGestureHandler implements LongPressDelegate { if (!mNativeScrolling && !mPinchInProgress) { mTouchCancelEventSent = false; if (mMotionEventDelegate.sendTouchEvent(event.getEventTime(), type, pts)) { + mTouchEventTimeoutHandler.start(event.getEventTime(), pts); return EVENT_FORWARDED_TO_NATIVE; } } else if (!mTouchCancelEventSent) { @@ -755,10 +834,18 @@ class ContentViewGestureHandler implements LongPressDelegate { } /** + * For testing to simulate a timeout of a touch event handler. + */ + void mockTouchEventTimeout() { + mTouchEventTimeoutHandler.mockTimeout(); + } + + /** * Respond to a MotionEvent being returned from the native side. - * @param handled Whether the MotionEvent was handled on the native side. + * @param ackResult Whether the MotionEvent was consumed on the native side. */ void confirmTouchEvent(int ackResult) { + if (mTouchEventTimeoutHandler.confirmTouchEvent()) return; if (mPendingMotionEvents.isEmpty()) { Log.w(TAG, "confirmTouchEvent with Empty pending list!"); return; diff --git a/content/public/android/java/src/org/chromium/content/browser/TouchPoint.java b/content/public/android/java/src/org/chromium/content/browser/TouchPoint.java index 2e44d9e..65c561a 100644 --- a/content/public/android/java/src/org/chromium/content/browser/TouchPoint.java +++ b/content/public/android/java/src/org/chromium/content/browser/TouchPoint.java @@ -141,4 +141,15 @@ class TouchPoint { TOUCH_POINT_STATE_STATIONARY = touchPointStationary; TOUCH_POINT_STATE_CANCELLED = touchPointCancelled; } + + /** + * Initialize the constants to distinct values if they have not been initialized. + * During pure-Java testing, initializeConstants() may not be called by native code. + * Unit tests should call this method before using the values. + */ + static void initializeConstantsForTesting() { + if (TOUCH_EVENT_TYPE_START == TOUCH_EVENT_TYPE_MOVE) { + initializeConstants(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + } + } } diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ContentViewGestureHandlerTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ContentViewGestureHandlerTest.java index d4a846e..8627a07d 100644 --- a/content/public/android/javatests/src/org/chromium/content/browser/ContentViewGestureHandlerTest.java +++ b/content/public/android/javatests/src/org/chromium/content/browser/ContentViewGestureHandlerTest.java @@ -30,6 +30,7 @@ public class ContentViewGestureHandlerTest extends InstrumentationTestCase { private static final String TAG = ContentViewGestureHandler.class.toString(); private MockListener mMockListener; + private MockMotionEventDelegate mMockMotionEventDelegate; private MockGestureDetector mMockGestureDetector; private ContentViewGestureHandler mGestureHandler; private LongPressDetector mLongPressDetector; @@ -88,8 +89,12 @@ public class ContentViewGestureHandlerTest extends InstrumentationTestCase { } static class MockMotionEventDelegate implements MotionEventDelegate { + public int mLastTouchAction; + public int mLastGestureType; + @Override public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) { + mLastTouchAction = action; return true; } @@ -97,6 +102,7 @@ public class ContentViewGestureHandlerTest extends InstrumentationTestCase { 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; } @@ -137,13 +143,15 @@ public class ContentViewGestureHandlerTest extends InstrumentationTestCase { mMockListener = new MockListener(); mMockGestureDetector = new MockGestureDetector( getInstrumentation().getTargetContext(), mMockListener); + mMockMotionEventDelegate = new MockMotionEventDelegate(); mGestureHandler = new ContentViewGestureHandler( - getInstrumentation().getTargetContext(), new MockMotionEventDelegate(), + getInstrumentation().getTargetContext(), mMockMotionEventDelegate, new MockZoomManager(getInstrumentation().getTargetContext(), null)); mLongPressDetector = new LongPressDetector( getInstrumentation().getTargetContext(), mGestureHandler); mGestureHandler.setTestDependencies( mLongPressDetector, mMockGestureDetector, mMockListener); + TouchPoint.initializeConstantsForTesting(); } /** @@ -232,6 +240,67 @@ public class ContentViewGestureHandlerTest extends InstrumentationTestCase { } /** + * 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. |