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

Commit 900c31fd authored by Daniel Norman's avatar Daniel Norman
Browse files

Sends ACTION_CANCEL before the very first touch exploration HOVER event.

This resets the InputDispatcher state in case the user had a pointer
down (and kept it down) before enabling TalkBack or while booting up
the device before TalkBack has started up.

Fix: 364408887
Test: atest TouchExplorerTest
Test: touch and hold on the screen;
      start TalkBack with a shortcut or adb shell;
      lift your finger off the screen;
      touch the screen again, and observe that touch exploration
      functions properly.
Flag: com.android.server.accessibility.reset_input_dispatcher_before_first_touch_exploration
Change-Id: I1c2bdb7b3d32cd124257e60e8c91c073c3040f50
parent 67271f5b
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -208,6 +208,16 @@ flag {
    }
}

flag {
    name: "reset_input_dispatcher_before_first_touch_exploration"
    namespace: "accessibility"
    description: "Resets InputDispatcher state by sending ACTION_CANCEL before the first TouchExploration hover events"
    bug: "364408887"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "scan_packages_without_lock"
    namespace: "accessibility"
@@ -221,6 +231,7 @@ flag {
    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
    bug: "295575684"
}

flag {
    name: "send_hover_events_based_on_event_stream"
    namespace: "accessibility"
+18 −0
Original line number Diff line number Diff line
@@ -1613,6 +1613,19 @@ public class TouchExplorer extends BaseEventStreamTransformation
                dispatchGesture(gestureEvent);
            }
            if (!mEvents.isEmpty() && !mRawEvents.isEmpty()) {
                if (Flags.resetInputDispatcherBeforeFirstTouchExploration()
                        && !mState.hasResetInputDispatcherState()) {
                    // Cancel any possible ongoing touch gesture from before touch exploration
                    // started. This clears out the InputDispatcher event stream state so that it
                    // is ready to accept new injected HOVER events.
                    mDispatcher.sendMotionEvent(
                            mEvents.get(0),
                            ACTION_CANCEL,
                            mRawEvents.get(0),
                            mPointerIdBits,
                            mPolicyFlags);
                    setHasResetInputDispatcherState(true);
                }
                // Deliver a down event.
                mDispatcher.sendMotionEvent(
                        mEvents.get(0),
@@ -1773,4 +1786,9 @@ public class TouchExplorer extends BaseEventStreamTransformation
                + ", mDraggingPointerId: " + mDraggingPointerId
                + " }";
    }

    @VisibleForTesting
    void setHasResetInputDispatcherState(boolean value) {
        mState.setHasResetInputDispatcherState(value);
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ public class TouchState {
    private MotionEvent mLastInjectedHoverEvent;
    // The last injected hover event used for performing clicks.
    private MotionEvent mLastInjectedHoverEventForClick;
    private boolean mHasResetInputDispatcherState;
    // The time of the last injected down.
    private long mLastInjectedDownEventTime;
    // Keep track of which pointers sent to the system are down.
@@ -361,6 +362,14 @@ public class TouchState {
        return mLastInjectedDownEventTime;
    }

    boolean hasResetInputDispatcherState() {
        return mHasResetInputDispatcherState;
    }

    void setHasResetInputDispatcherState(boolean value) {
        mHasResetInputDispatcherState = value;
    }

    public int getLastTouchedWindowId() {
        return mLastTouchedWindowId;
    }
+36 −5
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@ import android.content.Context;
import android.graphics.PointF;
import android.os.Looper;
import android.os.SystemClock;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -56,6 +58,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.utils.GestureLogParser;
import com.android.server.testutils.OffsettableClock;

@@ -119,6 +122,9 @@ public class TouchExplorerTest {
    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
            new DexmakerShareClassLoaderRule();

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    /**
     * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
     * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -154,18 +160,43 @@ public class TouchExplorerTest {
        mHandler = new TestHandler();
        mTouchExplorer = new TouchExplorer(mContext, mMockAms, null, mHandler);
        mTouchExplorer.setNext(mCaptor);
        // Start TouchExplorer in the state where it has already reset InputDispatcher so that
        // all tests do not start with an irrelevant ACTION_CANCEL.
        mTouchExplorer.setHasResetInputDispatcherState(true);
    }

    @Test
    public void testOneFingerMove_shouldInjectHoverEvents() {
        goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
        // Wait for transiting to touch exploring state.
        triggerTouchExplorationWithOneFingerDownMoveUp();
        assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
        assertState(STATE_TOUCH_EXPLORING);
    }

    @Test
    @EnableFlags(Flags.FLAG_RESET_INPUT_DISPATCHER_BEFORE_FIRST_TOUCH_EXPLORATION)
    public void testStartTouchExploration_shouldResetInputDispatcherStateWithActionCancel() {
        // Start TouchExplorer in the state where it has *not yet* reset InputDispatcher.
        mTouchExplorer.setHasResetInputDispatcherState(false);
        // Trigger touch exploration twice, with a handler fast-forward in between so TouchExplorer
        // treats these as two separate interactions.
        triggerTouchExplorationWithOneFingerDownMoveUp();
        mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
        triggerTouchExplorationWithOneFingerDownMoveUp();

        assertCapturedEvents(
                ACTION_CANCEL, // Only one ACTION_CANCEL before the first touch exploration
                ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT,
                ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
        assertState(STATE_TOUCH_EXPLORING);
    }

    private void triggerTouchExplorationWithOneFingerDownMoveUp() {
        send(downEvent());
        // Fast forward so that TouchExplorer's timeouts transition us to the touch exploring state.
        mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
        moveEachPointers(mLastEvent, p(10, 10));
        send(mLastEvent);
        goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
        assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
        assertState(STATE_TOUCH_EXPLORING);
        send(upEvent());
    }

    /**