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

Commit 0c25ba71 authored by Daniel Norman's avatar Daniel Norman Committed by Android (Google) Code Review
Browse files

Merge changes from topic "b384451671" into main

* changes:
  Removes ACTION_CANCEL which caused InputDispatcher inconsistency.
  Cleanup mOpenGesturesInProgress to use a simple boolean.
parents c6c026a7 614c6be5
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"
+41 −28
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -67,7 +66,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
    private static MotionEvent.PointerProperties[] sPointerProps;

    private final Handler mHandler;
    private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
    private boolean mOpenTouchGestureInProgress = false;

    private final AccessibilityTraceManager mTrace;
    private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
@@ -116,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(
@@ -124,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)
                && mOpenGesturesInProgress.get(EVENT_SOURCE, false)) {
                && 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) {
@@ -148,8 +157,8 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
         * Reset state for motion events passing through so we won't send a cancel event for
         * them.
         */
        if (!mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
            mOpenGesturesInProgress.put(inputSource, false);
        if (!mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT) && inputSource == EVENT_SOURCE) {
            mOpenTouchGestureInProgress = false;
        }
    }

@@ -224,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(EVENT_SOURCE);
                cancelInjectedGestureInProgress();
            }
        }
        mServiceInterfaceForCurrentGesture = serviceInterface;

@@ -323,18 +334,20 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
            int policyFlags) {
        if (getNext() != null) {
            super.onMotionEvent(event, rawEvent, policyFlags);
            if (event.getSource() == EVENT_SOURCE) {
                if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                mOpenGesturesInProgress.put(event.getSource(), true);
                    mOpenTouchGestureInProgress = true;
                }
                if ((event.getActionMasked() == MotionEvent.ACTION_UP)
                        || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
                mOpenGesturesInProgress.put(event.getSource(), false);
                    mOpenTouchGestureInProgress = false;
                }
            }
        }
    }

    private void cancelAnyGestureInProgress(int source) {
        if ((getNext() != null) && mOpenGesturesInProgress.get(source, false)) {
    private void cancelInjectedGestureInProgress() {
        if ((getNext() != null) && mOpenTouchGestureInProgress) {
            long now = SystemClock.uptimeMillis();
            MotionEvent cancelEvent =
                    obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
@@ -348,14 +361,14 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
                policyFlags |= WindowManagerPolicyConstants.FLAG_INJECTED_FROM_ACCESSIBILITY_TOOL;
            }
            sendMotionEventToNext(cancelEvent, cancelEvent, policyFlags);
            mOpenGesturesInProgress.put(source, false);
            mOpenTouchGestureInProgress = false;
        }
    }

    private void cancelAnyPendingInjectedEvents() {
        if (mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
            mHandler.removeMessages(MESSAGE_SEND_MOTION_EVENT);
            cancelAnyGestureInProgress(EVENT_SOURCE);
            cancelInjectedGestureInProgress();
            for (int i = mSequencesInProgress.size() - 1; i >= 0; i--) {
                notifyService(mServiceInterfaceForCurrentGesture,
                        mSequencesInProgress.get(i), false);
@@ -363,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(EVENT_SOURCE);
            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();