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

Commit a275d995 authored by Jackal Guo's avatar Jackal Guo Committed by Android (Google) Code Review
Browse files

Merge "Support A11yInputFilter on multi-display"

parents d1619e86 8b729932
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
@@ -802,7 +802,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