Index: content/public/android/java/src/org/chromium/content/browser/GestureDetectorProxy.java |
diff --git a/content/public/android/java/src/org/chromium/content/browser/GestureDetectorProxy.java b/content/public/android/java/src/org/chromium/content/browser/GestureDetectorProxy.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3f528ec71a91177324e1eb4e8c0c727a632f333e |
--- /dev/null |
+++ b/content/public/android/java/src/org/chromium/content/browser/GestureDetectorProxy.java |
@@ -0,0 +1,179 @@ |
+// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
bulach
2012/07/16 13:22:44
nit: 2012
|
+// 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.Handler; |
+import android.os.Message; |
+import android.view.GestureDetector; |
+import android.view.ViewConfiguration; |
+import android.view.GestureDetector.OnGestureListener; |
+import android.view.MotionEvent; |
+import android.util.Pair; |
+ |
+import java.util.Iterator; |
+ |
+/** |
+ * This class sits in between the system GestureDetector and ContentView in order |
+ * to control long press timers. |
+ * |
+ * For instance, we may receive a DOWN then UP, so we may need to cancel the |
+ * timer before the UP completes its roundtrip from WebKit. |
+ */ |
+class GestureDetectorProxy { |
+ private GestureDetector mGestureDetector; |
+ private OnGestureListener mListener; |
+ private MotionEvent mCurrentDownEvent; |
+ private Handler mHandler; |
+ private int mTouchSlopSquare; |
+ private boolean mInLongPress; |
+ |
+ // The following are used when touch events are offered to native, and not for |
+ // anything relating to the GestureDetector. |
+ // True iff a touch_move has exceeded the touch slop distance. |
+ private boolean mMoveConfirmed; |
+ // Coordinates of the start of a touch event (i.e. the touch_down). |
+ private int mTouchInitialX; |
+ private int mTouchInitialY; |
+ |
+ private static final int LONG_PRESS = 2; |
+ |
+ private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); |
+ private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); |
+ |
+ GestureDetectorProxy(Context context, GestureDetector gestureDetector, |
+ OnGestureListener listener) { |
+ mListener = listener; |
+ mGestureDetector = gestureDetector; |
+ mGestureDetector.setIsLongpressEnabled(false); |
+ mHandler = new LongPressHandler(); |
+ final ViewConfiguration configuration = ViewConfiguration.get(context); |
+ int touchSlop = configuration.getScaledTouchSlop(); |
+ mTouchSlopSquare = touchSlop * touchSlop; |
+ } |
+ |
+ private class LongPressHandler extends Handler { |
+ LongPressHandler() { |
+ super(); |
+ } |
+ |
+ @Override |
+ public void handleMessage(Message msg) { |
+ switch (msg.what) { |
+ case LONG_PRESS: |
+ dispatchLongPress(); |
+ break; |
+ default: |
+ throw new RuntimeException("Unknown message " + msg); //never |
+ } |
+ } |
+ } |
+ |
+ // Delegates to the system GestureDetector, and initiates a LONG_PRESS timer |
+ // if needed. |
+ boolean onTouchEvent(MotionEvent ev) { |
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) { |
+ if (mCurrentDownEvent != null) { |
+ mCurrentDownEvent.recycle(); |
+ } |
+ mCurrentDownEvent = MotionEvent.obtain(ev); |
+ mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() |
+ + TAP_TIMEOUT + LONGPRESS_TIMEOUT); |
+ mInLongPress = false; |
+ return mGestureDetector.onTouchEvent(ev); |
+ } |
+ return mGestureDetector.onTouchEvent(ev); |
+ } |
+ |
+ // True only if it's the start of a new stream (ACTION_DOWN), or a |
+ // continuation of the current stream. |
+ boolean canHandle(MotionEvent ev) { |
+ return ev.getAction() == MotionEvent.ACTION_DOWN || |
+ (mCurrentDownEvent != null && |
+ mCurrentDownEvent.getDownTime() == ev.getDownTime()); |
+ } |
+ |
+ // Cancel LONG_PRESS timers. |
+ void cancelLongPressIfNeeded(MotionEvent ev) { |
+ if (!hasPendingMessage() || |
+ mCurrentDownEvent == null || ev.getDownTime() != mCurrentDownEvent.getDownTime()) { |
+ return; |
+ } |
+ final int action = ev.getAction(); |
+ final float y = ev.getY(); |
+ final float x = ev.getX(); |
+ switch (action & MotionEvent.ACTION_MASK) { |
+ case MotionEvent.ACTION_MOVE: |
+ final int deltaX = (int) (x - mCurrentDownEvent.getX()); |
+ final int deltaY = (int) (y - mCurrentDownEvent.getY()); |
+ int distance = (deltaX * deltaX) + (deltaY * deltaY); |
+ if (distance > mTouchSlopSquare) { |
+ mInLongPress = false; |
+ mHandler.removeMessages(LONG_PRESS); |
+ } |
+ break; |
+ case MotionEvent.ACTION_UP: |
+ if (mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT > |
+ ev.getEventTime()) { |
+ mInLongPress = false; |
+ mHandler.removeMessages(LONG_PRESS); |
+ } |
+ break; |
+ } |
+ } |
+ |
+ // Given a stream of pending events, cancel the LONG_PRESS timer if appropriate. |
+ void cancelLongPressIfNeeded(Iterator<Pair<MotionEvent, Integer>> pendingEvents) { |
+ if (mCurrentDownEvent == null) |
+ return; |
+ long currentDownTime = mCurrentDownEvent.getDownTime(); |
+ while (pendingEvents.hasNext()) { |
+ MotionEvent pending = pendingEvents.next().first; |
+ if (pending.getDownTime() != currentDownTime) { |
+ break; |
+ } |
+ cancelLongPressIfNeeded(pending); |
+ } |
+ } |
+ |
+ void cancelLongPress() { |
+ if (mHandler.hasMessages(LONG_PRESS)) { |
+ mHandler.removeMessages(LONG_PRESS); |
+ } |
+ } |
+ |
+ // Used this to check if a onSingleTapUp is part of a long press event. |
+ boolean isInLongPress() { |
+ return mInLongPress; |
+ } |
+ |
+ private void dispatchLongPress() { |
+ mInLongPress = true; |
+ mListener.onLongPress(mCurrentDownEvent); |
+ } |
+ |
+ protected boolean hasPendingMessage() { |
+ return mHandler.hasMessages(LONG_PRESS); |
+ } |
+ |
+ void onOfferTouchEventToNative(MotionEvent event) { |
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { |
+ mMoveConfirmed = false; |
+ mTouchInitialX = Math.round(event.getX()); |
+ mTouchInitialY = Math.round(event.getY()); |
+ } |
+ } |
+ |
+ boolean confirmOfferMoveEventToNative(MotionEvent event) { |
+ if (!mMoveConfirmed) { |
+ int deltaX = Math.round(event.getX()) - mTouchInitialX; |
+ int deltaY = Math.round(event.getY()) - mTouchInitialY; |
+ if (deltaX * deltaX + deltaY * deltaY >= mTouchSlopSquare) { |
+ mMoveConfirmed = true; |
+ } |
+ } |
+ return mMoveConfirmed; |
+ } |
+} |