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

Commit 4b515bb2 authored by Katie Dektar's avatar Katie Dektar
Browse files

Magnification continuous zooming/panning uses repeat keys settings

Magnification allows continuous zoom/pan control only when repeat
keys is enabled, and matches the initial timeout before behavior
repeats to repeat keys timeout.

Test: atest MagnificationControllerTest, AccessibilityManagerServiceTest, manual
Bug: b/388847283
Flag: com.android.server.accessibility.enable_magnification_keyboard_control

Change-Id: I90347125dca7beece53dad3a65469869e9084360
parent d8c4e73b
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROA
import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.hardware.input.InputSettings.isRepeatKeysFeatureFlagEnabled;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
@@ -156,6 +157,7 @@ import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
@@ -3494,6 +3496,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        somethingChanged |= readMagnificationFollowTypingLocked(userState);
        somethingChanged |= readAlwaysOnMagnificationLocked(userState);
        somethingChanged |= readMouseKeysEnabledLocked(userState);
        somethingChanged |= readRepeatKeysSettingsLocked(userState);
        return somethingChanged;
    }

@@ -5771,6 +5774,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        private final Uri mUserSetupCompleteUri = Settings.Secure.getUriFor(
                Settings.Secure.USER_SETUP_COMPLETE);

        private final Uri mRepeatKeysEnabledUri = Settings.Secure.getUriFor(
                Settings.Secure.KEY_REPEAT_ENABLED);

        private final Uri mRepeatKeysTimeoutMsUri = Settings.Secure.getUriFor(
                Settings.Secure.KEY_REPEAT_TIMEOUT_MS);

        public AccessibilityContentObserver(Handler handler) {
            super(handler);
        }
@@ -5827,6 +5836,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    mNavigationModeUri, false, this, UserHandle.USER_ALL);
            contentResolver.registerContentObserver(
                    mUserSetupCompleteUri, false, this, UserHandle.USER_ALL);
            if (isRepeatKeysFeatureFlagEnabled() && Flags.enableMagnificationKeyboardControl()) {
                contentResolver.registerContentObserver(
                        mRepeatKeysEnabledUri, false, this, UserHandle.USER_ALL);
                contentResolver.registerContentObserver(
                        mRepeatKeysTimeoutMsUri, false, this, UserHandle.USER_ALL);
            }
        }

        @Override
@@ -5917,6 +5932,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                    }
                } else if (mNavigationModeUri.equals(uri) || mUserSetupCompleteUri.equals(uri)) {
                    updateShortcutsForCurrentNavigationMode();
                } else if (mRepeatKeysEnabledUri.equals(uri)
                        || mRepeatKeysTimeoutMsUri.equals(uri)) {
                    readRepeatKeysSettingsLocked(userState);
                }
            }
        }
@@ -6055,6 +6073,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        return false;
    }

    boolean readRepeatKeysSettingsLocked(AccessibilityUserState userState) {
        if (!isRepeatKeysFeatureFlagEnabled() || !Flags.enableMagnificationKeyboardControl()) {
            return false;
        }
        final boolean isRepeatKeysEnabled = Settings.Secure.getIntForUser(
                mContext.getContentResolver(),
                Settings.Secure.KEY_REPEAT_ENABLED,
                1, userState.mUserId) == 1;
        final int repeatKeysTimeoutMs = Settings.Secure.getIntForUser(
                mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
                ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT, userState.mUserId);
        mMagnificationController.setRepeatKeysEnabled(isRepeatKeysEnabled);
        mMagnificationController.setRepeatKeysTimeoutMs(repeatKeysTimeoutMs);

        // No need to update any other state, so always return false.
        return false;
    }

    boolean readMouseKeysEnabledLocked(AccessibilityUserState userState) {
        if (!keyboardA11yMouseKeys()) {
            return false;
+18 −11
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TypedValue;
import android.view.Display;
import android.view.ViewConfiguration;
import android.view.accessibility.MagnificationAnimationCallback;

import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
@@ -122,9 +123,8 @@ public class MagnificationController implements MagnificationConnectionManager.C
    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;
    private int mInitialKeyboardRepeatIntervalMs =
            ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
    @VisibleForTesting
    public static final int KEYBOARD_REPEAT_INTERVAL_MS = 60;

@@ -321,12 +321,6 @@ 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
@@ -383,7 +377,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
        if (mRepeatKeysEnabled) {
            mHandler.sendMessageDelayed(
                    PooledLambda.obtainMessage(MagnificationController::maybeContinuePan, this),
                    INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
                    mInitialKeyboardRepeatIntervalMs);
        }
    }

@@ -404,7 +398,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
        if (mRepeatKeysEnabled) {
            mHandler.sendMessageDelayed(
                    PooledLambda.obtainMessage(MagnificationController::maybeContinueZoom, this),
                    INITIAL_KEYBOARD_REPEAT_INTERVAL_MS);
                    mInitialKeyboardRepeatIntervalMs);
        }
    }

@@ -434,6 +428,19 @@ public class MagnificationController implements MagnificationConnectionManager.C
        }
    }

    public void setRepeatKeysEnabled(boolean isRepeatKeysEnabled) {
        mRepeatKeysEnabled = isRepeatKeysEnabled;
    }

    public void setRepeatKeysTimeoutMs(int repeatKeysTimeoutMs) {
        mInitialKeyboardRepeatIntervalMs = repeatKeysTimeoutMs;
    }

    @VisibleForTesting
    public int getInitialKeyboardRepeatIntervalMs() {
        return mInitialKeyboardRepeatIntervalMs;
    }

    private void handleUserInteractionChanged(int displayId, int mode) {
        if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
            return;
+28 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_DIALOG_FOR_TRUSTED_SERVICES;

import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
@@ -38,6 +39,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
import static com.android.server.accessibility.Flags.FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL;

import static com.google.common.truth.Truth.assertThat;

@@ -93,6 +95,7 @@ import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
@@ -583,6 +586,31 @@ public class AccessibilityManagerServiceTest {
        verify(mMockMagnificationController).setMagnificationFollowTypingEnabled(false);
    }

    @Test
    @RequiresFlagsEnabled({FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL, FLAG_KEYBOARD_REPEAT_KEYS})
    public void testRepeatKeysSettingsChanges_propagateToMagnificationController() {
        final AccessibilityUserState userState = mA11yms.mUserStates.get(
                mA11yms.getCurrentUserIdLocked());
        Settings.Secure.putIntForUser(
                mTestableContext.getContentResolver(),
                Settings.Secure.KEY_REPEAT_ENABLED,
                0, mA11yms.getCurrentUserIdLocked());

        mA11yms.readRepeatKeysSettingsLocked(userState);

        verify(mMockMagnificationController).setRepeatKeysEnabled(false);

        final int timeoutMs = 42;
        Settings.Secure.putIntForUser(
                mTestableContext.getContentResolver(),
                Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
                timeoutMs, mA11yms.getCurrentUserIdLocked());

        mA11yms.readRepeatKeysSettingsLocked(userState);

        verify(mMockMagnificationController).setRepeatKeysTimeoutMs(timeoutMs);
    }

    @Test
    public void testSettingsAlwaysOn_setEnabled_featureFlagDisabled_doNothing() {
        when(mMockMagnificationController.isAlwaysOnMagnificationFeatureFlagEnabled())
+182 −37
Original line number Diff line number Diff line
@@ -885,66 +885,142 @@ public class MagnificationControllerTest {
    }

    @Test
    public void magnificationCallbacks_panMagnificationContinuous() throws RemoteException {
    public void magnificationCallbacks_scaleMagnificationContinuous() throws RemoteException {
        setMagnificationEnabled(MODE_FULLSCREEN);
        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 8.0f, false);
        float currentScale = 2.0f;
        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, currentScale, 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);
        // Start zooming in using keyboard callbacks.
        mMagnificationController.onScaleMagnificationStart(TEST_DISPLAY,
                MagnificationController.ZOOM_DIRECTION_IN);

        // The center is unchanged.
        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(currentCenterX).isWithin(1.0f).of(newCenterX);
        expect.that(currentCenterY).isEqualTo(newCenterY);

        currentCenterX = newCenterX;
        currentCenterY = newCenterY;
        // The scale is increased.
        float newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
        expect.that(currentScale).isLessThan(newScale);
        currentScale = newScale;

        // Wait for the initial delay to occur.
        advanceTime(MagnificationController.INITIAL_KEYBOARD_REPEAT_INTERVAL_MS + 1);
        advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 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;
        // It should have scaled again after the handler was triggered.
        newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
        expect.that(currentScale).isLessThan(newScale);
        currentScale = newScale;

        for (int i = 0; i < 3; i++) {
            // Wait for repeat delay to occur.
            advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);

        // It should have moved a third time.
            // It should have scaled another time.
            newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
            expect.that(currentScale).isLessThan(newScale);
            currentScale = newScale;
        }

        // Stop magnification scale.
        mMagnificationController.onScaleMagnificationStop(TEST_DISPLAY,
                MagnificationController.ZOOM_DIRECTION_IN);

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

        newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
        expect.that(currentScale).isEqualTo(newScale);
    }

    @Test
    public void magnificationCallbacks_panMagnificationContinuous_repeatKeysTimeout200()
            throws RemoteException {
        // Shorter than default.
        testMagnificationContinuousPanningWithTimeout(200);
    }

    @Test
    public void magnificationCallbacks_panMagnificationContinuous_repeatKeysTimeout1000()
            throws RemoteException {
        // Longer than default.
        testMagnificationContinuousPanningWithTimeout(1000);
    }

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

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

        // Start moving down using keyboard callbacks.
        mMagnificationController.onPanMagnificationStart(TEST_DISPLAY,
                MagnificationController.PAN_DIRECTION_DOWN);

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

        currentCenterX = newCenterX;
        currentCenterY = newCenterY;

        for (int i = 0; i < 3; i++) {
            // Wait for the initial delay to occur.
            advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);

            // It should not have moved again because repeat keys is disabled.
            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(currentCenterX).isEqualTo(newCenterX);
            expect.that(currentCenterY).isEqualTo(newCenterY);
            currentCenterX = newCenterX;
            currentCenterY = newCenterY;
        }

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

        // It should not move again, even after the appropriate delay.
        advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);
    @Test
    public void magnificationCallbacks_scaleMagnification_notContinuousWithRepeatKeysDisabled()
            throws RemoteException {
        mMagnificationController.setRepeatKeysEnabled(false);
        setMagnificationEnabled(MODE_FULLSCREEN);
        float currentScale = 8.0f;
        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, currentScale, false);
        reset(mScreenMagnificationController);

        newCenterX = mScreenMagnificationController.getCenterX(TEST_DISPLAY);
        newCenterY = mScreenMagnificationController.getCenterY(TEST_DISPLAY);
        expect.that(newCenterX).isEqualTo(currentCenterX);
        expect.that(newCenterY).isEqualTo(currentCenterY);
        // Start scaling out using keyboard callbacks.
        mMagnificationController.onScaleMagnificationStart(TEST_DISPLAY,
                MagnificationController.ZOOM_DIRECTION_OUT);

        float newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
        expect.that(currentScale).isGreaterThan(newScale);

        currentScale = newScale;

        for (int i = 0; i < 3; i++) {
            // Wait for the initial delay to occur.
            advanceTime(mMagnificationController.getInitialKeyboardRepeatIntervalMs() + 1);

            // It should not have scaled again because repeat keys is disabled.
            newScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
            expect.that(currentScale).isEqualTo(newScale);
        }

        mMagnificationController.onScaleMagnificationStop(TEST_DISPLAY,
                MagnificationController.ZOOM_DIRECTION_OUT);
    }

    @Test
@@ -1736,6 +1812,75 @@ public class MagnificationControllerTest {
                MagnificationController.PAN_DIRECTION_UP);
    }

    private void
            testMagnificationContinuousPanningWithTimeout(int timeoutMs) throws RemoteException {
        mMagnificationController.setRepeatKeysTimeoutMs(timeoutMs);
        expect.that(timeoutMs).isEqualTo(
                mMagnificationController.getInitialKeyboardRepeatIntervalMs());

        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(timeoutMs + 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;

        for (int i = 0; i < 3; i++) {
            // Wait for repeat delay to occur.
            advanceTime(MagnificationController.KEYBOARD_REPEAT_INTERVAL_MS + 1);

            // It should have moved another 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);
    }

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