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

Commit 28239e8a authored by Zachary Kuznia's avatar Zachary Kuznia
Browse files

Improve gesture recognition state change for TouchExplorer.

This increases the tolerace for detecting a user interaction as a
gesture in Touch exploration mode.  The detection should also be more
consistant between devices, as the threshold is based on physical
units.

Change-Id: Iec935e862d2634fba0fea6dc6d81e62ed2213cbc
parent 4fb2d18c
Loading
Loading
Loading
Loading
+66 −72
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.gesture.GestureStore;
import android.gesture.GestureStroke;
import android.gesture.Prediction;
import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -70,7 +71,7 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen

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

    // Indicates that motion events from the second pointer are being checked
    // for a double tap.
@@ -83,47 +84,39 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
    // Policy flags of the previous event.
    private int mPolicyFlags;

    // The X of the previous event.
    private float mPreviousX;
    // These values track the previous point that was saved to use for gesture
    // detection.  They are only updated when the user moves more than the
    // recognition threshold.
    private float mPreviousGestureX;
    private float mPreviousGestureY;

    // The Y of the previous event.
    private float mPreviousY;

    // The X of the down event.
    // These values track the previous point that was used to determine if there
    // was a transition into or out of gesture detection.  They are updated when
    // the user moves more than the detection threshold.
    private float mBaseX;

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

    // 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;
    // This is the calculated movement threshold used track if the user is still
    // moving their finger.
    private final float mGestureDetectionThreshold;

    // 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;
    private static final int GESTURE_CONFIRM_MM = 10;
    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 1000;
    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 500;

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

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

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

@@ -132,15 +125,14 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
        mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
        mGestureLibrary.load();

        final float density = context.getResources().getDisplayMetrics().density;
        mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
        mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
                context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
    }

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

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

        mPolicyFlags = policyFlags;
        switch (event.getActionMasked()) {
@@ -148,54 +140,56 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
                mDoubleTapDetected = false;
                mSecondFingerDoubleTap = false;
                mRecognizingGesture = true;
                mGestureConfirmed = false;
                mGestureStarted = false;
                mPreviousGestureX = x;
                mPreviousGestureY = y;
                mStrokeBuffer.clear();
                mStrokeBuffer.add(new GesturePoint(x, y, time));

                mBaseX = x;
                mBaseY = y;
                mPreviousX = x;
                mPreviousY = y;
                mStrokeBuffer.clear();
                mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
                mVelocityTracker.clear();
                mVelocityTracker.addMovement(event);
                mBaseTime = time;
                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 float deltaX = mBaseX - x;
                    final float deltaY = mBaseY - y;
                    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;
                    if (moveDelta > mGestureDetectionThreshold) {
                        // If the pointer has moved more than the threshold,
                        // update the stored values.
                        mBaseX = x;
                        mBaseY = y;
                        mBaseTime = time;

                        // If this hasn't been confirmed as a gesture yet, send
                        // the event.
                        if (!mGestureStarted) {
                            mGestureStarted = true;
                            mListener.onGestureStarted();
                        }
                    } else {
                                // This won't match any gesture, so notify the
                                // listener.
                        final long timeDelta = time - mBaseTime;
                        final long threshold = mGestureStarted ?
                            CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
                            CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;

                        // If the pointer hasn't moved for longer than the
                        // timeout, cancel gesture detection.
                        if (timeDelta > threshold) {
                            cancelGesture();
                            mListener.onGestureCancelled(event, policyFlags);
                            return false;
                        }
                    }
                    }
                    final float dX = Math.abs(x - mPreviousX);
                    final float dY = Math.abs(y - mPreviousY);

                    final float dX = Math.abs(x - mPreviousGestureX);
                    final float dY = Math.abs(y - mPreviousGestureY);
                    if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
                        mPreviousX = x;
                        mPreviousY = y;
                        mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
                        mPreviousGestureX = x;
                        mPreviousGestureY = y;
                        mStrokeBuffer.add(new GesturePoint(x, y, time));
                    }
                }
                break;
@@ -204,8 +198,8 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
                if (maybeFinishDoubleTap(event, policyFlags)) {
                    return true;
                }
                if (mGestureConfirmed) {
                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
                if (mGestureStarted) {
                    mStrokeBuffer.add(new GesturePoint(x, y, time));

                    if (!recognizeGesture()) {
                        mListener.onGestureCancelled(event, policyFlags);
@@ -223,7 +217,7 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
                    // If this was the second finger, attempt to recognize double
                    // taps on it.
                    mSecondFingerDoubleTap = true;
                    mSecondPointerDownTime = event.getEventTime();
                    mSecondPointerDownTime = time;
                } else {
                    // If there are more than two fingers down, stop watching
                    // for a double tap.
@@ -268,8 +262,8 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
        mFirstTapDetected = false;
        mDoubleTapDetected = false;
        mSecondFingerDoubleTap = false;
        mGestureStarted = false;
        cancelGesture();
        mVelocityTracker.clear();
    }

    public boolean firstTapDetected() {
@@ -323,7 +317,7 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen

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