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

Commit cb49d47b authored by ryanlwlin's avatar ryanlwlin
Browse files

Fix touchexploration multi-finger gesture conflict

WindowMagnificationGestureHandler has higher priority to address
motion events. When the user put two fingers down on the screen,
magnification gesture detector will intercept all motion events.
It ends up the user couldn't perform any multi-finger gestures.

To fix it, we make magnification gesture detection more accurate:
1. swiping gesture sucesses only with one finger.
2. Regarding two-finger gesture, only swipe or stay on the screen
over a duration will be recognized.

Bug: 163016948
Test: manually test: enable Talback and perform 3-finger swipe gesture
      atest com.android.server.accessibility.magnification
Change-Id: I310cf6e3fb2cb2b5b6fbc6a0ba9f0aa1d219b4df
parent 664a31e6
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -98,8 +98,7 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene
        }
        mProcessMotionEvent = true;
        for (int i = 0; i < mGestureMatchers.size(); i++) {
            final GestureMatcher matcher =
                    mGestureMatchers.get(i);
            final GestureMatcher matcher = mGestureMatchers.get(i);
            matcher.onMotionEvent(event, rawEvent, policyFlags);
            if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
                clear();
@@ -128,7 +127,10 @@ public final class GesturesObserver implements GestureMatcher.StateChangeListene
            MotionEvent rawEvent, int policyFlags) {
        if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
            mListener.onGestureCompleted(gestureId, event, rawEvent, policyFlags);
            //Clear the states in onMotionEvent().
            // Ideally we clear the states in onMotionEvent(), this case is for hold gestures.
            // If we clear before processing up event , then MultiTap matcher cancels the gesture
            // due to incorrect state. It ends up listener#onGestureCancelled is called even
            // the gesture is detected.
            if (!mProcessMotionEvent) {
                clear();
            }
+10 −4
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ import java.lang.annotation.RetentionPolicy;
class MagnificationGestureMatcher {

    private static final int GESTURE_BASE = 100;
    public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1;
    public static final int GESTURE_TWO_FINGERS_DOWN_OR_SWIPE = GESTURE_BASE + 1;
    public static final int GESTURE_SWIPE = GESTURE_BASE + 2;
    public static final int GESTURE_SINGLE_TAP = GESTURE_BASE + 3;
    public static final int GESTURE_SINGLE_TAP_AND_HOLD = GESTURE_BASE + 4;
@@ -41,7 +41,7 @@ class MagnificationGestureMatcher {
    public static final int GESTURE_TRIPLE_TAP_AND_HOLD = GESTURE_BASE + 6;

    @IntDef(prefix = {"GESTURE_MAGNIFICATION_"}, value = {
            GESTURE_TWO_FINGER_DOWN,
            GESTURE_TWO_FINGERS_DOWN_OR_SWIPE,
            GESTURE_SWIPE
    })
    @Retention(RetentionPolicy.SOURCE)
@@ -57,8 +57,8 @@ class MagnificationGestureMatcher {
        switch (gestureId) {
            case GESTURE_SWIPE:
                return "GESTURE_SWIPE";
            case GESTURE_TWO_FINGER_DOWN:
                return "GESTURE_TWO_FINGER_DOWN";
            case GESTURE_TWO_FINGERS_DOWN_OR_SWIPE:
                return "GESTURE_TWO_FINGERS_DOWN_OR_SWIPE";
            case GESTURE_SINGLE_TAP:
                return "GESTURE_SINGLE_TAP";
            case GESTURE_SINGLE_TAP_AND_HOLD:
@@ -71,6 +71,12 @@ class MagnificationGestureMatcher {
        return "none";
    }

    /**
     * @param context
     * @return the duration in milliseconds between the first tap's down event and
     * the second tap's down event to be considered that the user is going to performing
     *  panning/scaling gesture.
     */
    static int getMagnificationMultiTapTimeout(Context context) {
        return ViewConfiguration.getDoubleTapTimeout() + context.getResources().getInteger(
                R.integer.config_screen_magnification_multi_tap_adjustment);
+1 −1
Original line number Diff line number Diff line
@@ -65,7 +65,7 @@ class MagnificationGesturesObserver implements GesturesObserver.Listener {
         *                  the last event before timeout.
         *
         * @see MagnificationGestureMatcher#GESTURE_SWIPE
         * @see MagnificationGestureMatcher#GESTURE_TWO_FINGER_DOWN
         * @see MagnificationGestureMatcher#GESTURE_TWO_FINGERS_DOWN_OR_SWIPE
         */
        void onGestureCompleted(@GestureId int gestureId, long lastDownEventTime,
                List<MotionEventInfo> delayedEventQueue, MotionEvent event);
+6 −1
Original line number Diff line number Diff line
@@ -48,6 +48,11 @@ class SimpleSwipe extends GestureMatcher {
        cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags);
    }

    @Override
    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        cancelGesture(event, rawEvent, policyFlags);
    }

    @Override
    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (gestureMatched(event, rawEvent, policyFlags)) {
@@ -65,7 +70,7 @@ class SimpleSwipe extends GestureMatcher {
    }

    private boolean gestureMatched(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        return mLastDown != null && (distance(mLastDown, event) >= mSwipeMinDistance);
        return mLastDown != null && (distance(mLastDown, event) > mSwipeMinDistance);
    }

    @Override
+123 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 * Copyright (C) 2021 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.
@@ -16,42 +16,76 @@

package com.android.server.accessibility.magnification;

import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import com.android.server.accessibility.gestures.GestureMatcher;

/**
 *
 * This class is responsible for matching two fingers down gestures. The gesture matching
 * result is determined in a duration.
 * This class is responsible for detecting that the user is using two fingers to perform
 * swiping gestures or just stay pressed on the screen. The gesture matching result is determined
 * in a duration.
 */
final class TwoFingersDown extends GestureMatcher {
final class TwoFingersDownOrSwipe extends GestureMatcher {

    private MotionEvent mLastDown;
    private final int mDoubleTapTimeout;
    private final int mDetectionDurationMillis;
    private final int mSwipeMinDistance;
    private MotionEvent mFirstPointerDown;
    private MotionEvent mSecondPointerDown;

    TwoFingersDown(Context context) {
        super(MagnificationGestureMatcher.GESTURE_TWO_FINGER_DOWN,
    TwoFingersDownOrSwipe(Context context) {
        super(MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE,
                new Handler(context.getMainLooper()), null);
        mDetectionDurationMillis = MagnificationGestureMatcher.getMagnificationMultiTapTimeout(
                context);
        mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
        mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();

    }

    @Override
    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        mLastDown = MotionEvent.obtain(event);
        mFirstPointerDown = MotionEvent.obtain(event);
        cancelAfter(mDetectionDurationMillis, event, rawEvent, policyFlags);
    }

    @Override
    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (mLastDown == null) {
        if (mFirstPointerDown == null) {
            cancelGesture(event, rawEvent, policyFlags);
        }
        if (event.getPointerCount() == 2) {
            mSecondPointerDown = MotionEvent.obtain(event);
            completeAfter(mDoubleTapTimeout, event, rawEvent, policyFlags);
        } else {
            cancelGesture(event, rawEvent, policyFlags);
        }
    }

    @Override
    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (mFirstPointerDown == null || mSecondPointerDown == null) {
            return;
        }
        if (distance(mFirstPointerDown, /* move */ event) > mSwipeMinDistance) {
            completeGesture(event, rawEvent, policyFlags);
            return;
        }
        if (distance(mSecondPointerDown, /* move */ event) > mSwipeMinDistance) {
            // The second pointer is swiping.
            completeGesture(event, rawEvent, policyFlags);
        }
    }

    @Override
    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        cancelGesture(event, rawEvent, policyFlags);
    }

    @Override
    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
@@ -60,9 +94,13 @@ final class TwoFingersDown extends GestureMatcher {

    @Override
    public void clear() {
        if (mLastDown != null) {
            mLastDown.recycle();
            mLastDown = null;
        if (mFirstPointerDown != null) {
            mFirstPointerDown.recycle();
            mFirstPointerDown = null;
        }
        if (mSecondPointerDown != null) {
            mSecondPointerDown.recycle();
            mSecondPointerDown = null;
        }
        super.clear();
    }
@@ -71,4 +109,15 @@ final class TwoFingersDown extends GestureMatcher {
    protected String getGestureName() {
        return this.getClass().getSimpleName();
    }

    private static double distance(@NonNull MotionEvent downEvent, @NonNull MotionEvent moveEvent) {
        final int downActionIndex = downEvent.getActionIndex();
        final int downPointerId = downEvent.getPointerId(downActionIndex);
        final int moveActionIndex = moveEvent.findPointerIndex(downPointerId);
        if (moveActionIndex < 0) {
            return -1;
        }
        return MathUtils.dist(downEvent.getX(downActionIndex), downEvent.getY(downActionIndex),
                moveEvent.getX(moveActionIndex), moveEvent.getY(moveActionIndex));
    }
}
Loading