| Index: content/public/android/java/src/org/chromium/content/browser/LongPressDetector.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/LongPressDetector.java b/content/public/android/java/src/org/chromium/content/browser/LongPressDetector.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..de6ac03c61c2e0d2208b6c57ed33a34fd62d2057
|
| --- /dev/null
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/LongPressDetector.java
|
| @@ -0,0 +1,179 @@
|
| +// Copyright (c) 2010 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.Handler;
|
| +import android.os.Message;
|
| +import android.view.ViewConfiguration;
|
| +import android.view.MotionEvent;
|
| +import android.util.Pair;
|
| +
|
| +import java.util.Iterator;
|
| +
|
| +/**
|
| + * This class controls long press timers and is owned by a ContentViewGestureHandler.
|
| + *
|
| + * 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 LongPressDetector {
|
| + private MotionEvent mCurrentDownEvent;
|
| + private LongPressDelegate mLongPressDelegate;
|
| + private Handler mLongPressHandler;
|
| + 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();
|
| +
|
| + LongPressDetector(Context context, LongPressDelegate delegate) {
|
| + mLongPressDelegate = delegate;
|
| + mLongPressHandler = 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
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * This is an interface to execute the LongPress when it receives the onLongPress message.
|
| + */
|
| + interface LongPressDelegate {
|
| + public void onLongPress(MotionEvent event);
|
| + }
|
| +
|
| + /**
|
| + * Initiates a LONG_PRESS gesture timer if needed.
|
| + */
|
| + void startLongPressTimerIfNeeded(MotionEvent ev) {
|
| + if (ev.getAction() != MotionEvent.ACTION_DOWN) return;
|
| +
|
| + if (mCurrentDownEvent != null) mCurrentDownEvent.recycle();
|
| +
|
| + mCurrentDownEvent = MotionEvent.obtain(ev);
|
| + mLongPressHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
|
| + + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
|
| + mInLongPress = false;
|
| + }
|
| +
|
| + // 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;
|
| + mLongPressHandler.removeMessages(LONG_PRESS);
|
| + }
|
| + break;
|
| + case MotionEvent.ACTION_UP:
|
| + if (mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT >
|
| + ev.getEventTime()) {
|
| + mInLongPress = false;
|
| + mLongPressHandler.removeMessages(LONG_PRESS);
|
| + }
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| + }
|
| +
|
| + // Given a stream of pending events, cancel the LONG_PRESS timer if appropriate.
|
| + void cancelLongPressIfNeeded(Iterator<Pair<MotionEvent, Boolean>> 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 (mLongPressHandler.hasMessages(LONG_PRESS)) {
|
| + mLongPressHandler.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;
|
| + mLongPressDelegate.onLongPress(mCurrentDownEvent);
|
| + }
|
| +
|
| + protected boolean hasPendingMessage() {
|
| + return mLongPressHandler.hasMessages(LONG_PRESS);
|
| + }
|
| +
|
| + void onOfferTouchEventToJavaScript(MotionEvent event) {
|
| + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
| + mMoveConfirmed = false;
|
| + mTouchInitialX = Math.round(event.getX());
|
| + mTouchInitialY = Math.round(event.getY());
|
| + }
|
| + }
|
| +
|
| + boolean confirmOfferMoveEventToJavaScript(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;
|
| + }
|
| +}
|
|
|