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

Commit 5a2c7673 authored by Hiroki Sato's avatar Hiroki Sato
Browse files

Enlarge pointer icon for magnification

When full screen magnification is enabled, pointer icons should be also
enlarged by the zoom factor.

This change adds an API in InputManagerInternal for accessibility to
update the scale factor when full screen magnification is enabled.

Currently there's only one caller, but it will be called from more
places in future changes.

Bug: 355734856
Test: Enable flag, and changing scale with slider or gesture will change pointer icons.
Test: PointerIconCacheTest
Test: atest com.android.server.accessibility.magnification
Flag: com.android.server.accessibility.magnification_enlarge_pointer

Change-Id: I985183b12d6a1c4a6aa4c53e4f462778aeb2e9ae
parent 011c1d3c
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.Flags;
import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;

import java.util.ArrayList;
@@ -955,6 +956,7 @@ public class FullScreenMagnificationController implements
                        context,
                        traceManager,
                        LocalServices.getService(WindowManagerInternal.class),
                        LocalServices.getService(InputManagerInternal.class),
                        new Handler(context.getMainLooper()),
                        context.getResources().getInteger(R.integer.config_longAnimTime)),
                lock,
@@ -1640,6 +1642,8 @@ public class FullScreenMagnificationController implements
     */
    public void persistScale(int displayId) {
        final float scale = getScale(displayId);
        notifyScaleForInput(displayId, scale);

        if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
            return;
        }
@@ -1690,6 +1694,20 @@ public class FullScreenMagnificationController implements
        }
    }

    /**
     * Notifies input manager that magnification scale changed non-transiently
     * so that pointer cursor is scaled as well.
     *
     * @param displayId The logical display id.
     * @param scale     The new scale factor.
     */
    public void notifyScaleForInput(int displayId, float scale) {
        if (Flags.magnificationEnlargePointer()) {
            mControllerCtx.getInputManager()
                    .setAccessibilityPointerIconScaleFactor(displayId, scale);
        }
    }

    /**
     * Resets all displays' magnification if last magnifying service is disabled.
     *
@@ -2166,6 +2184,7 @@ public class FullScreenMagnificationController implements
        private final Context mContext;
        private final AccessibilityTraceManager mTrace;
        private final WindowManagerInternal mWindowManager;
        private final InputManagerInternal mInputManager;
        private final Handler mHandler;
        private final Long mAnimationDuration;

@@ -2175,11 +2194,13 @@ public class FullScreenMagnificationController implements
        public ControllerContext(@NonNull Context context,
                @NonNull AccessibilityTraceManager traceManager,
                @NonNull WindowManagerInternal windowManager,
                @NonNull InputManagerInternal inputManager,
                @NonNull Handler handler,
                long animationDuration) {
            mContext = context;
            mTrace = traceManager;
            mWindowManager = windowManager;
            mInputManager = inputManager;
            mHandler = handler;
            mAnimationDuration = animationDuration;
        }
@@ -2208,6 +2229,14 @@ public class FullScreenMagnificationController implements
            return mWindowManager;
        }

        /**
         * @return InputManagerInternal
         */
        @NonNull
        public InputManagerInternal getInputManager() {
            return mInputManager;
        }

        /**
         * @return Handler for main looper
         */
+8 −0
Original line number Diff line number Diff line
@@ -262,4 +262,12 @@ public abstract class InputManagerInternal {
     */
    public abstract void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes,
            int modifierState, @KeyGestureEvent.KeyGestureType int event);

    /**
     * Sets the magnification scale factor for pointer icons.
     *
     * @param displayId   the ID of the display where the new scale factor is applied.
     * @param scaleFactor the new scale factor to be applied for pointer icons.
     */
    public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor);
}
+9 −0
Original line number Diff line number Diff line
@@ -3506,6 +3506,11 @@ public class InputManagerService extends IInputManager.Stub
                int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) {
            mKeyGestureController.handleKeyGesture(deviceId, keycodes, modifierState, gestureType);
        }

        @Override
        public void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
            InputManagerService.this.setAccessibilityPointerIconScaleFactor(displayId, scaleFactor);
        }
    }

    @Override
@@ -3688,6 +3693,10 @@ public class InputManagerService extends IInputManager.Stub
        mPointerIconCache.setPointerScale(scale);
    }

    void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor) {
        mPointerIconCache.setAccessibilityScaleFactor(displayId, scaleFactor);
    }

    interface KeyboardBacklightControllerInterface {
        default void incrementKeyboardBacklight(int deviceId) {}
        default void decrementKeyboardBacklight(int deviceId) {}
+35 −2
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseDoubleArray;
import android.util.SparseIntArray;
import android.view.ContextThemeWrapper;
import android.view.Display;
@@ -34,6 +35,7 @@ import android.view.DisplayInfo;
import android.view.PointerIcon;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.UiThread;

import java.util.Objects;
@@ -51,7 +53,7 @@ final class PointerIconCache {
    private final NativeInputManagerService mNative;

    // We use the UI thread for loading pointer icons.
    private final Handler mUiThreadHandler = UiThread.getHandler();
    private final Handler mUiThreadHandler;

    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
    private final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
@@ -70,6 +72,9 @@ final class PointerIconCache {
            POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
    private float mPointerIconScale = DEFAULT_POINTER_SCALE;
    // Note that android doesn't have SparseFloatArray, so this falls back to use double instead.
    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
    private final SparseDoubleArray mAccessibilityScaleFactorPerDisplay = new SparseDoubleArray();

    private final DisplayManager.DisplayListener mDisplayListener =
            new DisplayManager.DisplayListener() {
@@ -86,6 +91,7 @@ final class PointerIconCache {
                        mLoadedPointerIconsByDisplayAndType.remove(displayId);
                        mDisplayContexts.remove(displayId);
                        mDisplayDensities.delete(displayId);
                        mAccessibilityScaleFactorPerDisplay.delete(displayId);
                    }
                }

@@ -96,8 +102,15 @@ final class PointerIconCache {
            };

    /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService) {
        this(context, nativeService, UiThread.getHandler());
    }

    @VisibleForTesting
    /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService,
            Handler handler) {
        mContext = context;
        mNative = nativeService;
        mUiThreadHandler = handler;
    }

    public void systemRunning() {
@@ -134,6 +147,11 @@ final class PointerIconCache {
        mUiThreadHandler.post(() -> handleSetPointerScale(scale));
    }

    /** Set the scale for accessibility (magnification) for vector pointer icons. */
    public void setAccessibilityScaleFactor(int displayId, float scaleFactor) {
        mUiThreadHandler.post(() -> handleAccessibilityScaleFactor(displayId, scaleFactor));
    }

    /**
     * Get a loaded system pointer icon. This will fetch the icon from the cache, or load it if
     * it isn't already cached.
@@ -155,8 +173,10 @@ final class PointerIconCache {
                        /* force= */ true);
                theme.applyStyle(PointerIcon.vectorStrokeStyleToResource(mPointerIconStrokeStyle),
                        /* force= */ true);
                final float scale = mPointerIconScale
                        * (float) mAccessibilityScaleFactorPerDisplay.get(displayId, 1f);
                icon = PointerIcon.getLoadedSystemIcon(new ContextThemeWrapper(context, theme),
                        type, mUseLargePointerIcons, mPointerIconScale);
                        type, mUseLargePointerIcons, scale);
                iconsByType.put(type, icon);
            }
            return Objects.requireNonNull(icon);
@@ -261,6 +281,19 @@ final class PointerIconCache {
        mNative.reloadPointerIcons();
    }

    @android.annotation.UiThread
    private void handleAccessibilityScaleFactor(int displayId, float scale) {
        synchronized (mLoadedPointerIconsByDisplayAndType) {
            if (mAccessibilityScaleFactorPerDisplay.get(displayId, 1f) == scale) {
                return;
            }
            mAccessibilityScaleFactorPerDisplay.put(displayId, scale);
            // Clear cached icons on the display.
            mLoadedPointerIconsByDisplayAndType.remove(displayId);
        }
        mNative.reloadPointerIcons();
    }

    // Updates the cached display density for the given displayId, and returns true if
    // the cached density changed.
    @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+21 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.accessibility.magnification;

import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;

import static com.android.server.accessibility.Flags.FLAG_MAGNIFICATION_ENLARGE_POINTER;
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
@@ -76,6 +77,7 @@ import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.Flags;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;

@@ -126,6 +128,7 @@ public class FullScreenMagnificationControllerTest {
    final Resources mMockResources = mock(Resources.class);
    final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
    final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
    final InputManagerInternal mMockInputManager = mock(InputManagerInternal.class);
    private final MagnificationAnimationCallback mAnimationCallback = mock(
            MagnificationAnimationCallback.class);
    private final MagnificationInfoChangedCallback mRequestObserver = mock(
@@ -163,6 +166,7 @@ public class FullScreenMagnificationControllerTest {
        when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
        when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
        when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
        when(mMockControllerCtx.getInputManager()).thenReturn(mMockInputManager);
        when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
        when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
        mResolver = new MockContentResolver();
@@ -1478,6 +1482,23 @@ public class FullScreenMagnificationControllerTest {
                4.0f);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_MAGNIFICATION_ENLARGE_POINTER)
    public void persistScale_setValue_notifyInput() {
        register(TEST_DISPLAY);

        PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
        mFullScreenMagnificationController.setScale(TEST_DISPLAY, 4.0f, pivotPoint.x, pivotPoint.y,
                true, SERVICE_ID_1);
        mFullScreenMagnificationController.persistScale(TEST_DISPLAY);

        // persistScale may post a task to a background thread. Let's wait for it completes.
        waitForBackgroundThread();
        Assert.assertEquals(mFullScreenMagnificationController.getPersistedScale(TEST_DISPLAY),
                4.0f);
        verify(mMockInputManager).setAccessibilityPointerIconScaleFactor(TEST_DISPLAY, 4.0f);
    }

    @Test
    public void testOnContextChanged_alwaysOnFeatureDisabled_resetMagnification() {
        setScaleToMagnifying();
Loading