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

Commit 173c1d15 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Continuous zooming/panning while Magnification shortcuts are pressed" into main

parents ea9fa39f 0b0ebe54
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -607,7 +607,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                mLock,
                mContext,
                new MagnificationScaleProvider(mContext),
                Executors.newSingleThreadExecutor()
                Executors.newSingleThreadExecutor(),
                mContext.getMainLooper()
        );
        mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
        mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
+65 −8
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -53,6 +55,7 @@ import android.view.accessibility.MagnificationAnimationCallback;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.wm.WindowManagerInternal;
@@ -111,6 +114,20 @@ public class MagnificationController implements MagnificationConnectionManager.C

    private final Executor mBackgroundExecutor;

    private final Handler mHandler;
    private @PanDirection int mActivePanDirection = PAN_DIRECTION_DOWN;
    private int mActivePanDisplay = Display.INVALID_DISPLAY;
    private boolean mRepeatKeysEnabled = true;

    private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN;
    private int mActiveZoomDisplay = Display.INVALID_DISPLAY;

    // TODO(b/355499907): Get initial repeat interval from repeat keys settings.
    @VisibleForTesting
    public static final int INITIAL_KEYBOARD_REPEAT_INTERVAL_MS = 500;
    @VisibleForTesting
    public static final int KEYBOARD_REPEAT_INTERVAL_MS = 60;

    @GuardedBy("mLock")
    private final SparseIntArray mCurrentMagnificationModeArray = new SparseIntArray();
    @GuardedBy("mLock")
@@ -287,12 +304,13 @@ public class MagnificationController implements MagnificationConnectionManager.C

    public MagnificationController(AccessibilityManagerService ams, Object lock,
            Context context, MagnificationScaleProvider scaleProvider,
            Executor backgroundExecutor) {
            Executor backgroundExecutor, Looper looper) {
        mAms = ams;
        mLock = lock;
        mContext = context;
        mScaleProvider = scaleProvider;
        mBackgroundExecutor = backgroundExecutor;
        mHandler = new Handler(looper);
        LocalServices.getService(WindowManagerInternal.class)
                .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
        mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
@@ -303,14 +321,20 @@ public class MagnificationController implements MagnificationConnectionManager.C
        mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
        mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
                mBackgroundExecutor, mAms::updateAlwaysOnMagnification);

        // TODO(b/355499907): Add an observer for repeat keys enabled changes,
        // rather than initializing once at startup.
        mRepeatKeysEnabled = Settings.Secure.getIntForUser(
                mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_ENABLED, 1,
                UserHandle.USER_CURRENT) != 0;
    }

    @VisibleForTesting
    public MagnificationController(AccessibilityManagerService ams, Object lock,
            Context context, FullScreenMagnificationController fullScreenMagnificationController,
            MagnificationConnectionManager magnificationConnectionManager,
            MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) {
        this(ams, lock, context, scaleProvider, backgroundExecutor);
            MagnificationScaleProvider scaleProvider, Executor backgroundExecutor, Looper looper) {
        this(ams, lock, context, scaleProvider, backgroundExecutor, looper);
        mFullScreenMagnificationController = fullScreenMagnificationController;
        mMagnificationConnectionManager = magnificationConnectionManager;
    }
@@ -354,27 +378,60 @@ public class MagnificationController implements MagnificationConnectionManager.C
        // pan diagonally) by decreasing diagonal movement by sqrt(2) to make it appear the same
        // speed as non-diagonal movement.
        panMagnificationByStep(displayId, direction);
        mActivePanDirection = direction;
        mActivePanDisplay = displayId;
        if (mRepeatKeysEnabled) {
            mHandler.sendMessageDelayed(
                    PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this),
                    INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
        }
    }

    @Override
    public void onPanMagnificationStop(int displayId,
            @MagnificationController.PanDirection int direction) {
        // TODO(b/388847283): Handle held key gestures, which can be used
        // for continuous scaling and panning, until they are released.

        if (direction == mActivePanDirection) {
            mActivePanDisplay = Display.INVALID_DISPLAY;
        }
    }

    @Override
    public void onScaleMagnificationStart(int displayId,
            @MagnificationController.ZoomDirection int direction) {
        scaleMagnificationByStep(displayId, direction);
        mActiveZoomDirection = direction;
        mActiveZoomDisplay = displayId;
        if (mRepeatKeysEnabled) {
            mHandler.sendMessageDelayed(
                    PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this),
                    INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
        }
    }

    @Override
    public void onScaleMagnificationStop(int displayId,
            @MagnificationController.ZoomDirection int direction) {
        // TODO(b/388847283): Handle held key gestures, which can be used
        // for continuous scaling and panning, until they are released.
        if (direction == mActiveZoomDirection) {
            mActiveZoomDisplay = Display.INVALID_DISPLAY;
        }
    }

    private void maybeContinuePan() {
        if (mActivePanDisplay != Display.INVALID_DISPLAY) {
            panMagnificationByStep(mActivePanDisplay, mActivePanDirection);
            mHandler.sendMessageDelayed(
                    PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this),
                    KEYBOARD_REPEAT_INTERVAL_MS);
        }
    }

    private void maybeContinueZoom() {
        if (mActiveZoomDisplay != Display.INVALID_DISPLAY) {
            scaleMagnificationByStep(mActiveZoomDisplay, mActiveZoomDirection);
            mHandler.sendMessageDelayed(
                    PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this),
                    KEYBOARD_REPEAT_INTERVAL_MS);
        }
    }

    private void handleUserInteractionChanged(int displayId, int mode) {
+106 −15
Original line number Diff line number Diff line
@@ -58,9 +58,9 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.testing.DexmakerShareClassLoaderRule;
@@ -173,6 +173,8 @@ public class MagnificationControllerTest {
    @Mock
    private Scroller mMockScroller;

    private TestLooper mTestLooper;

    // To mock package-private class
    @Rule
    public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
@@ -199,14 +201,16 @@ public class MagnificationControllerTest {

        mMockResolver = new MockContentResolver();
        mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
        Looper looper = InstrumentationRegistry.getContext().getMainLooper();
        // Pretending ID of the Thread associated with looper as main thread ID in controller
        when(mContext.getMainLooper()).thenReturn(looper);
        mTestLooper = new TestLooper();
        when(mContext.getMainLooper()).thenReturn(
                InstrumentationRegistry.getContext().getMainLooper());
        when(mContext.getContentResolver()).thenReturn(mMockResolver);
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        Settings.Secure.putFloatForUser(mMockResolver,
                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE,
                CURRENT_USER_ID);
        Settings.Secure.putFloatForUser(mMockResolver, Settings.Secure.KEY_REPEAT_ENABLED, 1,
                CURRENT_USER_ID);
        mScaleProvider = spy(new MagnificationScaleProvider(mContext));

        when(mControllerCtx.getContext()).thenReturn(mContext);
@@ -251,7 +255,7 @@ public class MagnificationControllerTest {

        mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext,
                mScreenMagnificationController, mMagnificationConnectionManager, mScaleProvider,
                ConcurrentUtils.DIRECT_EXECUTOR));
                ConcurrentUtils.DIRECT_EXECUTOR, mTestLooper.getLooper()));
        mMagnificationController.setMagnificationCapabilities(
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);

@@ -261,6 +265,7 @@ public class MagnificationControllerTest {

    @After
    public void tearDown() {
        mTestLooper.dispatchAll();
        FakeSettingsProvider.clearSettingsProvider();
    }

@@ -879,6 +884,69 @@ public class MagnificationControllerTest {
                .that(numSteps).isLessThan(maxNumSteps);
    }

    @Test
    public void magnificationCallbacks_panMagnificationContinuous() throws RemoteException {
        setMagnificationEnabled(MODE_FULLSCREEN);
        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
        reset(mScreenMagnificationController);

        DisplayMetrics metrics = new DisplayMetrics();
        mDisplay.getMetrics(metrics);
        float expectedStep = 27 * metrics.density;

        float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);

        // Start moving right using keyboard callbacks.
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_RIGHT);

        float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
        expect.that(currentCenterX).isLessThan(newCenterX);
        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
        expect.that(currentCenterY).isEqualTo(newCenterY);

        currentCenterX = newCenterX;
        currentCenterY = newCenterY;

        // Wait for the initial delay to occur.
        advanceTime(MagnificationController.INITIAL_KEYBOARD_REPEAT_INTERVAL_MS + 1);

        // It should have moved again after the handler was triggered.
        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
        expect.that(currentCenterX).isLessThan(newCenterX);
        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
        expect.that(currentCenterY).isEqualTo(newCenterY);
        currentCenterX = newCenterX;
        currentCenterY = newCenterY;

        // Wait for repeat delay to occur.
        advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);

        // It should have moved a third time.
        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
        expect.that(currentCenterX).isLessThan(newCenterX);
        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
        expect.that(currentCenterY).isEqualTo(newCenterY);
        currentCenterX = newCenterX;
        currentCenterY = newCenterY;

        // Stop magnification pan.
        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_RIGHT);

        // It should not move again, even after the appropriate delay.
        advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);

        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
        expect.that(newCenterX).isEqualTo(currentCenterX);
        expect.that(newCenterY).isEqualTo(currentCenterY);
    }

    @Test
    public void enableWindowMode_notifyMagnificationChanged() throws RemoteException {
        setMagnificationEnabled(MODE_WINDOW);
@@ -1196,7 +1264,8 @@ public class MagnificationControllerTest {
        assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, lastActivatedMode);
    }

    @Test public void activateFullScreenMagnification_triggerCallback() throws RemoteException {
    @Test
    public void activateFullScreenMagnification_triggerCallback() throws RemoteException {
        setMagnificationEnabled(MODE_FULLSCREEN);
        verify(mMagnificationController).onFullScreenMagnificationActivationState(
                eq(TEST_DISPLAY), eq(true));
@@ -1573,8 +1642,8 @@ public class MagnificationControllerTest {
        float currentCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        float currentCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);

        // Move right.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        // Move right using keyboard callbacks.
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_RIGHT);
        float newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        float newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
@@ -1582,11 +1651,13 @@ public class MagnificationControllerTest {
        expect.that(newCenterX - currentCenterX).isWithin(0.01f).of(expectedStep);
        expect.that(currentCenterY).isEqualTo(newCenterY);

        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_RIGHT);
        currentCenterX = newCenterX;
        currentCenterY = newCenterY;

        // Move left.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_LEFT);
        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
@@ -1594,11 +1665,13 @@ public class MagnificationControllerTest {
        expect.that(currentCenterX - newCenterX).isWithin(0.01f).of(expectedStep);
        expect.that(currentCenterY).isEqualTo(newCenterY);

        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_LEFT);
        currentCenterX = newCenterX;
        currentCenterY = newCenterY;

        // Move down.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_DOWN);
        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
@@ -1606,17 +1679,22 @@ public class MagnificationControllerTest {
        expect.that(currentCenterY).isLessThan(newCenterY);
        expect.that(newCenterY - currentCenterY).isWithin(0.1f).of(expectedStep);

        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_DOWN);
        currentCenterX = newCenterX;
        currentCenterY = newCenterY;

        // Move up.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_UP);
        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
        expect.that(currentCenterX).isEqualTo(newCenterX);
        expect.that(currentCenterY).isGreaterThan(newCenterY);
        expect.that(currentCenterY - newCenterY).isWithin(0.01f).of(expectedStep);

        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_UP);
    }

    private void testWindowMagnificationPanWithStepSize(float expectedStepDip)
@@ -1626,28 +1704,41 @@ public class MagnificationControllerTest {
        final float expectedStep = expectedStepDip * metrics.density;

        // Move right.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_RIGHT);
        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
                floatThat(step -> Math.abs(step - expectedStep) < 0.0001), eq(0.0f));
        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_RIGHT);

        // Move left.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_LEFT);
        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
                floatThat(step -> Math.abs(expectedStep - step) < 0.0001), eq(0.0f));
        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_LEFT);

        // Move down.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_DOWN);
        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
                eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001));
        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_DOWN);

        // Move up.
        mMagnificationController.panMagnificationByStep(TEST_DISPLAY,
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_UP);
        verify(mMockConnection.getConnection()).moveWindowMagnifier(eq(TEST_DISPLAY),
                eq(0.0f), floatThat(step -> Math.abs(expectedStep - step) < 0.0001));
        mMagnificationController.onPanMagnificationStop(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_UP);
    }

    private void advanceTime(long timeMs) {
        mTestLooper.moveTimeForward(timeMs);
        mTestLooper.dispatchAll();
    }

    private static class WindowMagnificationMgrCallbackDelegate implements