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

Commit 8aee7621 authored by Riley Jones's avatar Riley Jones
Browse files

Refactoring AccessibilityManagerService Read & Restore

Implemented parameterized readA11yShortcutTarget and restoreA11yShortcutTarget functions

Test: atest com.android.server.accessibility
Flag: EXEMPT internal refactoring
Bug: 330775538
Change-Id: Ie8bd70ff150299d9a6ad6d1347be5c967e14dea9
parent a96c939d
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -180,6 +180,27 @@ public final class ShortcutUtils {
        }
    }

    /**
     * Converts {@link Settings.Secure} key to {@link UserShortcutType}.
     *
     * @param key The shortcut key in Settings.
     * @return The mapped type
     */
    @UserShortcutType
    public static int convertToType(String key) {
        return switch (key) {
            case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS -> UserShortcutType.SOFTWARE;
            case Settings.Secure.ACCESSIBILITY_QS_TARGETS -> UserShortcutType.QUICK_SETTINGS;
            case Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> UserShortcutType.HARDWARE;
            case Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED ->
                    UserShortcutType.TRIPLETAP;
            case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED ->
                    UserShortcutType.TWOFINGER_DOUBLETAP;
            default -> throw new IllegalArgumentException(
                    "Unsupported user shortcut key: " + key);
        };
    }

    /**
     * Updates an accessibility state if the accessibility service is a Always-On a11y service,
     * a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON
+125 −188

File changed.

Preview size limit exceeded, changes collapsed.

+33 −13
Original line number Diff line number Diff line
@@ -777,12 +777,15 @@ class AccessibilityUserState {
     * @return The array set of the strings
     */
    public ArraySet<String> getShortcutTargetsLocked(@UserShortcutType int shortcutType) {
        return new ArraySet<>(getShortcutTargetsInternalLocked(shortcutType));
    }
    private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) {
        if (shortcutType == UserShortcutType.HARDWARE) {
            return mAccessibilityShortcutKeyTargets;
        } else if (shortcutType == UserShortcutType.SOFTWARE) {
            return mAccessibilityButtonTargets;
        } else if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
            return getA11yQsTargets();
            return mAccessibilityQsTargets;
        } else if ((shortcutType == UserShortcutType.TRIPLETAP
                && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
                shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP
@@ -794,6 +797,32 @@ class AccessibilityUserState {
        return new ArraySet<>();
    }

    /**
     * Updates the corresponding shortcut targets with the provided set.
     * Tap shortcuts don't operate using sets of targets,
     * so trying to update {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP}
     * will instead throw an {@code IllegalArgumentException}
     * @param newTargets set of targets to replace the existing set.
     * @param shortcutType type to be replaced.
     * @return {@code true} if the set was changed, or {@code false} if the elements are the same.
     * @throws IllegalArgumentException if {@code TRIPLETAP} or {@code TWOFINGER_DOUBLETAP} is used.
     */
    boolean updateShortcutTargetsLocked(
            Set<String> newTargets, @UserShortcutType int shortcutType) {
        final int mask = UserShortcutType.TRIPLETAP | UserShortcutType.TWOFINGER_DOUBLETAP;
        if ((shortcutType & mask) != 0) {
            throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets.");
        }

        final Set<String> currentTargets = getShortcutTargetsInternalLocked(shortcutType);
        if (newTargets.equals(currentTargets)) {
            return false;
        }
        currentTargets.clear();
        currentTargets.addAll(newTargets);
        return true;
    }

    /**
     * Whether or not the given shortcut target is installed in device.
     *
@@ -844,8 +873,9 @@ class AccessibilityUserState {
            );
        }

        Set<String> targets = getShortcutTargetsLocked(shortcutType);
        boolean result = targets.removeIf(name -> {
        // getting internal set lets us directly modify targets, as it's not a copy.
        Set<String> targets = getShortcutTargetsInternalLocked(shortcutType);
        return targets.removeIf(name -> {
            ComponentName componentName;
            if (name == null
                    || (componentName = ComponentName.unflattenFromString(name)) == null) {
@@ -853,11 +883,6 @@ class AccessibilityUserState {
            }
            return componentName.equals(target);
        });
        if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
            updateA11yQsTargetLocked(targets);
        }

        return result;
    }

    /**
@@ -1114,11 +1139,6 @@ class AccessibilityUserState {
        );
    }

    public void updateA11yQsTargetLocked(Set<String> targets) {
        mAccessibilityQsTargets.clear();
        mAccessibilityQsTargets.addAll(targets);
    }

    /**
     * Returns a copy of the targets which has qs shortcut turned on
     */
+87 −45
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_D

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.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;

@@ -1082,7 +1084,7 @@ public class AccessibilityManagerServiceTest {

        mA11yms.enableShortcutsForTargets(
                /* enable= */ true,
                UserShortcutType.HARDWARE,
                HARDWARE,
                List.of(target),
                mA11yms.getCurrentUserIdLocked());
        mTestableLooper.processAllMessages();
@@ -1346,14 +1348,14 @@ public class AccessibilityManagerServiceTest {

        mA11yms.enableShortcutsForTargets(
                /* enable= */ true,
                UserShortcutType.HARDWARE,
                HARDWARE,
                List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
                mA11yms.getCurrentUserIdLocked());
        mTestableLooper.processAllMessages();

        assertThat(
                ShortcutUtils.isComponentIdExistingInSettings(
                        mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
                        mTestableContext, HARDWARE,
                        TARGET_STANDARD_A11Y_SERVICE.flattenToString())
        ).isTrue();
    }
@@ -1367,7 +1369,7 @@ public class AccessibilityManagerServiceTest {

        mA11yms.enableShortcutsForTargets(
                /* enable= */ false,
                UserShortcutType.HARDWARE,
                HARDWARE,
                List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
                mA11yms.getCurrentUserIdLocked());
        mTestableLooper.processAllMessages();
@@ -1375,7 +1377,7 @@ public class AccessibilityManagerServiceTest {
        assertThat(
                        ShortcutUtils.isComponentIdExistingInSettings(
                                mTestableContext,
                                ShortcutConstants.UserShortcutType.HARDWARE,
                                HARDWARE,
                                TARGET_STANDARD_A11Y_SERVICE.flattenToString()))
                .isFalse();
    }
@@ -1390,14 +1392,14 @@ public class AccessibilityManagerServiceTest {

        mA11yms.enableShortcutsForTargets(
                /* enable= */ true,
                UserShortcutType.QUICK_SETTINGS,
                QUICK_SETTINGS,
                List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
                mA11yms.getCurrentUserIdLocked());
        mTestableLooper.processAllMessages();

        assertThat(
                ShortcutUtils.isComponentIdExistingInSettings(
                        mTestableContext, UserShortcutType.QUICK_SETTINGS,
                        mTestableContext, QUICK_SETTINGS,
                        TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
        ).isTrue();
        verify(mStatusBarManagerInternal)
@@ -1417,14 +1419,14 @@ public class AccessibilityManagerServiceTest {

        mA11yms.enableShortcutsForTargets(
                /* enable= */ false,
                UserShortcutType.QUICK_SETTINGS,
                QUICK_SETTINGS,
                List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
                mA11yms.getCurrentUserIdLocked());
        mTestableLooper.processAllMessages();

        assertThat(
                ShortcutUtils.isComponentIdExistingInSettings(
                        mTestableContext, UserShortcutType.QUICK_SETTINGS,
                        mTestableContext, QUICK_SETTINGS,
                        TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
        ).isFalse();
        verify(mStatusBarManagerInternal)
@@ -1614,44 +1616,49 @@ public class AccessibilityManagerServiceTest {

    @Test
    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void restoreAccessibilityQsTargets_a11yQsTargetsRestored() {
    public void restoreShortcutTargets_qs_a11yQsTargetsRestored() {
        String daltonizerTile =
                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
        String colorInversionTile =
                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
        final AccessibilityUserState userState = new AccessibilityUserState(
                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
        userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
        userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);

        broadcastSettingRestored(
                Settings.Secure.ACCESSIBILITY_QS_TARGETS,
                /*previousValue=*/null,
                ShortcutUtils.convertToKey(QUICK_SETTINGS),
                /*newValue=*/colorInversionTile);

        assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM).getA11yQsTargets())
                .containsExactlyElementsIn(Set.of(daltonizerTile, colorInversionTile));
        Set<String> expected = Set.of(daltonizerTile, colorInversionTile);
        assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS)))
                .containsExactlyElementsIn(expected);
        assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS))
                .containsExactlyElementsIn(expected);
    }

    @Test
    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void restoreAccessibilityQsTargets_a11yQsTargetsNotRestored() {
    public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() {
        String daltonizerTile =
                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
        String colorInversionTile =
                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
        final AccessibilityUserState userState = new AccessibilityUserState(
                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
        userState.updateA11yQsTargetLocked(Set.of(daltonizerTile));
        userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
        putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId);
        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);

        broadcastSettingRestored(
                Settings.Secure.ACCESSIBILITY_QS_TARGETS,
                /*previousValue=*/null,
                ShortcutUtils.convertToKey(QUICK_SETTINGS),
                /*newValue=*/colorInversionTile);

        assertThat(userState.getA11yQsTargets())
                .containsExactlyElementsIn(Set.of(daltonizerTile));
        Set<String> expected = Set.of(daltonizerTile);
        assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS)))
                .containsExactlyElementsIn(expected);
        assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS))
                .containsExactlyElementsIn(expected);
    }

    @Test
@@ -1717,27 +1724,26 @@ public class AccessibilityManagerServiceTest {

    @Test
    @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE)
    public void restoreA11yShortcutTargetService_targetsMerged() {
    public void restoreShortcutTargets_hardware_targetsMerged() {
        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
        final String servicePrevious = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
        final String otherPrevious = TARGET_MAGNIFICATION;
        final String combinedPrevious = String.join(":", servicePrevious, otherPrevious);
        final String serviceRestored = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
        final AccessibilityUserState userState = new AccessibilityUserState(
                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
        setupShortcutTargetServices(userState);
        mA11yms.enableShortcutsForTargets(
                true, HARDWARE, List.of(servicePrevious, otherPrevious), userState.mUserId);

        broadcastSettingRestored(
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                /*previousValue=*/combinedPrevious,
                ShortcutUtils.convertToKey(HARDWARE),
                /*newValue=*/serviceRestored);

        final Set<String> expected = Set.of(servicePrevious, otherPrevious, serviceRestored);
        assertThat(readStringsFromSetting(
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))
        assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
                .containsExactlyElementsIn(expected);
        assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM)
                .getShortcutTargetsLocked(UserShortcutType.HARDWARE))
        assertThat(userState.getShortcutTargetsLocked(HARDWARE))
                .containsExactlyElementsIn(expected);
    }

@@ -1745,7 +1751,7 @@ public class AccessibilityManagerServiceTest {
    @EnableFlags({
            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
            Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
    public void restoreA11yShortcutTargetService_alreadyHadDefaultService_doesNotClear() {
    public void restoreShortcutTargets_hardware_alreadyHadDefaultService_doesNotClear() {
        final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
        mTestableContext.getOrCreateTestableResources().addOverride(
                R.string.config_defaultAccessibilityService, serviceDefault);
@@ -1754,17 +1760,18 @@ public class AccessibilityManagerServiceTest {
        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
        setupShortcutTargetServices(userState);

        // default is present in userState & setting, so it's not cleared
        putShortcutSettingForUser(HARDWARE, serviceDefault, UserHandle.USER_SYSTEM);
        userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);

        broadcastSettingRestored(
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                /*previousValue=*/serviceDefault,
                /*newValue=*/serviceDefault);

        final Set<String> expected = Set.of(serviceDefault);
        assertThat(readStringsFromSetting(
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))
        assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
                .containsExactlyElementsIn(expected);
        assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM)
                .getShortcutTargetsLocked(UserShortcutType.HARDWARE))
        assertThat(userState.getShortcutTargetsLocked(HARDWARE))
                .containsExactlyElementsIn(expected);
    }

@@ -1772,7 +1779,7 @@ public class AccessibilityManagerServiceTest {
    @EnableFlags({
            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
            Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
    public void restoreA11yShortcutTargetService_didNotHaveDefaultService_clearsDefaultService() {
    public void restoreShortcutTargets_hardware_didNotHaveDefaultService_clearsDefaultService() {
        final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
        final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
        // Restored value from the broadcast contains both default and non-default service.
@@ -1784,18 +1791,45 @@ public class AccessibilityManagerServiceTest {
        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
        setupShortcutTargetServices(userState);

        broadcastSettingRestored(
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                /*previousValue=*/null,
        broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
                /*newValue=*/combinedRestored);

        // The default service is cleared from the final restored value.
        final Set<String> expected = Set.of(serviceRestored);
        assertThat(readStringsFromSetting(
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE))
        assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
                .containsExactlyElementsIn(expected);
        assertThat(mA11yms.mUserStates.get(UserHandle.USER_SYSTEM)
                .getShortcutTargetsLocked(UserShortcutType.HARDWARE))
        assertThat(userState.getShortcutTargetsLocked(HARDWARE))
                .containsExactlyElementsIn(expected);
    }

    @Test
    @EnableFlags({
            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SHORTCUT_TARGET_SERVICE,
            Flags.FLAG_CLEAR_DEFAULT_FROM_A11Y_SHORTCUT_TARGET_SERVICE_RESTORE})
    public void restoreShortcutTargets_hardware_nullSetting_clearsDefaultService() {
        final String serviceDefault = TARGET_STANDARD_A11Y_SERVICE.flattenToString();
        final String serviceRestored = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
        // Restored value from the broadcast contains both default and non-default service.
        final String combinedRestored = String.join(":", serviceDefault, serviceRestored);
        mTestableContext.getOrCreateTestableResources().addOverride(
                R.string.config_defaultAccessibilityService, serviceDefault);
        final AccessibilityUserState userState = new AccessibilityUserState(
                UserHandle.USER_SYSTEM, mTestableContext, mA11yms);
        mA11yms.mUserStates.put(UserHandle.USER_SYSTEM, userState);
        setupShortcutTargetServices(userState);

        // UserState has default, but setting is null (this emulates a typical scenario in SUW).
        userState.updateShortcutTargetsLocked(Set.of(serviceDefault), HARDWARE);
        putShortcutSettingForUser(HARDWARE, null, UserHandle.USER_SYSTEM);

        broadcastSettingRestored(ShortcutUtils.convertToKey(HARDWARE),
                /*newValue=*/combinedRestored);

        // The default service is cleared from the final restored value.
        final Set<String> expected = Set.of(serviceRestored);
        assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(HARDWARE)))
                .containsExactlyElementsIn(expected);
        assertThat(userState.getShortcutTargetsLocked(HARDWARE))
                .containsExactlyElementsIn(expected);
    }

@@ -1806,11 +1840,10 @@ public class AccessibilityManagerServiceTest {
        return result;
    }

    private void broadcastSettingRestored(String setting, String previousValue, String newValue) {
    private void broadcastSettingRestored(String setting, String newValue) {
        Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                .putExtra(Intent.EXTRA_SETTING_NAME, setting)
                .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue)
                .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, newValue);
        sendBroadcastToAccessibilityManagerService(intent);
        mTestableLooper.processAllMessages();
@@ -1952,4 +1985,13 @@ public class AccessibilityManagerServiceTest {
    private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
        return service.getCurrentUserIdLocked() == context.getUserId();
    }

    private void putShortcutSettingForUser(@UserShortcutType int shortcutType,
            String shortcutValue, int userId) {
        Settings.Secure.putStringForUser(
                mTestableContext.getContentResolver(),
                ShortcutUtils.convertToKey(shortcutType),
                shortcutValue,
                userId);
    }
}
+4 −3
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSI
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;

import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;

import static com.google.common.truth.Truth.assertThat;
@@ -429,20 +430,20 @@ public class AccessibilityUserStateTest {
    }

    @Test
    public void updateA11yQsTargetLocked_valueUpdated() {
    public void updateShortcutTargetsLocked_quickSettings_valueUpdated() {
        Set<String> newTargets = Set.of(
                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString()
        );

        mUserState.updateA11yQsTargetLocked(newTargets);
        mUserState.updateShortcutTargetsLocked(newTargets, QUICK_SETTINGS);

        assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
    }

    @Test
    public void getA11yQsTargets_returnsCopiedData() {
        updateA11yQsTargetLocked_valueUpdated();
        updateShortcutTargetsLocked_quickSettings_valueUpdated();

        Set<String> targets = mUserState.getA11yQsTargets();
        targets.clear();