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

Commit 614c6be5 authored by Daniel Norman's avatar Daniel Norman
Browse files

Removes ACTION_CANCEL which caused InputDispatcher inconsistency.

This ACTION_CANCEL was used for two cases:
1. Cancelling an in-progress injected gesture if another injected
   gesture is requested.
2. Cancelling an in-progress real gesture if a new injected gesture is
   requested.

(2) was not functional because this ACTION_CANCEL had the deviceId of
the injected device, not the device used by real gestures. This bad
ACTION_CANCEL was then causing an inconsistent stream for future
injected events.
Rather than fix (2) this change removes that CANCEL case altogether; as of http://ag/21431224 CANCELing the real gesture is not necessary
because InputDispatcher already tracks deviceId and cancels
the real gesture on its own when A11y sends the injected gesture.

Bug: 384451671
Test: Manual testing; see b/384451671 #comment20
Test: atest MotionEventInjectorTest
Flag: com.android.server.accessibility.motion_event_injector_cancel_fix
Change-Id: I09aef2c853dfe86142e8b60e5b64c4560b2265a0
parent d88bfd50
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -208,6 +208,16 @@ flag {
    }
}

flag {
    name: "motion_event_injector_cancel_fix"
    namespace: "accessibility"
    description: "Fix the ACTION_CANCEL logic used in MotionEventInjector to avoid InputDispatcher inconsistency"
    bug: "384451671"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "package_monitor_dedicated_thread"
    namespace: "accessibility"
+27 −15
Original line number Diff line number Diff line
@@ -115,6 +115,8 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
    }

    // Note: MotionEventInjector is the first transformation in the AccessibilityInputFilter stream
    // so any event that arrives here is a real event from a real user interaction.
    @Override
    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (mTrace.isA11yTracingEnabledForTypes(
@@ -123,23 +125,31 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
                    AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE,
                    "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
        }
        // MotionEventInjector would cancel any injected gesture when any MotionEvent arrives.
        // For user using an external device to control the pointer movement, it's almost
        // impossible to perform the gestures. Any slightly unintended movement results in the
        // cancellation of the gesture.
        // InputDispatcher cancels an injected touch gesture if another MotionEvent arrives on this
        // display from another device or source, like a real touch or a real mouse pointer.
        // For user using an external device to control the pointer movement, it becomes difficult
        // to perform injected gestures because slight unintended movement results in cancellation
        // of the injected gesture; to fix this we swallow real mouse MotionEvents while an injected
        // touch gesture is in progress, preventing the mouse events from reaching InputDispatcher.
        if ((event.isFromSource(InputDevice.SOURCE_MOUSE)
                && event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE)
                && mOpenTouchGestureInProgress) {
            return;
        }
        if (Flags.motionEventInjectorCancelFix()) {
            // Pass this real event down the stream unmodified.
            super.onMotionEvent(event, rawEvent, policyFlags);
        } else {
            cancelAnyPendingInjectedEvents();
        if (!android.view.accessibility.Flags.preventA11yNontoolFromInjectingIntoSensitiveViews()) {
            if (!android.view.accessibility.Flags
                    .preventA11yNontoolFromInjectingIntoSensitiveViews()) {
                // Indicate that the input event is injected from accessibility, to let applications
                // distinguish it from events injected by other means.
                policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY;
            }
            sendMotionEventToNext(event, rawEvent, policyFlags);
        }
    }

    @Override
    public void clearEvents(int inputSource) {
@@ -223,8 +233,10 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
        }
        if (!continuingGesture) {
            cancelAnyPendingInjectedEvents();
            if (!Flags.motionEventInjectorCancelFix()) {
                // Injected gestures have been canceled, but real gestures still need cancelling
            cancelAnyGestureInProgress();
                cancelInjectedGestureInProgress();
            }
        }
        mServiceInterfaceForCurrentGesture = serviceInterface;

@@ -334,7 +346,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
        }
    }

    private void cancelAnyGestureInProgress() {
    private void cancelInjectedGestureInProgress() {
        if ((getNext() != null) && mOpenTouchGestureInProgress) {
            long now = SystemClock.uptimeMillis();
            MotionEvent cancelEvent =
@@ -356,7 +368,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
    private void cancelAnyPendingInjectedEvents() {
        if (mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
            mHandler.removeMessages(MESSAGE_SEND_MOTION_EVENT);
            cancelAnyGestureInProgress();
            cancelInjectedGestureInProgress();
            for (int i = mSequencesInProgress.size() - 1; i >= 0; i--) {
                notifyService(mServiceInterfaceForCurrentGesture,
                        mSequencesInProgress.get(i), false);
@@ -364,7 +376,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
            }
        } else if (mNumLastTouchPoints != 0) {
            // An injected gesture is in progress and waiting for a continuation. Cancel it.
            cancelAnyGestureInProgress();
            cancelInjectedGestureInProgress();
        }
        mNumLastTouchPoints = 0;
        mStrokeIdToPointerId.clear();
+65 −2
Original line number Diff line number Diff line
@@ -274,7 +274,10 @@ public class MotionEventInjectorTest {
    }

    @Test
    @DisableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
    @DisableFlags({
            FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS,
            Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX
    })
    public void testRegularEvent_afterGestureComplete_shouldPassToNext_withFlagInjectedFromA11y() {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
        injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
@@ -286,7 +289,10 @@ public class MotionEventInjectorTest {
    }

    @Test
    @EnableFlags(FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS)
    @EnableFlags({
            FLAG_PREVENT_A11Y_NONTOOL_FROM_INJECTING_INTO_SENSITIVE_VIEWS,
            Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX
    })
    public void testRegularEvent_afterGestureComplete_shouldPassToNext_withNoPolicyFlagChanges() {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
        injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
@@ -299,6 +305,7 @@ public class MotionEventInjectorTest {
    }

    @Test
    @DisableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testInjectEvents_withRealGestureUnderway_shouldCancelRealAndPassInjected() {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
        mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
@@ -316,6 +323,24 @@ public class MotionEventInjectorTest {
                        | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
    }

    @Test
    @EnableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testInjectEvents_withRealGestureUnderway_shouldNotCancelReal_ShouldPassInjected() {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
        mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
        injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);

        verify(next, times(1)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
        assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
        reset(next);

        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
        verify(next).onMotionEvent(
                argThat(mIsLineStart), argThat(mIsLineStart),
                eq(WindowManagerPolicyConstants.FLAG_PASS_TO_USER
                        | WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY));
    }

    @Test
    public void testInjectEvents_withRealMouseGestureUnderway_shouldContinueRealAndPassInjected() {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
@@ -354,6 +379,7 @@ public class MotionEventInjectorTest {
    }

    @Test
    @DisableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testOnMotionEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassReal()
            throws RemoteException {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
@@ -368,6 +394,24 @@ public class MotionEventInjectorTest {
        verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
    }

    @Test
    @EnableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testOnMotionEvents_openInjectedGestureInProgress_shouldNotCancel_shouldPassReal()
            throws RemoteException {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
        injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
        mMessageCapturingHandler.sendOneMessage(); // Send a motion event
        mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
        mMessageCapturingHandler.sendAllMessages();

        verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
        assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
        assertThat(mCaptor1.getAllValues().get(1), mIsClickDown);
        assertThat(mCaptor1.getAllValues().get(2), mIsLineMiddle);
        assertThat(mCaptor1.getAllValues().get(3), mIsLineEnd);
        verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
    }

    @Test
    public void
            testOnMotionEvents_fromMouseWithInjectedGestureInProgress_shouldNotCancelAndPassReal()
@@ -386,6 +430,7 @@ public class MotionEventInjectorTest {
    }

    @Test
    @DisableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testOnMotionEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassReal()
            throws RemoteException {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
@@ -676,6 +721,7 @@ public class MotionEventInjectorTest {
    }

    @Test
    @DisableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testContinuedGesture_realGestureArrivesInBetween_getsCanceled()
            throws Exception {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
@@ -710,6 +756,7 @@ public class MotionEventInjectorTest {
    }

    @Test
    @DisableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testClearEventsOnOtherSource_realGestureInProgress_shouldNotForgetAboutGesture() {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
        mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
@@ -723,6 +770,22 @@ public class MotionEventInjectorTest {
        assertThat(mCaptor1.getAllValues().get(2), mIsLineStart);
    }

    @Test
    @EnableFlags(Flags.FLAG_MOTION_EVENT_INJECTOR_CANCEL_FIX)
    public void testClearEventsOnOtherSource_shouldNotCancelRealOrInjectedGesture() {
        EventStreamTransformation next = attachMockNext(mMotionEventInjector);
        mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
        mMotionEventInjector.clearEvents(OTHER_EVENT_SOURCE);
        injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
        mMessageCapturingHandler.sendAllMessages();

        verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
        assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
        assertThat(mCaptor1.getAllValues().get(1), mIsLineStart);
        assertThat(mCaptor1.getAllValues().get(2), mIsLineMiddle);
        assertThat(mCaptor1.getAllValues().get(3), mIsLineEnd);
    }

    @Test
    public void testOnDestroy_shouldCancelGestures() throws RemoteException {
        mMotionEventInjector.onDestroy();