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

Commit 02bd8c6c authored by Katie's avatar Katie
Browse files

[A11y] Adds ability for magnifier to follow mouse and stylus

Lets magnifier follow mouse and stylus hover moves and mouse
moves, when flag is enabled. No behavior change when flag
is disabled. Mouse following is "continuous" mode, where the
mouse traverses across the screen as the viewport moves from
edge to edge.

Bug: b/354696546
Test: Manual in emulator; new automated tests
Flag: com.android.server.accessibility.enable_magnification_follows_mouse
Change-Id: Ic6080224e0ae1bfb5e8441820612be68842fc1ea
parent 8c1b8116
Loading
Loading
Loading
Loading
+17 −9
Original line number Diff line number Diff line
@@ -45,9 +45,11 @@ import android.view.accessibility.AccessibilityEvent;

import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.MouseEventHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
import com.android.server.policy.WindowManagerPolicy;
@@ -864,15 +866,21 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
                    TYPE_MAGNIFICATION_OVERLAY, null /* options */);
            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper =
                    new FullScreenMagnificationVibrationHelper(uiContext);
            magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext,
                    mAms.getMagnificationController().getFullScreenMagnificationController(),
            FullScreenMagnificationController controller =
                    mAms.getMagnificationController().getFullScreenMagnificationController();
            magnificationGestureHandler =
                    new FullScreenMagnificationGestureHandler(
                            uiContext,
                            controller,
                            mAms.getTraceManager(),
                            mAms.getMagnificationController(),
                            detectControlGestures,
                            detectTwoFingerTripleTap,
                            triggerable,
                    new WindowMagnificationPromptController(displayContext, mUserId), displayId,
                    fullScreenMagnificationVibrationHelper);
                            new WindowMagnificationPromptController(displayContext, mUserId),
                            displayId,
                            fullScreenMagnificationVibrationHelper,
                            new MouseEventHandler(controller));
        }
        return magnificationGestureHandler;
    }
+38 −14
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.accessibility.magnification;

import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_STYLUS;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -183,7 +185,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
    private final int mMinimumVelocity;
    private final int mMaximumVelocity;

    public FullScreenMagnificationGestureHandler(@UiContext Context context,
    private MouseEventHandler mMouseEventHandler;

    public FullScreenMagnificationGestureHandler(
            @UiContext Context context,
            FullScreenMagnificationController fullScreenMagnificationController,
            AccessibilityTraceManager trace,
            Callback callback,
@@ -192,7 +197,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            boolean detectShortcutTrigger,
            @NonNull WindowMagnificationPromptController promptController,
            int displayId,
            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
            MouseEventHandler mouseEventHandler) {
        this(
                context,
                fullScreenMagnificationController,
@@ -207,9 +213,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                /* magnificationLogger= */ null,
                ViewConfiguration.get(context),
                new OneFingerPanningSettingsProvider(
                        context,
                        Flags.enableMagnificationOneFingerPanningGesture()
                ));
                        context, Flags.enableMagnificationOneFingerPanningGesture()),
                mouseEventHandler);
    }

    /** Constructor for tests. */
@@ -227,8 +232,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
            FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
            MagnificationLogger magnificationLogger,
            ViewConfiguration viewConfiguration,
            OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider
    ) {
            OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider,
            MouseEventHandler mouseEventHandler) {
        super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
                detectShortcutTrigger, trace, callback);
        if (DEBUG_ALL) {
@@ -318,6 +323,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
        mOverscrollEdgeSlop = context.getResources().getDimensionPixelSize(
                R.dimen.accessibility_fullscreen_magnification_gesture_edge_slop);
        mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
        mMouseEventHandler = mouseEventHandler;

        if (mDetectShortcutTrigger) {
            mScreenStateReceiver = new ScreenStateReceiver(context, this);
@@ -331,15 +337,28 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

    @Override
    void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (event.getSource() == SOURCE_TOUCHSCREEN) {
            if (event.getActionMasked() == ACTION_DOWN) {
                cancelFling();
            }
            handleTouchEventWith(mCurrentState, event, rawEvent, policyFlags);
        } else if (Flags.enableMagnificationFollowsMouse()
                && (event.getSource() == SOURCE_MOUSE || event.getSource() == SOURCE_STYLUS)) {
            if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
                // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
                // over, rather than only interacting with the current display.

        handleEventWith(mCurrentState, event, rawEvent, policyFlags);
                // Send through the mouse/stylus event handler.
                mMouseEventHandler.onEvent(event, mDisplayId);
            }
            // Dispatch to normal event handling flow.
            dispatchTransformedEvent(event, rawEvent, policyFlags);
        }
    }

    private void handleTouchEventWith(
            State stateHandler, MotionEvent event, MotionEvent rawEvent, int policyFlags) {

    private void handleEventWith(State stateHandler,
            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        // To keep InputEventConsistencyVerifiers within GestureDetectors happy
        mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
        mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
@@ -1421,6 +1440,11 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH

        protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
                int policyFlags) {
            if (Flags.enableMagnificationFollowsMouse()
                    && !event.isFromSource(SOURCE_TOUCHSCREEN)) {
                // Only touch events need to be cached and sent later.
                return;
            }
            if (event.getActionMasked() == ACTION_DOWN) {
                mPreLastDown = mLastDown;
                mLastDown = MotionEvent.obtain(event);
@@ -1458,7 +1482,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
                mDelayedEventQueue = info.mNext;

                info.event.setDownTime(info.event.getDownTime() + offset);
                handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
                handleTouchEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);

                info.recycle();
            } while (mDelayedEventQueue != null);
+29 −4
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.accessibility.magnification;

import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_STYLUS;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_UP;
@@ -139,13 +141,36 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo
        }
    }

    /**
     * Some touchscreen, mouse and stylus events may modify magnifier state. Checks for whether the
     * event should not be dispatched to the magnifier.
     *
     * @param event The event to check.
     * @return `true` if the event should be sent through the normal event flow or `false` if it
     *     should be observed by magnifier.
     */
    private boolean shouldDispatchTransformedEvent(MotionEvent event) {
        if ((!mDetectSingleFingerTripleTap && !mDetectTwoFingerTripleTap && !mDetectShortcutTrigger)
                || !event.isFromSource(SOURCE_TOUCHSCREEN)) {
            return true;
        if (event.getSource() == SOURCE_TOUCHSCREEN) {
            if (mDetectSingleFingerTripleTap
                    || mDetectTwoFingerTripleTap
                    || mDetectShortcutTrigger) {
                // Observe touchscreen events while magnification activation is detected.
                return false;
            }
        }
        if (Flags.enableMagnificationFollowsMouse()) {
            if (event.isFromSource(SOURCE_MOUSE) || event.isFromSource(SOURCE_STYLUS)) {
                // Note that mouse events include other mouse-like pointing devices
                // such as touchpads and pointing sticks.
                // Observe any mouse or stylus movement.
                // We observe all movement to ensure that events continue to come in order,
                // even though only some movement types actually move the viewport.
                return false;
            }
        }
        // Magnification dispatches (ignores) all other events
        return true;
    }

    final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (DEBUG_EVENT_STREAM) {
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.accessibility.magnification;

import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;

import android.view.MotionEvent;

import com.android.server.accessibility.AccessibilityManagerService;

/** MouseEventHandler handles mouse and stylus events that should move the viewport. */
public final class MouseEventHandler {
    private final FullScreenMagnificationController mFullScreenMagnificationController;

    public MouseEventHandler(FullScreenMagnificationController fullScreenMagnificationController) {
        mFullScreenMagnificationController = fullScreenMagnificationController;
    }

    /**
     * Handles a mouse or stylus event, moving the magnifier if needed.
     *
     * @param event The mouse or stylus MotionEvent to consume
     * @param displayId The display that is being magnified
     */
    public void onEvent(MotionEvent event, int displayId) {
        if (event.getAction() == ACTION_HOVER_MOVE
                || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE)) {
            final float eventX = event.getX();
            final float eventY = event.getY();

            // Only move the viewport when over a magnified region.
            // TODO(b/354696546): Ensure this doesn't stop the viewport from reaching the
            // corners and edges at high levels of magnification.
            if (mFullScreenMagnificationController.magnificationRegionContains(
                    displayId, eventX, eventY)) {
                mFullScreenMagnificationController.setCenter(
                        displayId,
                        eventX,
                        eventY,
                        /* animate= */ false,
                        AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
            }
        }
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -148,6 +148,10 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl

    @Override
    void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (event.getSource() != SOURCE_TOUCHSCREEN) {
            // Window Magnification viewport doesn't move with mouse events (yet).
            return;
        }
        // To keep InputEventConsistencyVerifiers within GestureDetectors happy.
        mObservePanningScalingState.mPanningScalingHandler.onTouchEvent(event);
        mCurrentState.onMotionEvent(event, rawEvent, policyFlags);
Loading