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

Commit 56ba8aa7 authored by Siarhei Vishniakou's avatar Siarhei Vishniakou
Browse files

Add multi-device checks to AccessibilityInputFilterInputTest

Prior to this CL, AccessibilityInputFilter assumed that only a single
input device could be active at a time. However, after the recent
multi-device input feature, that assumption no longer holds.

As a result, InputFilter is being sent multi-device streams from the
InputDispatcher. This is problematic, because InputFilter gets confused
and start processing the events from all devices at the same time.
Components like TouchExplorer don't look at the device id, and quickly
get into an inconsistent state.

This causes some events to be dropped by InputFilter, and some to be
sent back to the InputDispatcher. In some situations, this leads to an
inconsistent input stream being injected back into dispatcher, which
could lead to a crash.

In this CL, we add a multiplexer to AccessibilityInputFilter so that
only one input device can be active at a time. All MotionEvents from
other devices are going to be ignored.

Multi-device behaviour of AccessibilityInputFilter is also being tested
in this CL, for the cases where
none of the a11y features are on, and where all of the a11y features are
on.

Bug: 310014874
Test: atest FrameworksServicesTests:AccessibilityInputFilterInputTest
Change-Id: Iac18fd671787d880aca1f6fbfd17d822e5ae8ee6
parent 0ca9cb73
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -65,6 +65,16 @@ flag {
    bug: "319175022"
}

flag {
    name: "handle_multi_device_input"
    namespace: "accessibility"
    description: "Select a single active device when a multi-device stream is received by AccessibilityInputFilter"
    bug: "310014874"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "pinch_zoom_zero_min_span"
    namespace: "accessibility"
+127 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
@@ -35,6 +36,8 @@ import android.view.InputEvent;
import android.view.InputFilter;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.accessibility.AccessibilityEvent;

import com.android.server.LocalServices;
@@ -203,6 +206,62 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo

    private EventStreamState mKeyboardStreamState;

    /**
     * The last MotionEvent emitted from the input device that's currently active. This is used to
     * keep track of which input device is currently active, and also to generate the cancel event
     * if a new device becomes active.
     */
    private MotionEvent mLastActiveDeviceMotionEvent = null;

    private static MotionEvent cancelMotion(MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
                || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT
                || event.getActionMasked() == MotionEvent.ACTION_UP) {
            throw new IllegalArgumentException("Can't cancel " + event);
        }
        final int action;
        if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
                || event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
            action = MotionEvent.ACTION_HOVER_EXIT;
        } else {
            action = MotionEvent.ACTION_CANCEL;
        }

        final int pointerCount;
        if (event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
            pointerCount = event.getPointerCount() - 1;
        } else {
            pointerCount = event.getPointerCount();
        }
        final PointerProperties[] properties = new PointerProperties[pointerCount];
        final PointerCoords[] coords = new PointerCoords[pointerCount];
        int newPointerIndex = 0;
        for (int i = 0; i < event.getPointerCount(); i++) {
            if (event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
                if (event.getActionIndex() == i) {
                    // Skip the pointer that's going away
                    continue;
                }
            }
            final PointerCoords c = new PointerCoords();
            c.x = event.getX(i);
            c.y = event.getY(i);
            coords[newPointerIndex] = c;
            final PointerProperties p = new PointerProperties();
            p.id = event.getPointerId(i);
            p.toolType = event.getToolType(i);
            properties[newPointerIndex] = p;
            newPointerIndex++;
        }

        return MotionEvent.obtain(event.getDownTime(), SystemClock.uptimeMillis(), action,
                pointerCount, properties, coords,
                event.getMetaState(), event.getButtonState(),
                event.getXPrecision(), event.getYPrecision(), event.getDeviceId(),
                event.getEdgeFlags(), event.getSource(), event.getDisplayId(), event.getFlags(),
                event.getClassification());
    }

    AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
        this(context, service, new SparseArray<>(0));
    }
@@ -260,6 +319,17 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
                    AccessibilityTrace.FLAGS_INPUT_FILTER,
                    "event=" + event + ";policyFlags=" + policyFlags);
        }
        if (Flags.handleMultiDeviceInput()) {
            if (!shouldProcessMultiDeviceEvent(event, policyFlags)) {
                // We are only allowing a single device to be active at a time.
                return;
            }
        }

        onInputEventInternal(event, policyFlags);
    }

    private void onInputEventInternal(InputEvent event, int policyFlags) {
        if (mEventHandler.size() == 0) {
            if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
            super.onInputEvent(event, policyFlags);
@@ -353,6 +423,63 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
        }
    }

    boolean shouldProcessMultiDeviceEvent(InputEvent event, int policyFlags) {
        if (event instanceof MotionEvent motion) {
            // Only allow 1 device to be sending motion events at a time
            // If the event is from an active device, let it through.
            // If the event is not from an active device, only let it through if it starts a new
            // gesture like ACTION_DOWN or ACTION_HOVER_ENTER
            final boolean eventIsFromCurrentDevice = mLastActiveDeviceMotionEvent != null
                    && mLastActiveDeviceMotionEvent.getDeviceId() == motion.getDeviceId();
            final int actionMasked = motion.getActionMasked();
            switch (actionMasked) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_HOVER_ENTER:
                case MotionEvent.ACTION_HOVER_MOVE: {
                    if (mLastActiveDeviceMotionEvent != null
                            && mLastActiveDeviceMotionEvent.getDeviceId() != motion.getDeviceId()) {
                        // This is a new gesture from a new device. Cancel the existing state
                        // and let this through
                        MotionEvent canceled = cancelMotion(mLastActiveDeviceMotionEvent);
                        onInputEventInternal(canceled, policyFlags);
                    }
                    mLastActiveDeviceMotionEvent = MotionEvent.obtain(motion);
                    return true;
                }
                case MotionEvent.ACTION_MOVE:
                case MotionEvent.ACTION_POINTER_DOWN:
                case MotionEvent.ACTION_POINTER_UP: {
                    if (eventIsFromCurrentDevice) {
                        mLastActiveDeviceMotionEvent = MotionEvent.obtain(motion);
                        return true;
                    } else {
                        return false;
                    }
                }
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_HOVER_EXIT: {
                    if (eventIsFromCurrentDevice) {
                        // This is the last event of the gesture from this device.
                        mLastActiveDeviceMotionEvent = null;
                        return true;
                    } else {
                        // Event is from another device
                        return false;
                    }
                }
                default: {
                    if (mLastActiveDeviceMotionEvent != null
                            && event.getDeviceId() != mLastActiveDeviceMotionEvent.getDeviceId()) {
                        // This is an event from another device, ignore it.
                        return false;
                    }
                }
            }
        }
        return true;
    }

    private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
        if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
            super.onInputEvent(event, policyFlags);
+21 −2
Original line number Diff line number Diff line
@@ -106,11 +106,30 @@ class EventDispatcher {
                return;
            }
        }
        final long downTime;
        if (action == MotionEvent.ACTION_DOWN) {
            event.setDownTime(event.getEventTime());
            downTime = event.getEventTime();
        } else {
            event.setDownTime(mState.getLastInjectedDownEventTime());
        }
            downTime = mState.getLastInjectedDownEventTime();
        }

        // The only way to change device id of the motion event is by re-creating the whole thing
        final PointerProperties[] properties = new PointerProperties[event.getPointerCount()];
        final PointerCoords[] coords = new PointerCoords[event.getPointerCount()];
        for (int i = 0; i < event.getPointerCount(); i++) {
            final PointerCoords c = new PointerCoords();
            event.getPointerCoords(i, c);
            coords[i] = c;
            final PointerProperties p = new PointerProperties();
            event.getPointerProperties(i, p);
            properties[i] = p;
        }
        event = MotionEvent.obtain(downTime, event.getEventTime(), event.getAction(),
                event.getPointerCount(), properties, coords,
                event.getMetaState(), event.getButtonState(),
                event.getXPrecision(), event.getYPrecision(), rawEvent.getDeviceId(),
                event.getEdgeFlags(), rawEvent.getSource(), event.getDisplayId(), event.getFlags(),
                event.getClassification());
        // If the user is long pressing but the long pressing pointer
        // was not exactly over the accessibility focused item we need
        // to remap the location of that pointer so the user does not
+265 −52

File changed.

Preview size limit exceeded, changes collapsed.