summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwangxianzhu@chromium.org <wangxianzhu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-07 21:46:09 +0000
committerwangxianzhu@chromium.org <wangxianzhu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-07 21:46:09 +0000
commitaf96692733281c4c98ae76c1173e5ce428cfa750 (patch)
tree9967f710d2f1b27c63b437c879776eb8a7c46a60
parent0e455397dcd9b3de1b6a27c3ee02f08c42986500 (diff)
downloadchromium_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
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java89
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/TouchPoint.java11
-rw-r--r--content/public/android/javatests/src/org/chromium/content/browser/ContentViewGestureHandlerTest.java71
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.