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

Commit 8b729932 authored by Jackal Guo's avatar Jackal Guo
Browse files

Support A11yInputFilter on multi-display

In order to support multi-display, some event handlers need to be
plural. Based on the characteristic of each event handler, only
the event handler that needs to deal with concurrent events from
different display needs multi-instance.

Test: a11y CTS & unit tests
Test: atest SystemUITests
Change-Id: I76379fb82aad09ee20609d81bd5b1dda15931905
parent bfda3a93
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -1166,9 +1166,10 @@ public final class AccessibilityManager {
    /**
     * Notifies that the accessibility button in the system's navigation area has been clicked
     *
     * @param displayId The logical display id.
     * @hide
     */
    public void notifyAccessibilityButtonClicked() {
    public void notifyAccessibilityButtonClicked(int displayId) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
@@ -1177,7 +1178,7 @@ public final class AccessibilityManager {
            }
        }
        try {
            service.notifyAccessibilityButtonClicked();
            service.notifyAccessibilityButtonClicked(displayId);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
        }
+1 −1
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ interface IAccessibilityManager {

    IBinder getWindowToken(int windowId, int userId);

    void notifyAccessibilityButtonClicked();
    void notifyAccessibilityButtonClicked(int displayId);

    void notifyAccessibilityButtonVisibilityChanged(boolean available);

+3 −1
Original line number Diff line number Diff line
@@ -799,7 +799,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
    }

    private void onAccessibilityClick(View v) {
        mAccessibilityManager.notifyAccessibilityButtonClicked();
        final Display display = v.getDisplay();
        mAccessibilityManager.notifyAccessibilityButtonClicked(
                display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY);
    }

    private boolean onAccessibilityLongClick(View v) {
+163 −84
Original line number Diff line number Diff line
@@ -17,13 +17,11 @@
package com.android.server.accessibility;

import android.content.Context;
import android.os.Handler;
import android.os.PowerManager;
import android.util.DebugUtils;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputFilter;
@@ -31,10 +29,11 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;

import com.android.internal.util.BitUtils;
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;

import java.util.ArrayList;

/**
 * This class is an input filter for implementing accessibility features such
 * as display magnification and explore by touch.
@@ -108,23 +107,24 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo

    private final AccessibilityManagerService mAms;

    private boolean mInstalled;

    private int mUserId;

    private int mEnabledFeatures;
    private final SparseArray<EventStreamTransformation> mEventHandler;

    private TouchExplorer mTouchExplorer;
    private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);

    private MagnificationGestureHandler mMagnificationGestureHandler;
    private final SparseArray<MagnificationGestureHandler> mMagnificationGestureHandler =
            new SparseArray<>(0);

    private MotionEventInjector mMotionEventInjector;
    private final SparseArray<MotionEventInjector> mMotionEventInjector = new SparseArray<>(0);

    private AutoclickController mAutoclickController;

    private KeyboardInterceptor mKeyboardInterceptor;

    private EventStreamTransformation mEventHandler;
    private boolean mInstalled;

    private int mUserId;

    private int mEnabledFeatures;

    private EventStreamState mMouseStreamState;

@@ -133,10 +133,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
    private EventStreamState mKeyboardStreamState;

    AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
        this(context, service, new SparseArray<>(0));
    }

    AccessibilityInputFilter(Context context, AccessibilityManagerService service,
            SparseArray<EventStreamTransformation> eventHandler) {
        super(context.getMainLooper());
        mContext = context;
        mAms = service;
        mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mEventHandler = eventHandler;
    }

    @Override
@@ -160,6 +166,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
        super.onUninstalled();
    }

    void onDisplayChanged() {
        if (mInstalled) {
            disableFeatures();
            enableFeatures();
        }
    }

    @Override
    public void onInputEvent(InputEvent event, int policyFlags) {
        if (DEBUG) {
@@ -167,8 +180,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
                    + Integer.toHexString(policyFlags));
        }

        if (mEventHandler == null) {
            if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event);
        if (mEventHandler.size() == 0) {
            if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
            super.onInputEvent(event, policyFlags);
            return;
        }
@@ -182,16 +195,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
        int eventSource = event.getSource();
        if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
            state.reset();
            mEventHandler.clearEvents(eventSource);
            clearEventsForAllEventHandlers(eventSource);
            super.onInputEvent(event, policyFlags);
            return;
        }

        if (state.updateDeviceId(event.getDeviceId())) {
            mEventHandler.clearEvents(eventSource);
        if (state.updateInputSource(event.getSource())) {
            clearEventsForAllEventHandlers(eventSource);
        }

        if (!state.deviceIdValid()) {
        if (!state.inputSourceValid()) {
            super.onInputEvent(event, policyFlags);
            return;
        }
@@ -240,6 +253,15 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
        return null;
    }

    private void clearEventsForAllEventHandlers(int eventSource) {
        for (int i = 0; i < mEventHandler.size(); i++) {
            final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
            if (eventHandler != null) {
                eventHandler.clearEvents(eventSource);
            }
        }
    }

    private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
        if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
            super.onInputEvent(event, policyFlags);
@@ -258,7 +280,10 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
            super.onInputEvent(event, policyFlags);
            return;
        }
        mEventHandler.onKeyEvent(event, policyFlags);
        // Since the display id of KeyEvent always would be -1 and there is only one
        // KeyboardInterceptor for all display, pass KeyEvent to the mEventHandler of
        // DEFAULT_DISPLAY to handle.
        mEventHandler.get(Display.DEFAULT_DISPLAY).onKeyEvent(event, policyFlags);
    }

    private void handleMotionEvent(MotionEvent event, int policyFlags) {
@@ -267,10 +292,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
        }
        mPm.userActivity(event.getEventTime(), false);
        MotionEvent transformedEvent = MotionEvent.obtain(event);
        mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
        final int displayId = event.getDisplayId();
        mEventHandler.get(isDisplayIdValid(displayId) ? displayId : Display.DEFAULT_DISPLAY)
                .onMotionEvent(transformedEvent, event, policyFlags);
        transformedEvent.recycle();
    }

    private boolean isDisplayIdValid(int displayId) {
        return mEventHandler.get(displayId) != null;
    }

    @Override
    public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
            int policyFlags) {
@@ -323,14 +354,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
    }

    void notifyAccessibilityEvent(AccessibilityEvent event) {
        if (mEventHandler != null) {
            mEventHandler.onAccessibilityEvent(event);
        for (int i = 0; i < mEventHandler.size(); i++) {
            final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
            if (eventHandler != null) {
                eventHandler.onAccessibilityEvent(event);
            }
        }
    }

    void notifyAccessibilityButtonClicked() {
        if (mMagnificationGestureHandler != null) {
            mMagnificationGestureHandler.notifyShortcutTriggered();
    void notifyAccessibilityButtonClicked(int displayId) {
        if (mMagnificationGestureHandler.size() != 0) {
            final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
            if (handler != null) {
                handler.notifyShortcutTriggered();
            }
        }
    }

@@ -339,14 +376,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo

        resetStreamState();

        final ArrayList<Display> displaysList = mAms.getValidDisplayList();

        if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
            mAutoclickController = new AutoclickController(mContext, mUserId);
            addFirstEventHandler(mAutoclickController);
            addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController);
        }

        for (int i = displaysList.size() - 1; i >= 0; i--) {
            final int displayId = displaysList.get(i).getDisplayId();

            if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
            mTouchExplorer = new TouchExplorer(mContext, mAms);
            addFirstEventHandler(mTouchExplorer);
                TouchExplorer explorer = new TouchExplorer(mContext, mAms);
                addFirstEventHandler(displayId, explorer);
                mTouchExplorer.put(displayId, explorer);
            }

            if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
@@ -356,64 +399,101 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
                        & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0;
                final boolean triggerable = (mEnabledFeatures
                        & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0;
            mMagnificationGestureHandler = new MagnificationGestureHandler(
                    mContext, mAms.getMagnificationController(),
                MagnificationGestureHandler magnificationGestureHandler =
                        new MagnificationGestureHandler(mContext,
                                mAms.getMagnificationController(),
                                detectControlGestures, triggerable);
            addFirstEventHandler(mMagnificationGestureHandler);
                addFirstEventHandler(displayId, magnificationGestureHandler);
                mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
            }

            if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
            mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper());
            addFirstEventHandler(mMotionEventInjector);
            mAms.setMotionEventInjector(mMotionEventInjector);
                MotionEventInjector injector = new MotionEventInjector(
                        mContext.getMainLooper());
                addFirstEventHandler(displayId, injector);
                // TODO: Need to set MotionEventInjector per display.
                mAms.setMotionEventInjector(injector);
                mMotionEventInjector.put(displayId, injector);
            }
        }

        if ((mEnabledFeatures & FLAG_FEATURE_FILTER_KEY_EVENTS) != 0) {
            mKeyboardInterceptor = new KeyboardInterceptor(mAms,
                    LocalServices.getService(WindowManagerPolicy.class));
            addFirstEventHandler(mKeyboardInterceptor);
            // Since the display id of KeyEvent always would be -1 and it would be dispatched to
            // the display with input focus directly, we only need one KeyboardInterceptor for
            // default display.
            addFirstEventHandler(Display.DEFAULT_DISPLAY, mKeyboardInterceptor);
        }
    }

    /**
     * Adds an event handler to the event handler chain. The handler is added at the beginning of
     * the chain.
     * Adds an event handler to the event handler chain for giving display. The handler is added at
     * the beginning of the chain.
     *
     * @param displayId The logical display id.
     * @param handler The handler to be added to the event handlers list.
     */
    private void addFirstEventHandler(EventStreamTransformation handler) {
        if (mEventHandler != null) {
            handler.setNext(mEventHandler);
    private void addFirstEventHandler(int displayId, EventStreamTransformation handler) {
        EventStreamTransformation eventHandler = mEventHandler.get(displayId);
        if (eventHandler != null) {
            handler.setNext(eventHandler);
        } else {
            handler.setNext(this);
        }
        mEventHandler = handler;
        eventHandler = handler;
        mEventHandler.put(displayId, eventHandler);
    }

    /**
     * Adds an event handler to the event handler chain for all displays. The handler is added at
     * the beginning of the chain.
     *
     * @param displayList The list of displays
     * @param handler The handler to be added to the event handlers list.
     */
    private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
            EventStreamTransformation handler) {
        for (int i = 0; i < displayList.size(); i++) {
            final int displayId = displayList.get(i).getDisplayId();
            addFirstEventHandler(displayId, handler);
        }
    }

    private void disableFeatures() {
        if (mMotionEventInjector != null) {
        for (int i = mMotionEventInjector.size() - 1; i >= 0; i--) {
            final MotionEventInjector injector = mMotionEventInjector.valueAt(i);
            // TODO: Need to set MotionEventInjector per display.
            mAms.setMotionEventInjector(null);
            mMotionEventInjector.onDestroy();
            mMotionEventInjector = null;
            if (injector != null) {
                injector.onDestroy();
            }
        }
        mMotionEventInjector.clear();
        if (mAutoclickController != null) {
            mAutoclickController.onDestroy();
            mAutoclickController = null;
        }
        if (mTouchExplorer != null) {
            mTouchExplorer.onDestroy();
            mTouchExplorer = null;
        for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
            final TouchExplorer explorer = mTouchExplorer.valueAt(i);
            if (explorer != null) {
                explorer.onDestroy();
            }
        }
        if (mMagnificationGestureHandler != null) {
            mMagnificationGestureHandler.onDestroy();
            mMagnificationGestureHandler = null;
        mTouchExplorer.clear();
        for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
            final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
            if (handler != null) {
                handler.onDestroy();
            }
        }
        mMagnificationGestureHandler.clear();
        if (mKeyboardInterceptor != null) {
            mKeyboardInterceptor.onDestroy();
            mKeyboardInterceptor = null;
        }

        mEventHandler = null;
        mEventHandler.clear();
        resetStreamState();
    }

@@ -441,41 +521,41 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
     * whose events should not be handled by a11y event stream transformations.
     */
    private static class EventStreamState {
        private int mDeviceId;
        private int mSource;

        EventStreamState() {
            mDeviceId = -1;
            mSource = -1;
        }

        /**
         * Updates the ID of the device associated with the state. If the ID changes, resets
         * internal state.
         * Updates the input source of the device associated with the state. If the source changes,
         * resets internal state.
         *
         * @param deviceId Updated input device ID.
         * @return Whether the device ID has changed.
         * @param source Updated input source.
         * @return Whether the input source has changed.
         */
        public boolean updateDeviceId(int deviceId) {
            if (mDeviceId == deviceId) {
        public boolean updateInputSource(int source) {
            if (mSource == source) {
                return false;
            }
            // Reset clears internal state, so make sure it's called before |mDeviceId| is updated.
            // Reset clears internal state, so make sure it's called before |mSource| is updated.
            reset();
            mDeviceId = deviceId;
            mSource = source;
            return true;
        }

        /**
         * @return Whether device ID is valid.
         * @return Whether input source is valid.
         */
        public boolean deviceIdValid() {
            return mDeviceId >= 0;
        public boolean inputSourceValid() {
            return mSource >= 0;
        }

        /**
         * Resets the event stream state.
         */
        public void reset() {
            mDeviceId = -1;
            mSource = -1;
        }

        /**
@@ -592,20 +672,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo

        /*
         * Key events from different devices may be interleaved. For example, the volume up and
         * down keys can come from different device IDs.
         * down keys can come from different input sources.
         */
        @Override
        public boolean updateDeviceId(int deviceId) {
        public boolean updateInputSource(int deviceId) {
            return false;
        }

        // We manage all device ids simultaneously; there is no concept of validity.
        // We manage all input source simultaneously; there is no concept of validity.
        @Override
        public boolean deviceIdValid() {
        public boolean inputSourceValid() {
            return true;
        }


        @Override
        final public boolean shouldProcessKeyEvent(KeyEvent event) {
            // For each keyboard device, wait for a down event from a device to start processing
+121 −58

File changed.

Preview size limit exceeded, changes collapsed.

Loading