diff --git a/src/api-impl/android/view/GestureDetector.java b/src/api-impl/android/view/GestureDetector.java index 023c9b56..09962b60 100644 --- a/src/api-impl/android/view/GestureDetector.java +++ b/src/api-impl/android/view/GestureDetector.java @@ -1,25 +1,826 @@ +/* +* Copyright (C) 2008 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + package android.view; import android.content.Context; +import android.os.Bundle; import android.os.Handler; +import android.os.Message; +/** + * Detects various gestures and events using the supplied {@link MotionEvent}s. +* The {@link OnGestureListener} callback will notify users when a particular +* motion event has occurred. This class should only be used with {@link MotionEvent}s +* reported via touch (don't use for trackball events). +* +* To use this class: +*
+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this + * will only be called after the detector is confident that the user's + * first tap is not followed by a second tap leading to a double-tap + * gesture. + * + * @param e The down motion event of the single-tap. + * @return true if the event is consumed, else false + */ + boolean onSingleTapConfirmed(MotionEvent e); + + /** + * Notified when a double-tap occurs. Triggered on the down event of second tap. + * + * @param e The down motion event of the first tap of the double-tap. + * @return true if the event is consumed, else false + */ + boolean onDoubleTap(MotionEvent e); + + /** + * Notified when an event within a double-tap gesture occurs, including + * the down, move, and up events. + * + * @param e The motion event that occurred during the double-tap gesture. + * @return true if the event is consumed, else false + */ + boolean onDoubleTapEvent(MotionEvent e); + } + + /** + * The listener that is used to notify when a context click occurs. When listening for a + * context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in + * {@link View#onGenericMotionEvent(MotionEvent)}. + */ + public interface OnContextClickListener { + /** + * Notified when a context click occurs. + * + * @param e The motion event that occurred during the context click. + * @return true if the event is consumed, else false + */ + boolean onContextClick(MotionEvent e); + } + + /** + * A convenience class to extend when you only want to listen for a subset + * of all the gestures. This implements all methods in the + * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener} + * but does nothing and return {@code false} for all applicable methods. + */ + public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener, + OnContextClickListener { + + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + public void onLongPress(MotionEvent e) { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + return false; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + return false; + } + + public void onShowPress(MotionEvent e) { + } + + public boolean onDown(MotionEvent e) { + return false; + } + + public boolean onDoubleTap(MotionEvent e) { + return false; + } + + public boolean onDoubleTapEvent(MotionEvent e) { + return false; + } + + public boolean onSingleTapConfirmed(MotionEvent e) { + return false; + } + + public boolean onContextClick(MotionEvent e) { + return false; + } + } + + private int mTouchSlopSquare; + private int mDoubleTapTouchSlopSquare; + private int mDoubleTapSlopSquare; + private float mAmbiguousGestureMultiplier; + private int mMinimumFlingVelocity; + private int mMaximumFlingVelocity; + + private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); + private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); + + // constants for Message.what used by GestureHandler below + private static final int SHOW_PRESS = 1; + private static final int LONG_PRESS = 2; + private static final int TAP = 3; + + private final Handler mHandler; + private final OnGestureListener mListener; + private OnDoubleTapListener mDoubleTapListener; + private OnContextClickListener mContextClickListener; + + private boolean mStillDown; + private boolean mDeferConfirmSingleTap; + private boolean mInLongPress; + private boolean mInContextClick; + private boolean mAlwaysInTapRegion; + private boolean mAlwaysInBiggerTapRegion; + private boolean mIgnoreNextUpEvent; + + private MotionEvent mCurrentDownEvent; + private MotionEvent mCurrentMotionEvent; + private MotionEvent mPreviousUpEvent; + + /** + * True when the user is still touching for the second tap (down, move, and + * up events). Can only be true if there is a double tap listener attached. + */ + private boolean mIsDoubleTapping; + + private float mLastFocusX; + private float mLastFocusY; + private float mDownFocusX; + private float mDownFocusY; + + private boolean mIsLongpressEnabled; + + /** + * Determines speed during touch scrolling + */ + private VelocityTracker mVelocityTracker; + + private class GestureHandler extends Handler { + GestureHandler() { + super(); + } + + GestureHandler(Handler handler) { + super(handler.getLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null) { + if (!mStillDown) { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } else { + mDeferConfirmSingleTap = true; + } + } + break; + + default: + throw new RuntimeException("Unknown message " + msg); //never + } + } + } + + /** + * Creates a GestureDetector with the supplied listener. + * This variant of the constructor should be used from a non-UI thread + * (as it allows specifying the Handler). + * + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use + * + * @throws NullPointerException if {@code listener} is null. + * + * @deprecated Use {@link #GestureDetector(android.content.Context, + * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead. + */ + @Deprecated + public GestureDetector(OnGestureListener listener, Handler handler) { + this(null, listener, handler); + } + + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * @see android.os.Handler#Handler() + * + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * + * @throws NullPointerException if {@code listener} is null. + * + * @deprecated Use {@link #GestureDetector(android.content.Context, + * android.view.GestureDetector.OnGestureListener)} instead. + */ + @Deprecated + public GestureDetector(OnGestureListener listener) { + this(null, listener, null); + } + + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a {@link android.os.Looper} thread. + * @see android.os.Handler#Handler() + * + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} + * @param listener the listener invoked for all the callbacks, this must + * not be null. If the listener implements the {@link OnDoubleTapListener} or + * {@link OnContextClickListener} then it will also be set as the listener for + * these callbacks (for example when using the {@link SimpleOnGestureListener}). + * + * @throws NullPointerException if {@code listener} is null. + */ + // TODO(b/182007470): Use @ConfigurationContext instead + public GestureDetector(Context context, OnGestureListener listener) { + this(context, listener, null); + } + + /** + * Creates a GestureDetector with the supplied listener that runs deferred events on the + * thread associated with the supplied {@link android.os.Handler}. + * @see android.os.Handler#Handler() + * + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} + * @param listener the listener invoked for all the callbacks, this must + * not be null. If the listener implements the {@link OnDoubleTapListener} or + * {@link OnContextClickListener} then it will also be set as the listener for + * these callbacks (for example when using the {@link SimpleOnGestureListener}). + * @param handler the handler to use for running deferred listener events. + * + * @throws NullPointerException if {@code listener} is null. + */ + public GestureDetector(Context context, OnGestureListener listener, Handler handler) { + if (handler != null) { + mHandler = new GestureHandler(handler); + } else { + mHandler = new GestureHandler(); + } + mListener = listener; + if (listener instanceof OnDoubleTapListener) { + setOnDoubleTapListener((OnDoubleTapListener) listener); + } + if (listener instanceof OnContextClickListener) { + setContextClickListener((OnContextClickListener) listener); + } + init(context); } - public static class SimpleOnGestureListener implements OnGestureListener { - + /** + * Creates a GestureDetector with the supplied listener that runs deferred events on the + * thread associated with the supplied {@link android.os.Handler}. + * @see android.os.Handler#Handler() + * + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use for running deferred listener events. + * @param unused currently not used. + * + * @throws NullPointerException if {@code listener} is null. + */ + public GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused) { + this(context, listener, handler); } + + private void init(Context context) { + if (mListener == null) { + throw new NullPointerException("OnGestureListener must not be null"); + } + mIsLongpressEnabled = true; + + // Fallback to support pre-donuts releases + int touchSlop, doubleTapSlop, doubleTapTouchSlop; + /*if (context == null) { + //noinspection deprecation + touchSlop = ViewConfiguration.getTouchSlop(); + doubleTapTouchSlop = touchSlop; // Hack rather than adding a hidden method for this + doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); + //noinspection deprecation + mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); + mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); + mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); + } else*/ { + final ViewConfiguration configuration = ViewConfiguration.get(context); + touchSlop = configuration.getScaledTouchSlop(); + doubleTapTouchSlop = configuration.getScaledTouchSlop(); // configuration.getScaledDoubleTapTouchSlop(); + doubleTapSlop = configuration.getScaledDoubleTapSlop(); + mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); + mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity(); + mAmbiguousGestureMultiplier = 0; // configuration.getScaledAmbiguousGestureMultiplier(); + } + mTouchSlopSquare = touchSlop * touchSlop; + mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop; + mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; + } + + /** + * Sets the listener which will be called for double-tap and related + * gestures. + * + * @param onDoubleTapListener the listener invoked for all the callbacks, or + * null to stop listening for double-tap gestures. + */ + public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) { + mDoubleTapListener = onDoubleTapListener; + } + + /** + * Sets the listener which will be called for context clicks. + * + * @param onContextClickListener the listener invoked for all the callbacks, or null to stop + * listening for context clicks. + */ + public void setContextClickListener(OnContextClickListener onContextClickListener) { + mContextClickListener = onContextClickListener; + } + + /** + * Set whether longpress is enabled, if this is enabled when a user + * presses and holds down you get a longpress event and nothing further. + * If it's disabled the user can press and hold down and then later + * moved their finger and you will get scroll events. By default + * longpress is enabled. + * + * @param isLongpressEnabled whether longpress should be enabled. + */ + public void setIsLongpressEnabled(boolean isLongpressEnabled) { + mIsLongpressEnabled = isLongpressEnabled; + } + + /** + * @return true if longpress is enabled, else false. + */ + public boolean isLongpressEnabled() { + return mIsLongpressEnabled; + } + + /** + * Analyzes the given motion event and if applicable triggers the + * appropriate callbacks on the {@link OnGestureListener} supplied. + * + * @param ev The current motion event. + * @return true if the {@link OnGestureListener} consumed the event, + * else false. + */ + public boolean onTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + + if (mCurrentMotionEvent != null) { + mCurrentMotionEvent.recycle(); + } + mCurrentMotionEvent = MotionEvent.obtain(ev); + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + final boolean pointerUp = + (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP; + final int skipIndex = pointerUp ? ev.getActionIndex() : -1; + final boolean isGeneratedGesture = false; + // (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0; + + // Determine focal point + float sumX = 0, sumY = 0; + final int count = ev.getPointerCount(); + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + sumX += ev.getX(i); + sumY += ev.getY(i); + } + final int div = pointerUp ? count - 1 : count; + final float focusX = sumX / div; + final float focusY = sumY / div; + + boolean handled = false; + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_POINTER_DOWN: + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + // Cancel long press and taps + cancelTaps(); + break; + + case MotionEvent.ACTION_POINTER_UP: + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + + // Check the dot product of current velocities. + // If the pointer that left was opposing another velocity vector, clear. + mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); + final int upIndex = ev.getActionIndex(); + final int id1 = ev.getPointerId(upIndex); + final float x1 = mVelocityTracker.getXVelocity(id1); + final float y1 = mVelocityTracker.getYVelocity(id1); + for (int i = 0; i < count; i++) { + if (i == upIndex) continue; + + final int id2 = ev.getPointerId(i); + final float x = x1 * mVelocityTracker.getXVelocity(id2); + final float y = y1 * mVelocityTracker.getYVelocity(id2); + + final float dot = x + y; + if (dot < 0) { + mVelocityTracker.clear(); + break; + } + } + break; + + case MotionEvent.ACTION_DOWN: + if (mDoubleTapListener != null) { + boolean hadTapMessage = mHandler.hasMessages(TAP); + if (hadTapMessage) mHandler.removeMessages(TAP); + if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) + && hadTapMessage + && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { + // This is a second tap + mIsDoubleTapping = true; + // Give a callback with the first tap of the double-tap + handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); + // Give a callback with down event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else { + // This is a first tap + mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); + } + } + + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + if (mCurrentDownEvent != null) { + mCurrentDownEvent.recycle(); + } + mCurrentDownEvent = MotionEvent.obtain(ev); + mAlwaysInTapRegion = true; + mAlwaysInBiggerTapRegion = true; + mStillDown = true; + mInLongPress = false; + mDeferConfirmSingleTap = false; + + if (mIsLongpressEnabled) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendMessageAtTime( + mHandler.obtainMessage(LONG_PRESS), + mCurrentDownEvent/*.getDownTime()*/.getEventTime() + + ViewConfiguration.getLongPressTimeout()); + } + mHandler.sendEmptyMessageAtTime(SHOW_PRESS, + mCurrentDownEvent/*.getDownTime()*/.getEventTime() + TAP_TIMEOUT); + handled |= mListener.onDown(ev); + break; + + case MotionEvent.ACTION_MOVE: + if (mInLongPress || mInContextClick) { + break; + } + + // final int motionClassification = ev.getClassification(); + final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS); + + final float scrollX = mLastFocusX - focusX; + final float scrollY = mLastFocusY - focusY; + if (mIsDoubleTapping) { + // Give the move events of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mAlwaysInTapRegion) { + final int deltaX = (int) (focusX - mDownFocusX); + final int deltaY = (int) (focusY - mDownFocusY); + int distance = (deltaX * deltaX) + (deltaY * deltaY); + int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; + + final boolean ambiguousGesture = false; + // motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; + final boolean shouldInhibitDefaultAction = + hasPendingLongPress && ambiguousGesture; + if (shouldInhibitDefaultAction) { + // Inhibit default long press + if (distance > slopSquare) { + // The default action here is to remove long press. But if the touch + // slop below gets increased, and we never exceed the modified touch + // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing* + // will happen in response to user input. To prevent this, + // reschedule long press with a modified timeout. + mHandler.removeMessages(LONG_PRESS); + final long longPressTimeout = ViewConfiguration.getLongPressTimeout(); + mHandler.sendMessageAtTime( + mHandler.obtainMessage(LONG_PRESS), + ev.getDownTime() + + (long) (longPressTimeout * mAmbiguousGestureMultiplier)); + } + // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll + // until the gesture is resolved. + // However, for safety, simply increase the touch slop in case the + // classification is erroneous. Since the value is squared, multiply twice. + slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier; + } + + if (distance > slopSquare) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastFocusX = focusX; + mLastFocusY = focusY; + mAlwaysInTapRegion = false; + mHandler.removeMessages(TAP); + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + } + int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare; + if (distance > doubleTapSlopSquare) { + mAlwaysInBiggerTapRegion = false; + } + } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastFocusX = focusX; + mLastFocusY = focusY; + } + // final boolean deepPress = false; + // motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; + // if (deepPress && hasPendingLongPress) { + // mHandler.removeMessages(LONG_PRESS); + // mHandler.sendMessage( + // mHandler.obtainMessage( + // LONG_PRESS, + // TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS, + // 0 /* arg2 */)); + // } + break; + + case MotionEvent.ACTION_UP: + mStillDown = false; + MotionEvent currentUpEvent = MotionEvent.obtain(ev); + if (mIsDoubleTapping) { + // Finally, give the up event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mInLongPress) { + mHandler.removeMessages(TAP); + mInLongPress = false; + } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { + handled = mListener.onSingleTapUp(ev); + if (mDeferConfirmSingleTap && mDoubleTapListener != null) { + mDoubleTapListener.onSingleTapConfirmed(ev); + } + } else if (!mIgnoreNextUpEvent) { + + // A fling must travel the minimum tap distance + final VelocityTracker velocityTracker = mVelocityTracker; + final int pointerId = ev.getPointerId(0); + velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); + final float velocityY = velocityTracker.getYVelocity(pointerId); + final float velocityX = velocityTracker.getXVelocity(pointerId); + + if ((Math.abs(velocityY) > mMinimumFlingVelocity) + || (Math.abs(velocityX) > mMinimumFlingVelocity)) { + handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY); + } + } + if (mPreviousUpEvent != null) { + mPreviousUpEvent.recycle(); + } + // Hold the event we obtained above - listeners may have changed the original. + mPreviousUpEvent = currentUpEvent; + if (mVelocityTracker != null) { + // This may have been cleared when we called out to the + // application above. + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + mIsDoubleTapping = false; + mDeferConfirmSingleTap = false; + mIgnoreNextUpEvent = false; + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + break; + + case MotionEvent.ACTION_CANCEL: + cancel(); + break; + } + + return handled; + } + + /** + * Analyzes the given generic motion event and if applicable triggers the + * appropriate callbacks on the {@link OnGestureListener} supplied. + * + * @param ev The current motion event. + * @return true if the {@link OnGestureListener} consumed the event, + * else false. + */ + /*public boolean onGenericMotionEvent(MotionEvent ev) { + final int actionButton = ev.getActionButton(); + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_BUTTON_PRESS: + if (mContextClickListener != null && !mInContextClick && !mInLongPress + && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY + || actionButton == MotionEvent.BUTTON_SECONDARY)) { + if (mContextClickListener.onContextClick(ev)) { + mInContextClick = true; + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + return true; + } + } + break; + + case MotionEvent.ACTION_BUTTON_RELEASE: + if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY + || actionButton == MotionEvent.BUTTON_SECONDARY)) { + mInContextClick = false; + mIgnoreNextUpEvent = true; + } + break; + } + return false; + }*/ + + private void cancel() { + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + mIsDoubleTapping = false; + mStillDown = false; + mAlwaysInTapRegion = false; + mAlwaysInBiggerTapRegion = false; + mDeferConfirmSingleTap = false; + mInLongPress = false; + mInContextClick = false; + mIgnoreNextUpEvent = false; + } + + private void cancelTaps() { + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + mHandler.removeMessages(TAP); + mIsDoubleTapping = false; + mAlwaysInTapRegion = false; + mAlwaysInBiggerTapRegion = false; + mDeferConfirmSingleTap = false; + mInLongPress = false; + mInContextClick = false; + mIgnoreNextUpEvent = false; + } + + private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown) { + if (!mAlwaysInBiggerTapRegion) { + return false; + } + + final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime(); + if (deltaTime > DOUBLE_TAP_TIMEOUT) { + return false; + } + + int deltaX = (int) firstDown.getX() - (int) secondDown.getX(); + int deltaY = (int) firstDown.getY() - (int) secondDown.getY(); + final boolean isGeneratedGesture = false; + // (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0; + int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare; + return (deltaX * deltaX + deltaY * deltaY < slopSquare); + } + + private void dispatchLongPress() { + mHandler.removeMessages(TAP); + mDeferConfirmSingleTap = false; + mInLongPress = true; + mListener.onLongPress(mCurrentDownEvent); + } + } diff --git a/src/api-impl/android/view/MotionEvent.java b/src/api-impl/android/view/MotionEvent.java index bb9ec71c..9473f33a 100644 --- a/src/api-impl/android/view/MotionEvent.java +++ b/src/api-impl/android/view/MotionEvent.java @@ -1638,6 +1638,7 @@ public final class MotionEvent extends InputEvent { ev.coord_y = other.coord_y; ev.raw_x = other.raw_x; ev.raw_y = other.raw_y; + ev.eventTime = other.eventTime; return ev; } diff --git a/src/api-impl/android/view/ViewConfiguration.java b/src/api-impl/android/view/ViewConfiguration.java index 6a52ed30..baa42423 100644 --- a/src/api-impl/android/view/ViewConfiguration.java +++ b/src/api-impl/android/view/ViewConfiguration.java @@ -24,11 +24,11 @@ public class ViewConfiguration { } public static int getTapTimeout() { - return 0; + return 100; } public static int getLongPressTimeout() { - return 0; + return 400; } public int getScaledPagingTouchSlop(){ @@ -40,7 +40,7 @@ public class ViewConfiguration { } public static int getDoubleTapTimeout() { - return 0; + return 300; } public int getScaledDoubleTapSlop() {