Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7647b678 authored by Zach Kuznia's avatar Zach Kuznia Committed by Android (Google) Code Review
Browse files

Merge "Make AccessibilityGestureDetector handle gesture detection start and end."

parents 31b48bee 9254b3e3
Loading
Loading
Loading
Loading
+87 −11
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import android.gesture.Prediction;
import android.util.Slog;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;

import com.android.internal.R;

@@ -46,7 +48,9 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
    public interface Listener {
        public void onDoubleTapAndHold(MotionEvent event, int policyFlags);
        public boolean onDoubleTap(MotionEvent event, int policyFlags);
        public boolean onGesture(int gestureId);
        public boolean onGestureCompleted(int gestureId);
        public void onGestureStarted();
        public void onGestureCancelled(MotionEvent event, int policyFlags);
    }

    private final Listener mListener;
@@ -64,6 +68,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
    // Indicates that motion events are being collected to match a gesture.
    private boolean mRecognizingGesture;

    // Indicates that we've collected enough data to be sure it could be a
    // gesture.
    private boolean mGestureConfirmed;

    // Indicates that motion events from the second pointer are being checked
    // for a double tap.
    private boolean mSecondFingerDoubleTap;
@@ -81,18 +89,41 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
    // The Y of the previous event.
    private float mPreviousY;

    // The X of the down event.
    private float mBaseX;

    // The Y of the down event.
    private float mBaseY;

    // Slop between the first and second tap to be a double tap.
    private final int mDoubleTapSlop;

    // The scaled velocity above which we detect gestures.
    private final int mScaledGestureDetectionVelocity;

    // Buffer for storing points for gesture detection.
    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);

    // Helper to track gesture velocity.
    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();

    // The minimal delta between moves to add a gesture point.
    private static final int TOUCH_TOLERANCE = 3;

    // The minimal score for accepting a predicted gesture.
    private static final float MIN_PREDICTION_SCORE = 2.0f;

    // The velocity above which we detect gestures.  Expressed in DIPs/Second.
    private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;

    // Constant used to calculate velocity in seconds.
    private static final int VELOCITY_UNITS_SECONDS = 1000;

    AccessibilityGestureDetector(Context context, Listener listener) {
        mListener = listener;

        mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();

        mGestureDetector = new GestureDetector(context, this);
        mGestureDetector.setOnDoubleTapListener(this);

@@ -100,9 +131,14 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
        mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */);
        mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
        mGestureLibrary.load();

        final float density = context.getResources().getDisplayMetrics().density;
        mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
    }

    public boolean onMotionEvent(MotionEvent event, int policyFlags) {
        mVelocityTracker.addMovement(event);

        final float x = event.getX();
        final float y = event.getY();

@@ -112,14 +148,48 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
                mDoubleTapDetected = false;
                mSecondFingerDoubleTap = false;
                mRecognizingGesture = true;
                mGestureConfirmed = false;
                mBaseX = x;
                mBaseY = y;
                mPreviousX = x;
                mPreviousY = y;
                mStrokeBuffer.clear();
                mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
                mVelocityTracker.clear();
                mVelocityTracker.addMovement(event);
                break;

            case MotionEvent.ACTION_MOVE:
                if (mRecognizingGesture) {
                    if (!mGestureConfirmed) {
                        mVelocityTracker.addMovement(event);
                        // It is *important* to use the distance traveled by the pointers
                        // on the screen which may or may not be magnified.
                        final float deltaX = mBaseX - event.getX(0);
                        final float deltaY = mBaseY - event.getY(0);
                        final double moveDelta = Math.hypot(deltaX, deltaY);
                        // The user has moved enough for us to decide.
                        if (moveDelta > mDoubleTapSlop) {
                            // Check whether the user is performing a gesture. We
                            // detect gestures if the pointer is moving above a
                            // given velocity.
                            mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS);
                            final float maxAbsVelocity = Math.max(
                                    Math.abs(mVelocityTracker.getXVelocity(0)),
                                    Math.abs(mVelocityTracker.getYVelocity(0)));
                            if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
                                // We have to perform gesture detection, so
                                // notify the listener.
                                mGestureConfirmed = true;
                                mListener.onGestureStarted();
                            } else {
                                // This won't match any gesture, so notify the
                                // listener.
                                cancelGesture();
                                mListener.onGestureCancelled(event, policyFlags);
                            }
                        }
                    }
                    final float dX = Math.abs(x - mPreviousX);
                    final float dY = Math.abs(y - mPreviousY);
                    if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
@@ -134,12 +204,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
                if (maybeFinishDoubleTap(event, policyFlags)) {
                    return true;
                }
                if (mRecognizingGesture) {
                if (mGestureConfirmed) {
                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));

                    if (recognizeGesture()) {
                        return true;
                    if (!recognizeGesture()) {
                        mListener.onGestureCancelled(event, policyFlags);
                    }
                    return true;
                }
                break;

@@ -167,6 +238,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
                    return true;
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                clear();
                break;
        }

        // If we're detecting taps on the second finger, map events from the
@@ -194,18 +269,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
        mDoubleTapDetected = false;
        mSecondFingerDoubleTap = false;
        cancelGesture();
        mStrokeBuffer.clear();
        mVelocityTracker.clear();
    }

    public boolean firstTapDetected() {
        return mFirstTapDetected;
    }

    public void cancelGesture() {
        mRecognizingGesture = false;
        mStrokeBuffer.clear();
    }

    @Override
    public void onLongPress(MotionEvent e) {
        maybeSendLongPress(e, mPolicyFlags);
@@ -251,6 +321,12 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
        return mListener.onDoubleTap(event, policyFlags);
    }

    private void cancelGesture() {
        mRecognizingGesture = false;
        mGestureConfirmed = false;
        mStrokeBuffer.clear();
    }

    private boolean recognizeGesture() {
        Gesture gesture = new Gesture();
        gesture.addStroke(new GestureStroke(mStrokeBuffer));
@@ -265,7 +341,7 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
                }
                try {
                    final int gestureId = Integer.parseInt(bestPrediction.name);
                    if (mListener.onGesture(gestureId)) {
                    if (mListener.onGestureCompleted(gestureId)) {
                        return true;
                    }
                } catch (NumberFormatException nfe) {
+53 −98
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
@@ -87,9 +86,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
    // Invalid pointer ID.
    private static final int INVALID_POINTER_ID = -1;

    // The velocity above which we detect gestures.
    private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;

    // The minimal distance before we take the middle of the distance between
    // the two dragging pointers as opposed to use the location of the primary one.
    private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
@@ -134,15 +130,9 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
    // the two dragging pointers as opposed to use the location of the primary one.
    private final int mScaledMinPointerDistanceToUseMiddleLocation;

    // The scaled velocity above which we detect gestures.
    private final int mScaledGestureDetectionVelocity;

    // The handler to which to delegate events.
    private EventStreamTransformation mNext;

    // Helper to track gesture velocity.
    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();

    // Helper class to track received pointers.
    private final ReceivedPointerTracker mReceivedPointerTracker;

@@ -200,7 +190,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
        final float density = context.getResources().getDisplayMetrics().density;
        mScaledMinPointerDistanceToUseMiddleLocation =
            (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
        mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
    }

    @Override
@@ -289,11 +278,19 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe

        mReceivedPointerTracker.onMotionEvent(rawEvent);

        if (mGestureDetector.onMotionEvent(event, policyFlags)) {
        // The motion detector is interested in the movements in physical space,
        // so it uses the rawEvent to ignore magnification and other
        // transformations.
        if (mGestureDetector.onMotionEvent(rawEvent, policyFlags)) {
            // Event was handled by the gesture detector.
            return;
        }

        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
            clear(event, policyFlags);
            return;
        }

        switch(mCurrentState) {
            case STATE_TOUCH_EXPLORING: {
                handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
@@ -305,7 +302,7 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                handleMotionEventStateDelegating(event, policyFlags);
            } break;
            case STATE_GESTURE_DETECTING: {
                handleMotionEventGestureDetecting(rawEvent, policyFlags);
                // Already handled.
            } break;
            default:
                throw new IllegalStateException("Illegal state: " + mCurrentState);
@@ -440,26 +437,50 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
    }

    @Override
    public boolean onGesture(int gestureId) {
    public boolean onGestureCompleted(int gestureId) {
        if (mCurrentState != STATE_GESTURE_DETECTING) {
            return false;
        }

        mAms.onTouchInteractionEnd();

        // Announce the end of the gesture recognition.
        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
        // Announce the end of a the touch interaction.
        sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
        endGestureDetection();

        mAms.onGesture(gestureId);

        mExitGestureDetectionModeDelayed.cancel();
        mCurrentState = STATE_TOUCH_EXPLORING;

        return true;
    }

    @Override
    public void onGestureStarted() {
      // We have to perform gesture detection, so
      // clear the current state and try to detect.
      mCurrentState = STATE_GESTURE_DETECTING;
      mSendHoverEnterAndMoveDelayed.cancel();
      mSendHoverExitDelayed.cancel();
      mExitGestureDetectionModeDelayed.post();
      // Send accessibility event to announce the start
      // of gesture recognition.
      sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
    }

    @Override
    public void onGestureCancelled(MotionEvent event, int policyFlags) {
      if (mCurrentState == STATE_GESTURE_DETECTING) {
          endGestureDetection();
      } else if (mCurrentState == STATE_TOUCH_EXPLORING) {
          final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
          final int pointerIdBits = (1 << pointerId);

          // Cache the event until we discern exploration from gesturing.
          mSendHoverEnterAndMoveDelayed.addEvent(event);

          // We have just decided that the user is touch,
          // exploring so start sending events.
          mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
          mSendHoverExitDelayed.cancel();
          sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
      }
    }

    /**
     * Handles a motion event in touch exploring state.
     *
@@ -471,8 +492,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
            int policyFlags) {
        ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;

        mVelocityTracker.addMovement(rawEvent);

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                mAms.onTouchInteractionStart();
@@ -525,46 +544,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                        if (mSendHoverEnterAndMoveDelayed.isPending()) {
                            // Cache the event until we discern exploration from gesturing.
                            mSendHoverEnterAndMoveDelayed.addEvent(event);

                            // It is *important* to use the distance traveled by the pointers
                            // on the screen which may or may not be magnified.
                            final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
                                - rawEvent.getX(pointerIndex);
                            final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
                                - rawEvent.getY(pointerIndex);
                            final double moveDelta = Math.hypot(deltaX, deltaY);
                            // The user has moved enough for us to decide.
                            if (moveDelta > mDoubleTapSlop) {
                                // Check whether the user is performing a gesture. We
                                // detect gestures if the pointer is moving above a
                                // given velocity.
                                mVelocityTracker.computeCurrentVelocity(1000);
                                final float maxAbsVelocity = Math.max(
                                        Math.abs(mVelocityTracker.getXVelocity(pointerId)),
                                        Math.abs(mVelocityTracker.getYVelocity(pointerId)));
                                if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
                                    // We have to perform gesture detection, so
                                    // clear the current state and try to detect.
                                    mCurrentState = STATE_GESTURE_DETECTING;
                                    mVelocityTracker.clear();
                                    mSendHoverEnterAndMoveDelayed.cancel();
                                    mSendHoverExitDelayed.cancel();
                                    mExitGestureDetectionModeDelayed.post();
                                    // Send accessibility event to announce the start
                                    // of gesture recognition.
                                    sendAccessibilityEvent(
                                            AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
                                } else {
                                    // We have just decided that the user is touch,
                                    // exploring so start sending events.
                                    mGestureDetector.cancelGesture();
                                    mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
                                    mSendHoverExitDelayed.cancel();
                                    sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
                                            pointerIdBits, policyFlags);
                                }
                                break;
                            }
                        } else {
                            if (mTouchExplorationInProgress) {
                                sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
@@ -602,11 +581,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                            }
                        }

                        // We know that a new state transition is to happen and the
                        // new state will not be gesture recognition, so cancel
                        // the gesture.
                        mGestureDetector.cancelGesture();

                        if (isDraggingGesture(event)) {
                            // Two pointers moving in the same direction within
                            // a given distance perform a drag.
@@ -620,7 +594,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                            mCurrentState = STATE_DELEGATING;
                            sendDownForAllNotInjectedPointers(event, policyFlags);
                        }
                        mVelocityTracker.clear();
                    } break;
                    default: {
                        // More than one pointer so the user is not touch exploring
@@ -639,7 +612,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                        // More than two pointers are delegated to the view hierarchy.
                        mCurrentState = STATE_DELEGATING;
                        sendDownForAllNotInjectedPointers(event, policyFlags);
                        mVelocityTracker.clear();
                    }
                }
            } break;
@@ -648,8 +620,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                final int pointerId = event.getPointerId(event.getActionIndex());
                final int pointerIdBits = (1 << pointerId);

                mVelocityTracker.clear();

                if (mSendHoverEnterAndMoveDelayed.isPending()) {
                    // If we have not delivered the enter schedule an exit.
                    mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
@@ -663,9 +633,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                }

            } break;
            case MotionEvent.ACTION_CANCEL: {
                clear(event, policyFlags);
            } break;
        }
    }

@@ -756,9 +723,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
                }
                mCurrentState = STATE_TOUCH_EXPLORING;
            } break;
            case MotionEvent.ACTION_CANCEL: {
                clear(event, policyFlags);
            } break;
        }
    }

@@ -795,9 +759,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe

                mCurrentState = STATE_TOUCH_EXPLORING;
            } break;
            case MotionEvent.ACTION_CANCEL: {
                clear(event, policyFlags);
            } break;
            default: {
                // Deliver the event.
                sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
@@ -805,10 +766,9 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
        }
    }

    private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_UP: {
    private void endGestureDetection() {
        mAms.onTouchInteractionEnd();

        // Announce the end of the gesture recognition.
        sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
        // Announce the end of a the touch interaction.
@@ -816,11 +776,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe

        mExitGestureDetectionModeDelayed.cancel();
        mCurrentState = STATE_TOUCH_EXPLORING;
            } break;
            case MotionEvent.ACTION_CANCEL: {
                clear(event, policyFlags);
            } break;
        }
    }

    /**