| Index: content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java
|
| 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
|
| deleted file mode 100644
|
| index 92573f4550742e59f064ee6526dd86287a782a46..0000000000000000000000000000000000000000
|
| --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewGestureHandler.java
|
| +++ /dev/null
|
| @@ -1,993 +0,0 @@
|
| -// Copyright 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.annotation.TargetApi;
|
| -import android.content.Context;
|
| -import android.os.Build;
|
| -import android.os.Bundle;
|
| -import android.os.SystemClock;
|
| -import android.util.Log;
|
| -import android.view.InputDevice;
|
| -import android.view.MotionEvent;
|
| -import android.view.ScaleGestureDetector;
|
| -import android.view.ViewConfiguration;
|
| -
|
| -import org.chromium.base.CommandLine;
|
| -import org.chromium.base.TraceEvent;
|
| -import org.chromium.content.browser.third_party.GestureDetector;
|
| -import org.chromium.content.browser.third_party.GestureDetector.OnDoubleTapListener;
|
| -import org.chromium.content.browser.third_party.GestureDetector.OnGestureListener;
|
| -import org.chromium.content.common.ContentSwitches;
|
| -
|
| -/**
|
| - * This class handles all MotionEvent handling done in ContentViewCore including the gesture
|
| - * recognition. It sends all related native calls through the interface MotionEventDelegate.
|
| - */
|
| -class ContentViewGestureHandler {
|
| -
|
| - private static final String TAG = "ContentViewGestureHandler";
|
| - /**
|
| - * Used for FLING_START x velocity
|
| - */
|
| - static final String VELOCITY_X = "Velocity X";
|
| - /**
|
| - * Used for FLING_START y velocity
|
| - */
|
| - static final String VELOCITY_Y = "Velocity Y";
|
| - /**
|
| - * Used for SCROLL_BY x distance (scroll offset of update)
|
| - */
|
| - static final String DISTANCE_X = "Distance X";
|
| - /**
|
| - * Used for SCROLL_BY y distance (scroll offset of update)
|
| - */
|
| - static final String DISTANCE_Y = "Distance Y";
|
| - /**
|
| - * Used for SCROLL_START delta X hint (movement triggering scroll)
|
| - */
|
| - static final String DELTA_HINT_X = "Delta Hint X";
|
| - /**
|
| - * Used for SCROLL_START delta Y hint (movement triggering scroll)
|
| - */
|
| - static final String DELTA_HINT_Y = "Delta Hint Y";
|
| - /**
|
| - * Used in SINGLE_TAP_CONFIRMED to check whether ShowPress has been called before.
|
| - */
|
| - static final String SHOW_PRESS = "ShowPress";
|
| - /**
|
| - * Used for PINCH_BY delta
|
| - */
|
| - static final String DELTA = "Delta";
|
| -
|
| - private final Bundle mExtraParamBundleSingleTap;
|
| - private final Bundle mExtraParamBundleFling;
|
| - private final Bundle mExtraParamBundleScroll;
|
| - private final Bundle mExtraParamBundleScrollStart;
|
| - private final Bundle mExtraParamBundleDoubleTapDragZoom;
|
| - private final Bundle mExtraParamBundlePinchBy;
|
| - private GestureDetector mGestureDetector;
|
| - private OnGestureListener mListener;
|
| - private OnDoubleTapListener mDoubleTapListener;
|
| - private ScaleGestureDetector mMultiTouchDetector;
|
| - private ScaleGestureListener mMultiTouchListener;
|
| - private MotionEvent mCurrentDownEvent;
|
| - private final MotionEventDelegate mMotionEventDelegate;
|
| -
|
| - // Remember whether onShowPress() is called. If it is not, in onSingleTapConfirmed()
|
| - // we will first show the press state, then trigger the click.
|
| - private boolean mShowPressIsCalled;
|
| -
|
| - // Whether a sent TAP_DOWN event has yet to be accompanied by a corresponding
|
| - // SINGLE_TAP_UP, SINGLE_TAP_CONFIRMED, TAP_CANCEL or DOUBLE_TAP.
|
| - private boolean mNeedsTapEndingEvent;
|
| -
|
| - // This flag is used for ignoring the remaining touch events, i.e., All the events until the
|
| - // next ACTION_DOWN. This is automatically set to false on the next ACTION_DOWN.
|
| - private boolean mIgnoreRemainingTouchEvents;
|
| -
|
| - // TODO(klobag): this is to avoid a bug in GestureDetector. With multi-touch,
|
| - // mAlwaysInTapRegion is not reset. So when the last finger is up, onSingleTapUp()
|
| - // will be mistakenly fired.
|
| - private boolean mIgnoreSingleTap;
|
| -
|
| - // True from right before we send the first scroll event until the last finger is raised.
|
| - private boolean mTouchScrolling;
|
| -
|
| - // Used to remove the touch slop from the initial scroll event in a scroll gesture.
|
| - private boolean mSeenFirstScrollEvent;
|
| -
|
| - private boolean mPinchInProgress = false;
|
| -
|
| - private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
|
| -
|
| - // Indicate current double tap mode state.
|
| - private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
|
| -
|
| - // x, y coordinates for an Anchor on double tap drag zoom.
|
| - private float mDoubleTapDragZoomAnchorX;
|
| - private float mDoubleTapDragZoomAnchorY;
|
| -
|
| - // On double tap this will store the y coordinates of the touch.
|
| - private float mDoubleTapY;
|
| -
|
| - // Double tap drag zoom sensitive (speed).
|
| - private static final float DOUBLE_TAP_DRAG_ZOOM_SPEED = 0.005f;
|
| -
|
| - // Used to track the last rawX/Y coordinates for moves. This gives absolute scroll distance.
|
| - // Useful for full screen tracking.
|
| - private float mLastRawX = 0;
|
| - private float mLastRawY = 0;
|
| -
|
| - // Cache of square of the scaled touch slop so we don't have to calculate it on every touch.
|
| - private int mScaledTouchSlopSquare;
|
| -
|
| - // Object that keeps track of and updates scroll snapping behavior.
|
| - private final SnapScrollController mSnapScrollController;
|
| -
|
| - // Used to track the accumulated scroll error over time. This is used to remove the
|
| - // rounding error we introduced by passing integers to webkit.
|
| - private float mAccumulatedScrollErrorX = 0;
|
| - private float mAccumulatedScrollErrorY = 0;
|
| -
|
| - // The page's viewport and scale sometimes allow us to disable double tap gesture detection,
|
| - // according to the logic in ContentViewCore.onRenderCoordinatesUpdated().
|
| - private boolean mShouldDisableDoubleTap;
|
| -
|
| - // Keeps track of the last long press event, if we end up opening a context menu, we would need
|
| - // to potentially use the event to send TAP_CANCEL to remove ::active styling
|
| - private MotionEvent mLastLongPressEvent;
|
| -
|
| - // Whether the click delay should always be disabled by sending clicks for double tap gestures.
|
| - private final boolean mDisableClickDelay;
|
| -
|
| - private final float mPxToDp;
|
| -
|
| - static final int DOUBLE_TAP_MODE_NONE = 0;
|
| - static final int DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS = 1;
|
| - static final int DOUBLE_TAP_MODE_DRAG_ZOOM = 2;
|
| - static final int DOUBLE_TAP_MODE_DISABLED = 3;
|
| -
|
| - /**
|
| - * This is an interface to handle MotionEvent related communication with the native side also
|
| - * access some ContentView specific parameters.
|
| - */
|
| - public interface MotionEventDelegate {
|
| - /**
|
| - * Signal the start of gesture detection for the provided {@link MotionEvent}.
|
| - * @param event The {@link MotionEvent} being fed to the gesture detectors.
|
| - */
|
| - public void onTouchEventHandlingBegin(MotionEvent event);
|
| -
|
| - /**
|
| - * Signal that all gestures for the current {@link MotionEvent} have been dispatched.
|
| - */
|
| - public void onTouchEventHandlingEnd();
|
| -
|
| - /**
|
| - * Forward a generated event to the client. This will normally be wrapped by
|
| - * calls to {@link #onTouchEventHandlingBegin(MotionEvent)} and
|
| - * {@link #onTouchEventHandlingEnd()}, unless the gesture is generated from
|
| - * a touch timeout, e.g., LONG_PRESS.
|
| - * @param type The type of the gesture event.
|
| - * @param timeMs The time the gesture event occurred at.
|
| - * @param x The x location for the gesture event.
|
| - * @param y The y location for the gesture event.
|
| - * @param extraParams A bundle that holds specific extra parameters for certain gestures.
|
| - * This is read-only and should not be modified in this function.
|
| - * Refer to gesture type definition for more information.
|
| - * @return Whether the gesture was forwarded successfully.
|
| - */
|
| - boolean onGestureEventCreated(int type, long timeMs, int x, int y, Bundle extraParams);
|
| - }
|
| -
|
| - ContentViewGestureHandler(Context context, MotionEventDelegate delegate) {
|
| - mExtraParamBundleSingleTap = new Bundle();
|
| - mExtraParamBundleFling = new Bundle();
|
| - mExtraParamBundleScroll = new Bundle();
|
| - mExtraParamBundleScrollStart = new Bundle();
|
| - mExtraParamBundleDoubleTapDragZoom = new Bundle();
|
| - mExtraParamBundlePinchBy = new Bundle();
|
| -
|
| - mMotionEventDelegate = delegate;
|
| - mSnapScrollController = new SnapScrollController(context);
|
| - mPxToDp = 1.0f / context.getResources().getDisplayMetrics().density;
|
| -
|
| - mDisableClickDelay = CommandLine.isInitialized() &&
|
| - CommandLine.getInstance().hasSwitch(ContentSwitches.DISABLE_CLICK_DELAY);
|
| -
|
| - initGestureDetectors(context);
|
| - }
|
| -
|
| - private void initGestureDetectors(final Context context) {
|
| - final int scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
| - mScaledTouchSlopSquare = scaledTouchSlop * scaledTouchSlop;
|
| - try {
|
| - TraceEvent.begin();
|
| - GestureDetector.SimpleOnGestureListener listener =
|
| - new GestureDetector.SimpleOnGestureListener() {
|
| - @Override
|
| - public boolean onDown(MotionEvent e) {
|
| - mShowPressIsCalled = false;
|
| - mIgnoreSingleTap = false;
|
| - mTouchScrolling = false;
|
| - mSeenFirstScrollEvent = false;
|
| - mLastRawX = e.getRawX();
|
| - mLastRawY = e.getRawY();
|
| - mAccumulatedScrollErrorX = 0;
|
| - mAccumulatedScrollErrorY = 0;
|
| - mLastLongPressEvent = null;
|
| - mNeedsTapEndingEvent = false;
|
| - if (sendMotionEventAsGesture(GestureEventType.TAP_DOWN, e, null)) {
|
| - mNeedsTapEndingEvent = true;
|
| - }
|
| - // Return true to indicate that we want to handle touch
|
| - return true;
|
| - }
|
| -
|
| - @Override
|
| - public boolean onScroll(MotionEvent e1, MotionEvent e2,
|
| - float rawDistanceX, float rawDistanceY) {
|
| - assert e1.getEventTime() <= e2.getEventTime();
|
| - float distanceX = rawDistanceX;
|
| - float distanceY = rawDistanceY;
|
| - if (!mSeenFirstScrollEvent) {
|
| - // Remove the touch slop region from the first scroll event to avoid a
|
| - // jump.
|
| - mSeenFirstScrollEvent = true;
|
| - double distance = Math.sqrt(
|
| - distanceX * distanceX + distanceY * distanceY);
|
| - double epsilon = 1e-3;
|
| - if (distance > epsilon) {
|
| - double ratio = Math.max(0, distance - scaledTouchSlop) / distance;
|
| - distanceX *= ratio;
|
| - distanceY *= ratio;
|
| - }
|
| - }
|
| - mSnapScrollController.updateSnapScrollMode(distanceX, distanceY);
|
| - if (mSnapScrollController.isSnappingScrolls()) {
|
| - if (mSnapScrollController.isSnapHorizontal()) {
|
| - distanceY = 0;
|
| - } else {
|
| - distanceX = 0;
|
| - }
|
| - }
|
| -
|
| - mLastRawX = e2.getRawX();
|
| - mLastRawY = e2.getRawY();
|
| - if (!mTouchScrolling) {
|
| - sendTapCancelIfNecessary(e1);
|
| - // Note that scroll start hints are in distance traveled, where
|
| - // scroll deltas are in the opposite direction.
|
| - mExtraParamBundleScrollStart.putInt(DELTA_HINT_X, (int) -rawDistanceX);
|
| - mExtraParamBundleScrollStart.putInt(DELTA_HINT_Y, (int) -rawDistanceY);
|
| - assert mExtraParamBundleScrollStart.size() == 2;
|
| - if (sendGesture(GestureEventType.SCROLL_START, e2.getEventTime(),
|
| - (int) e1.getX(), (int) e1.getY(),
|
| - mExtraParamBundleScrollStart)) {
|
| - mTouchScrolling = true;
|
| - }
|
| - }
|
| -
|
| - // distanceX and distanceY is the scrolling offset since last onScroll.
|
| - // Because we are passing integers to webkit, this could introduce
|
| - // rounding errors. The rounding errors will accumulate overtime.
|
| - // To solve this, we should be adding back the rounding errors each time
|
| - // when we calculate the new offset.
|
| - int x = (int) e2.getX();
|
| - int y = (int) e2.getY();
|
| - int dx = (int) (distanceX + mAccumulatedScrollErrorX);
|
| - int dy = (int) (distanceY + mAccumulatedScrollErrorY);
|
| - mAccumulatedScrollErrorX = distanceX + mAccumulatedScrollErrorX - dx;
|
| - mAccumulatedScrollErrorY = distanceY + mAccumulatedScrollErrorY - dy;
|
| -
|
| - mExtraParamBundleScroll.putInt(DISTANCE_X, dx);
|
| - mExtraParamBundleScroll.putInt(DISTANCE_Y, dy);
|
| - assert mExtraParamBundleScroll.size() == 2;
|
| -
|
| - if ((dx | dy) != 0) {
|
| - sendGesture(GestureEventType.SCROLL_BY,
|
| - e2.getEventTime(), x, y, mExtraParamBundleScroll);
|
| - }
|
| -
|
| - return true;
|
| - }
|
| -
|
| - @Override
|
| - public boolean onFling(MotionEvent e1, MotionEvent e2,
|
| - float velocityX, float velocityY) {
|
| - assert e1.getEventTime() <= e2.getEventTime();
|
| - if (mSnapScrollController.isSnappingScrolls()) {
|
| - if (mSnapScrollController.isSnapHorizontal()) {
|
| - velocityY = 0;
|
| - } else {
|
| - velocityX = 0;
|
| - }
|
| - }
|
| -
|
| - fling(e2.getEventTime(), (int) e1.getX(0), (int) e1.getY(0),
|
| - (int) velocityX, (int) velocityY);
|
| - return true;
|
| - }
|
| -
|
| - @Override
|
| - public void onShowPress(MotionEvent e) {
|
| - mShowPressIsCalled = true;
|
| - sendMotionEventAsGesture(GestureEventType.SHOW_PRESS, e, null);
|
| - }
|
| -
|
| - @Override
|
| - public boolean onSingleTapUp(MotionEvent e) {
|
| - if (isDistanceBetweenDownAndUpTooLong(e.getRawX(), e.getRawY())) {
|
| - sendTapCancelIfNecessary(e);
|
| - mIgnoreSingleTap = true;
|
| - return true;
|
| - }
|
| - // This is a hack to address the issue where user hovers
|
| - // over a link for longer than DOUBLE_TAP_TIMEOUT, then
|
| - // onSingleTapConfirmed() is not triggered. But we still
|
| - // want to trigger the tap event at UP. So we override
|
| - // onSingleTapUp() in this case. This assumes singleTapUp
|
| - // gets always called before singleTapConfirmed.
|
| - if (!mIgnoreSingleTap) {
|
| - if (e.getEventTime() - e.getDownTime() > DOUBLE_TAP_TIMEOUT) {
|
| - if (sendTapEndingEventAsGesture(
|
| - GestureEventType.SINGLE_TAP_UP, e, null)) {
|
| - mIgnoreSingleTap = true;
|
| - }
|
| - return true;
|
| - } else if (isDoubleTapDisabled() || mDisableClickDelay) {
|
| - // If double tap has been disabled, there is no need to wait
|
| - // for the double tap timeout.
|
| - return onSingleTapConfirmed(e);
|
| - } else {
|
| - // Notify Blink about this tapUp event anyway,
|
| - // when none of the above conditions applied.
|
| - sendMotionEventAsGesture(
|
| - GestureEventType.SINGLE_TAP_UNCONFIRMED, e, null);
|
| - }
|
| - }
|
| -
|
| - return triggerLongTapIfNeeded(e);
|
| - }
|
| -
|
| - @Override
|
| - public boolean onSingleTapConfirmed(MotionEvent e) {
|
| - // Long taps in the edges of the screen have their events delayed by
|
| - // ContentViewHolder for tab swipe operations. As a consequence of the delay
|
| - // this method might be called after receiving the up event.
|
| - // These corner cases should be ignored.
|
| - if (mIgnoreSingleTap) return true;
|
| -
|
| - mExtraParamBundleSingleTap.putBoolean(SHOW_PRESS, mShowPressIsCalled);
|
| - assert mExtraParamBundleSingleTap.size() == 1;
|
| - if (sendTapEndingEventAsGesture(GestureEventType.SINGLE_TAP_CONFIRMED, e,
|
| - mExtraParamBundleSingleTap)) {
|
| - mIgnoreSingleTap = true;
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - @Override
|
| - public boolean onDoubleTapEvent(MotionEvent e) {
|
| - switch (e.getActionMasked()) {
|
| - case MotionEvent.ACTION_DOWN:
|
| - // Note that this will be called before the corresponding |onDown()|
|
| - // of the same ACTION_DOWN event. Thus, the preceding TAP_DOWN
|
| - // should be cancelled prior to sending a new one (in |onDown()|).
|
| - sendTapCancelIfNecessary(e);
|
| - mDoubleTapDragZoomAnchorX = e.getX();
|
| - mDoubleTapDragZoomAnchorY = e.getY();
|
| - mDoubleTapMode = DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS;
|
| - // If a long-press fires during a double-tap, the GestureDetector
|
| - // will stop feeding MotionEvents to |onDoubleTapEvent()|,
|
| - // preventing double-tap drag zoom. Long press detection will be
|
| - // re-enabled on the next ACTION_DOWN.
|
| - mGestureDetector.setIsLongpressEnabled(false);
|
| - break;
|
| - case MotionEvent.ACTION_MOVE:
|
| - if (mDoubleTapMode
|
| - == DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS) {
|
| - float distanceX = mDoubleTapDragZoomAnchorX - e.getX();
|
| - float distanceY = mDoubleTapDragZoomAnchorY - e.getY();
|
| -
|
| - // Begin double tap drag zoom mode if the move distance is
|
| - // further than the threshold.
|
| - if (isDistanceGreaterThanTouchSlop(distanceX, distanceY)) {
|
| - sendTapCancelIfNecessary(e);
|
| - mExtraParamBundleScrollStart.putInt(DELTA_HINT_X,
|
| - (int) -distanceX);
|
| - mExtraParamBundleScrollStart.putInt(DELTA_HINT_Y,
|
| - (int) -distanceY);
|
| - assert mExtraParamBundleScrollStart.size() == 2;
|
| - sendGesture(GestureEventType.SCROLL_START, e.getEventTime(),
|
| - (int) e.getX(), (int) e.getY(),
|
| - mExtraParamBundleScrollStart);
|
| - pinchBegin(e.getEventTime(),
|
| - Math.round(mDoubleTapDragZoomAnchorX),
|
| - Math.round(mDoubleTapDragZoomAnchorY));
|
| - mDoubleTapMode = DOUBLE_TAP_MODE_DRAG_ZOOM;
|
| - }
|
| - } else if (mDoubleTapMode == DOUBLE_TAP_MODE_DRAG_ZOOM) {
|
| - assert mExtraParamBundleDoubleTapDragZoom.isEmpty();
|
| - sendGesture(GestureEventType.SCROLL_BY, e.getEventTime(),
|
| - (int) e.getX(), (int) e.getY(),
|
| - mExtraParamBundleDoubleTapDragZoom);
|
| -
|
| - float dy = mDoubleTapY - e.getY();
|
| - pinchBy(e.getEventTime(),
|
| - Math.round(mDoubleTapDragZoomAnchorX),
|
| - Math.round(mDoubleTapDragZoomAnchorY),
|
| - (float) Math.pow(dy > 0 ?
|
| - 1.0f - DOUBLE_TAP_DRAG_ZOOM_SPEED :
|
| - 1.0f + DOUBLE_TAP_DRAG_ZOOM_SPEED,
|
| - Math.abs(dy * mPxToDp)));
|
| - }
|
| - break;
|
| - case MotionEvent.ACTION_UP:
|
| - if (mDoubleTapMode != DOUBLE_TAP_MODE_DRAG_ZOOM) {
|
| - // Normal double tap gesture.
|
| - sendTapEndingEventAsGesture(
|
| - GestureEventType.DOUBLE_TAP, e, null);
|
| - }
|
| - endDoubleTapDragIfNecessary(e);
|
| - break;
|
| - case MotionEvent.ACTION_CANCEL:
|
| - sendTapCancelIfNecessary(e);
|
| - endDoubleTapDragIfNecessary(e);
|
| - break;
|
| - default:
|
| - break;
|
| - }
|
| - mDoubleTapY = e.getY();
|
| - return true;
|
| - }
|
| -
|
| - @Override
|
| - public boolean onLongPress(MotionEvent e) {
|
| - assert !isDoubleTapActive();
|
| - if (isScaleGestureDetectionInProgress()) return false;
|
| - setIgnoreSingleTap(true);
|
| - mLastLongPressEvent = e;
|
| - sendMotionEventAsGesture(GestureEventType.LONG_PRESS, e, null);
|
| - // Returning true puts the GestureDetector in "longpress" mode, disabling
|
| - // further scrolling. This is undesirable, as it is quite common for a
|
| - // longpress gesture to fire on content that won't trigger a context menu.
|
| - return false;
|
| - }
|
| -
|
| - /**
|
| - * This method inspects the distance between where the user started touching
|
| - * the surface, and where she released. If the points are too far apart, we
|
| - * should assume that the web page has consumed the scroll-events in-between,
|
| - * and as such, this should not be considered a single-tap.
|
| - *
|
| - * We use the Android frameworks notion of how far a touch can wander before
|
| - * we think the user is scrolling.
|
| - *
|
| - * @param x the new x coordinate
|
| - * @param y the new y coordinate
|
| - * @return true if the distance is too long to be considered a single tap
|
| - */
|
| - private boolean isDistanceBetweenDownAndUpTooLong(float x, float y) {
|
| - return isDistanceGreaterThanTouchSlop(mLastRawX - x, mLastRawY - y);
|
| - }
|
| - };
|
| - mListener = listener;
|
| - mDoubleTapListener = listener;
|
| - mGestureDetector = new GestureDetector(context, listener);
|
| -
|
| - mMultiTouchListener = new ScaleGestureListener();
|
| - mMultiTouchDetector = new ScaleGestureDetector(context, mMultiTouchListener);
|
| - // ScaleGestureDetector's "QuickScale" feature was introduced in KitKat.
|
| - // As ContentViewGestureHandler already implements this feature,
|
| - // explicitly disable it to prevent double-handling of the gesture.
|
| - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
| - disableQuickScale(mMultiTouchDetector);
|
| - }
|
| - } finally {
|
| - TraceEvent.end();
|
| - }
|
| - }
|
| -
|
| - private class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
|
| - // Completely silence scaling events. Used in WebView when zoom support
|
| - // is turned off.
|
| - private boolean mPermanentlyIgnoreDetectorEvents = false;
|
| -
|
| - // Whether any pinch zoom event has been sent to native.
|
| - private boolean mPinchEventSent;
|
| -
|
| - // ScaleGestureDetector previous to 4.2.2 failed to record the touch event time
|
| - // (b/7626515), so we record it manually for synthesizing pinch gestures.
|
| - private long mCurrentEventTime;
|
| -
|
| - void setCurrentEventTime(long currentEventTime) {
|
| - mCurrentEventTime = currentEventTime;
|
| - }
|
| -
|
| - private long getEventTime(ScaleGestureDetector detector) {
|
| - // Workaround for b/7626515, fixed in 4.2.2.
|
| - assert mCurrentEventTime != 0;
|
| - assert detector.getEventTime() == 0 || detector.getEventTime() == mCurrentEventTime;
|
| - return mCurrentEventTime;
|
| - }
|
| -
|
| - boolean getPermanentlyIgnoreDetectorEvents() {
|
| - return mPermanentlyIgnoreDetectorEvents;
|
| - }
|
| -
|
| - void setPermanentlyIgnoreDetectorEvents(boolean value) {
|
| - // Note that returning false from onScaleBegin / onScale makes the
|
| - // gesture detector not to emit further scaling notifications
|
| - // related to this gesture. Thus, if detector events are enabled in
|
| - // the middle of the gesture, we don't need to do anything.
|
| - mPermanentlyIgnoreDetectorEvents = value;
|
| - }
|
| -
|
| - @Override
|
| - public boolean onScaleBegin(ScaleGestureDetector detector) {
|
| - if (mPermanentlyIgnoreDetectorEvents) return false;
|
| - mPinchEventSent = false;
|
| - setIgnoreSingleTap(true);
|
| - return true;
|
| - }
|
| -
|
| - @Override
|
| - public void onScaleEnd(ScaleGestureDetector detector) {
|
| - if (!mPinchEventSent) return;
|
| - pinchEnd(getEventTime(detector));
|
| - mPinchEventSent = false;
|
| - }
|
| -
|
| - @Override
|
| - public boolean onScale(ScaleGestureDetector detector) {
|
| - if (mPermanentlyIgnoreDetectorEvents) return false;
|
| - // It is possible that pinchBegin() was never called when we reach here.
|
| - // This happens when webkit handles the 2nd touch down event. That causes
|
| - // ContentView to ignore the onScaleBegin() call. And if webkit does not
|
| - // handle the touch move events afterwards, we will face a situation
|
| - // that pinchBy() is called without any pinchBegin().
|
| - // To solve this problem, we call pinchBegin() here if it is never called.
|
| - if (!mPinchEventSent) {
|
| - pinchBegin(getEventTime(detector),
|
| - (int) detector.getFocusX(), (int) detector.getFocusY());
|
| - mPinchEventSent = true;
|
| - }
|
| - pinchBy(getEventTime(detector), (int) detector.getFocusX(), (int) detector.getFocusY(),
|
| - detector.getScaleFactor());
|
| - return true;
|
| - }
|
| - };
|
| -
|
| - @TargetApi(Build.VERSION_CODES.KITKAT)
|
| - private static void disableQuickScale(ScaleGestureDetector scaleGestureDetector) {
|
| - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
| - scaleGestureDetector.setQuickScaleEnabled(false);
|
| - }
|
| -
|
| - /**
|
| - * Fling the ContentView from the current position.
|
| - * @param x Fling touch starting position
|
| - * @param y Fling touch starting position
|
| - * @param velocityX Initial velocity of the fling (X) measured in pixels per second.
|
| - * @param velocityY Initial velocity of the fling (Y) measured in pixels per second.
|
| - */
|
| - void fling(long timeMs, int x, int y, int velocityX, int velocityY) {
|
| - if (velocityX == 0 && velocityY == 0) {
|
| - endTouchScrollIfNecessary(timeMs, true);
|
| - return;
|
| - }
|
| -
|
| - if (!mTouchScrolling) {
|
| - // The native side needs a SCROLL_BEGIN before FLING_START
|
| - // to send the fling to the correct target. Send if it has not sent.
|
| - // The distance traveled in one second is a reasonable scroll start hint.
|
| - mExtraParamBundleScrollStart.putInt(DELTA_HINT_X, velocityX);
|
| - mExtraParamBundleScrollStart.putInt(DELTA_HINT_Y, velocityY);
|
| - assert mExtraParamBundleScrollStart.size() == 2;
|
| - sendGesture(GestureEventType.SCROLL_START, timeMs, x, y, mExtraParamBundleScrollStart);
|
| - }
|
| - endTouchScrollIfNecessary(timeMs, false);
|
| -
|
| - mExtraParamBundleFling.putInt(VELOCITY_X, velocityX);
|
| - mExtraParamBundleFling.putInt(VELOCITY_Y, velocityY);
|
| - assert mExtraParamBundleFling.size() == 2;
|
| - sendGesture(GestureEventType.FLING_START, timeMs, x, y, mExtraParamBundleFling);
|
| - }
|
| -
|
| - /**Ï€
|
| - * End DOUBLE_TAP_MODE_DRAG_ZOOM by sending SCROLL_END and PINCH_END events.
|
| - * @param event A hint event that its x, y, and eventTime will be used for the ending events
|
| - * to send. This argument is an optional and can be null.
|
| - */
|
| - private void endDoubleTapDragIfNecessary(MotionEvent event) {
|
| - assert event != null;
|
| - if (!isDoubleTapActive()) return;
|
| - if (mDoubleTapMode == DOUBLE_TAP_MODE_DRAG_ZOOM) {
|
| - pinchEnd(event.getEventTime());
|
| - sendGesture(GestureEventType.SCROLL_END, event.getEventTime(),
|
| - (int) event.getX(), (int) event.getY(), null);
|
| - }
|
| - mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
|
| - updateDoubleTapListener();
|
| - }
|
| -
|
| - /**
|
| - * Reset touch scroll flag and optionally send a SCROLL_END event if necessary.
|
| - * @param timeMs The time in ms for the event initiating this gesture.
|
| - * @param sendScrollEndEvent Whether to send SCROLL_END event.
|
| - */
|
| - private void endTouchScrollIfNecessary(long timeMs, boolean sendScrollEndEvent) {
|
| - if (!mTouchScrolling) return;
|
| - mTouchScrolling = false;
|
| - if (sendScrollEndEvent) {
|
| - sendGesture(GestureEventType.SCROLL_END, timeMs, 0, 0, null);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * @return Whether native is tracking a scroll.
|
| - */
|
| - boolean isNativeScrolling() {
|
| - // TODO(wangxianzhu): Also return true when fling is active once the UI knows exactly when
|
| - // the fling ends.
|
| - return mTouchScrolling;
|
| - }
|
| -
|
| - /**
|
| - * @return Whether native is tracking a pinch (i.e. between sending PINCH_BEGIN and PINCH_END).
|
| - */
|
| - boolean isNativePinching() {
|
| - return mPinchInProgress;
|
| - }
|
| -
|
| - /**
|
| - * Starts a pinch gesture.
|
| - * @param timeMs The time in ms for the event initiating this gesture.
|
| - * @param x The x coordinate for the event initiating this gesture.
|
| - * @param y The x coordinate for the event initiating this gesture.
|
| - */
|
| - private void pinchBegin(long timeMs, int x, int y) {
|
| - sendGesture(GestureEventType.PINCH_BEGIN, timeMs, x, y, null);
|
| - }
|
| -
|
| - /**
|
| - * Pinch by a given percentage.
|
| - * @param timeMs The time in ms for the event initiating this gesture.
|
| - * @param anchorX The x coordinate for the anchor point to be used in pinch.
|
| - * @param anchorY The y coordinate for the anchor point to be used in pinch.
|
| - * @param delta The percentage to pinch by.
|
| - */
|
| - private void pinchBy(long timeMs, int anchorX, int anchorY, float delta) {
|
| - mExtraParamBundlePinchBy.putFloat(DELTA, delta);
|
| - assert mExtraParamBundlePinchBy.size() == 1;
|
| - sendGesture(GestureEventType.PINCH_BY, timeMs, anchorX, anchorY, mExtraParamBundlePinchBy);
|
| - mPinchInProgress = true;
|
| - }
|
| -
|
| - /**
|
| - * End a pinch gesture.
|
| - * @param timeMs The time in ms for the event initiating this gesture.
|
| - */
|
| - private void pinchEnd(long timeMs) {
|
| - sendGesture(GestureEventType.PINCH_END, timeMs, 0, 0, null);
|
| - mPinchInProgress = false;
|
| - }
|
| -
|
| - /**
|
| - * Ignore singleTap gestures.
|
| - */
|
| - void setIgnoreSingleTap(boolean value) {
|
| - mIgnoreSingleTap = value;
|
| - }
|
| -
|
| - /**
|
| - * Cancel the current touch event sequence by sending ACTION_CANCEL and ignore all the
|
| - * subsequent events until the next ACTION_DOWN.
|
| - *
|
| - * One example usecase is stop processing the touch events when showing context popup menu.
|
| - */
|
| - public void setIgnoreRemainingTouchEvents() {
|
| - if (mIgnoreRemainingTouchEvents) return;
|
| -
|
| - MotionEvent me = obtainActionCancelMotionEvent();
|
| - if (mCurrentDownEvent != null) {
|
| - // Only insert a synthetic event if there's an active touch sequence.
|
| - onTouchEvent(me);
|
| - } else {
|
| - // Otherwise, we still want to reset the gesture detector pipeline
|
| - // (e.g., reset double-tap detection state).
|
| - mGestureDetector.onTouchEvent(me);
|
| - processTouchEventForMultiTouch(me);
|
| - }
|
| - me.recycle();
|
| -
|
| - assert mCurrentDownEvent == null;
|
| - mIgnoreRemainingTouchEvents = true;
|
| - }
|
| -
|
| - /**
|
| - * Handle the incoming MotionEvent.
|
| - * @return Whether the event was handled.
|
| - */
|
| - boolean onTouchEvent(MotionEvent event) {
|
| - final int eventAction = event.getActionMasked();
|
| - // Only these actions have any effect on gesture detection. Other
|
| - // actions have no corresponding WebTouchEvent type and may confuse the
|
| - // touch pipline, so we ignore them entirely.
|
| - if (eventAction != MotionEvent.ACTION_DOWN
|
| - && eventAction != MotionEvent.ACTION_UP
|
| - && eventAction != MotionEvent.ACTION_CANCEL
|
| - && eventAction != MotionEvent.ACTION_MOVE
|
| - && eventAction != MotionEvent.ACTION_POINTER_DOWN
|
| - && eventAction != MotionEvent.ACTION_POINTER_UP) {
|
| - return false;
|
| - }
|
| -
|
| - try {
|
| - TraceEvent.begin("onTouchEvent");
|
| -
|
| - if (mIgnoreRemainingTouchEvents) {
|
| - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
| - mIgnoreRemainingTouchEvents = false;
|
| - } else {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - return processTouchEvent(event);
|
| - } finally {
|
| - TraceEvent.end("onTouchEvent");
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Handle content view losing focus -- ensure that any remaining active state is removed.
|
| - */
|
| - void onWindowFocusLost() {
|
| - // TODO(jdduke): Determine if this should behave more like setIgnoreRemainingTouchEvents().
|
| - if (mLastLongPressEvent != null) {
|
| - sendTapCancelIfNecessary(mLastLongPressEvent);
|
| - }
|
| - }
|
| -
|
| - private MotionEvent obtainActionCancelMotionEvent() {
|
| - MotionEvent me = MotionEvent.obtain(
|
| - mCurrentDownEvent != null ?
|
| - mCurrentDownEvent.getDownTime() : SystemClock.uptimeMillis(),
|
| - SystemClock.uptimeMillis(),
|
| - MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
|
| - me.setSource(mCurrentDownEvent != null ?
|
| - mCurrentDownEvent.getSource() : InputDevice.SOURCE_CLASS_POINTER);
|
| - return me;
|
| - }
|
| -
|
| - /**
|
| - * Resets gesture handlers state; called on didStartLoading().
|
| - * Note that this does NOT clear the pending motion events queue;
|
| - * it gets cleared in hasTouchEventHandlers() called from WebKit
|
| - * FrameLoader::transitionToCommitted iff the page ever had touch handlers.
|
| - */
|
| - void resetGestureHandlers() {
|
| - MotionEvent me = obtainActionCancelMotionEvent();
|
| - mGestureDetector.onTouchEvent(me);
|
| - processTouchEventForMultiTouch(me);
|
| - me.recycle();
|
| - }
|
| -
|
| - private boolean processTouchEvent(MotionEvent event) {
|
| - if (!canHandle(event)) return false;
|
| -
|
| - try {
|
| - mMotionEventDelegate.onTouchEventHandlingBegin(event);
|
| -
|
| - final boolean wasTouchScrolling = mTouchScrolling;
|
| -
|
| - mSnapScrollController.setSnapScrollingMode(event, isScaleGestureDetectionInProgress());
|
| -
|
| - if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
|
| - endDoubleTapDragIfNecessary(event);
|
| - } else if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
| - mGestureDetector.setIsLongpressEnabled(true);
|
| - mCurrentDownEvent = MotionEvent.obtain(event);
|
| - }
|
| -
|
| - boolean handled = mGestureDetector.onTouchEvent(event);
|
| - handled |= processTouchEventForMultiTouch(event);
|
| -
|
| - if (event.getAction() == MotionEvent.ACTION_UP
|
| - || event.getAction() == MotionEvent.ACTION_CANCEL) {
|
| - if (event.getAction() == MotionEvent.ACTION_CANCEL) {
|
| - sendTapCancelIfNecessary(event);
|
| - }
|
| -
|
| - // "Last finger raised" could be an end to movement, but it should
|
| - // only terminate scrolling if the event did not cause a fling.
|
| - if (wasTouchScrolling && !handled) {
|
| - endTouchScrollIfNecessary(event.getEventTime(), true);
|
| - }
|
| -
|
| - if (mCurrentDownEvent != null) recycleEvent(mCurrentDownEvent);
|
| - mCurrentDownEvent = null;
|
| - }
|
| - return handled;
|
| - } finally {
|
| - mMotionEventDelegate.onTouchEventHandlingEnd();
|
| - }
|
| - }
|
| -
|
| - private boolean isScaleGestureDetectionInProgress() {
|
| - return !mMultiTouchListener.getPermanentlyIgnoreDetectorEvents()
|
| - && mMultiTouchDetector.isInProgress();
|
| - }
|
| -
|
| - private boolean processTouchEventForMultiTouch(MotionEvent event) {
|
| - // TODO(jdduke): Need to deal with multi-touch transition
|
| - mMultiTouchListener.setCurrentEventTime(event.getEventTime());
|
| - try {
|
| - boolean inGesture = isScaleGestureDetectionInProgress();
|
| - boolean retVal = mMultiTouchDetector.onTouchEvent(event);
|
| - if (!inGesture && (event.getActionMasked() == MotionEvent.ACTION_UP
|
| - || event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
|
| - return false;
|
| - }
|
| - return retVal;
|
| - } catch (Exception e) {
|
| - Log.e(TAG, "ScaleGestureDetector got into a bad state!", e);
|
| - assert false;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - private void recycleEvent(MotionEvent event) {
|
| - event.recycle();
|
| - }
|
| -
|
| - private boolean sendMotionEventAsGesture(
|
| - int type, MotionEvent event, Bundle extraParams) {
|
| - return sendGesture(type, event.getEventTime(),
|
| - (int) event.getX(), (int) event.getY(), extraParams);
|
| - }
|
| -
|
| - private boolean sendGesture(
|
| - int type, long timeMs, int x, int y, Bundle extraParams) {
|
| - assert timeMs != 0;
|
| - // The only valid gestures that can occur after the touch sequence has
|
| - // ended are SHOW_PRESS and SINGLE_TAP_CONFIRMED, potentially triggered
|
| - // after the double-tap delay window times out.
|
| - if (mCurrentDownEvent == null
|
| - && type != GestureEventType.SINGLE_TAP_CONFIRMED
|
| - && type != GestureEventType.SHOW_PRESS) {
|
| - return false;
|
| - }
|
| - return mMotionEventDelegate.onGestureEventCreated(type, timeMs, x, y, extraParams);
|
| - }
|
| -
|
| - private boolean sendTapEndingEventAsGesture(int type, MotionEvent e, Bundle extraParams) {
|
| - if (!sendMotionEventAsGesture(type, e, extraParams)) return false;
|
| - mNeedsTapEndingEvent = false;
|
| - return true;
|
| - }
|
| -
|
| - private void sendTapCancelIfNecessary(MotionEvent e) {
|
| - if (!mNeedsTapEndingEvent) return;
|
| - if (!sendTapEndingEventAsGesture(GestureEventType.TAP_CANCEL, e, null)) return;
|
| - mLastLongPressEvent = null;
|
| - }
|
| -
|
| - /**
|
| - * @return Whether the ContentViewGestureHandler can handle a MotionEvent right now. True only
|
| - * if it's the start of a new stream (ACTION_DOWN), or a continuation of the current stream.
|
| - */
|
| - private boolean canHandle(MotionEvent ev) {
|
| - return ev.getAction() == MotionEvent.ACTION_DOWN ||
|
| - (mCurrentDownEvent != null && mCurrentDownEvent.getDownTime() == ev.getDownTime());
|
| - }
|
| -
|
| - /**
|
| - * @return Whether the event can trigger a LONG_TAP gesture. True when it can and the event
|
| - * will be consumed.
|
| - */
|
| - private boolean triggerLongTapIfNeeded(MotionEvent ev) {
|
| - if (mLastLongPressEvent != null
|
| - && ev.getAction() == MotionEvent.ACTION_UP
|
| - && !isScaleGestureDetectionInProgress()) {
|
| - sendTapCancelIfNecessary(ev);
|
| - sendMotionEventAsGesture(GestureEventType.LONG_TAP, ev, null);
|
| - return true;
|
| - }
|
| - return false;
|
| - }
|
| -
|
| - /**
|
| - * This is for testing only.
|
| - * Sends a show pressed state gesture through mListener. This should always be called after
|
| - * a down event;
|
| - */
|
| - void sendShowPressedStateGestureForTesting() {
|
| - if (mCurrentDownEvent == null) return;
|
| - mListener.onShowPress(mCurrentDownEvent);
|
| - }
|
| -
|
| - /**
|
| - * This is for testing only.
|
| - * @return Whether a sent TapDown event has been accompanied by a tap-ending event.
|
| - */
|
| - boolean needsTapEndingEventForTesting() {
|
| - return mNeedsTapEndingEvent;
|
| - }
|
| -
|
| - /**
|
| - * Update whether multi-touch gestures are supported.
|
| - */
|
| - public void updateMultiTouchSupport(boolean supportsMultiTouchZoom) {
|
| - mMultiTouchListener.setPermanentlyIgnoreDetectorEvents(!supportsMultiTouchZoom);
|
| - }
|
| -
|
| - /**
|
| - * Update whether double-tap gestures are supported. This allows
|
| - * double-tap gesture suppression independent of whether or not the page's
|
| - * viewport and scale would normally prevent double-tap.
|
| - * Note: This should never be called while a double-tap gesture is in progress.
|
| - * @param supportDoubleTap Whether double-tap gestures are supported.
|
| - */
|
| - public void updateDoubleTapSupport(boolean supportDoubleTap) {
|
| - assert !isDoubleTapActive();
|
| - int doubleTapMode = supportDoubleTap ?
|
| - DOUBLE_TAP_MODE_NONE : DOUBLE_TAP_MODE_DISABLED;
|
| - if (mDoubleTapMode == doubleTapMode) return;
|
| - mDoubleTapMode = doubleTapMode;
|
| - updateDoubleTapListener();
|
| - }
|
| -
|
| - /**
|
| - * Update whether double-tap gesture detection should be suppressed due to
|
| - * the viewport or scale of the current page. Suppressing double-tap gesture
|
| - * detection allows for rapid and responsive single-tap gestures.
|
| - * @param shouldDisableDoubleTap Whether double-tap should be suppressed.
|
| - */
|
| - public void updateShouldDisableDoubleTap(boolean shouldDisableDoubleTap) {
|
| - if (mShouldDisableDoubleTap == shouldDisableDoubleTap) return;
|
| - mShouldDisableDoubleTap = shouldDisableDoubleTap;
|
| - updateDoubleTapListener();
|
| - }
|
| -
|
| - /**
|
| - * @return Whether double-tap gesture detection is enabled.
|
| - */
|
| - public boolean isDoubleTapDisabled() {
|
| - return mDoubleTapMode == DOUBLE_TAP_MODE_DISABLED || mShouldDisableDoubleTap;
|
| - }
|
| -
|
| - /**
|
| - * @return Whether the click delay preceding a double tap is disabled.
|
| - */
|
| - public boolean isClickDelayDisabled() {
|
| - return mDisableClickDelay;
|
| - }
|
| -
|
| - /**
|
| - * @return Whether a double tap-gesture is in-progress.
|
| - */
|
| - public boolean isDoubleTapActive() {
|
| - return mDoubleTapMode != DOUBLE_TAP_MODE_DISABLED &&
|
| - mDoubleTapMode != DOUBLE_TAP_MODE_NONE;
|
| - }
|
| -
|
| - private void updateDoubleTapListener() {
|
| - if (isDoubleTapDisabled()) {
|
| - // Defer nulling the DoubleTapListener until the double tap gesture is complete.
|
| - if (isDoubleTapActive()) return;
|
| - mGestureDetector.setOnDoubleTapListener(null);
|
| - } else {
|
| - mGestureDetector.setOnDoubleTapListener(mDoubleTapListener);
|
| - }
|
| - }
|
| -
|
| - private boolean isDistanceGreaterThanTouchSlop(float distanceX, float distanceY) {
|
| - return distanceX * distanceX + distanceY * distanceY > mScaledTouchSlopSquare;
|
| - }
|
| -}
|
|
|