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

Commit 5a605978 authored by Chun-Ku Lin's avatar Chun-Ku Lin Committed by Android (Google) Code Review
Browse files

Merge "Updating ACCESSIBILITY_QS_TARGETS when user moves the A11y QS tiles in...

Merge "Updating ACCESSIBILITY_QS_TARGETS when user moves the A11y QS tiles in the QS Panel" into main
parents 023fb386 9fd04c90
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -142,7 +142,7 @@ interface IAccessibilityManager {
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
    void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);

    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
    oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);

    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+121 −19
Original line number Diff line number Diff line
@@ -1706,20 +1706,101 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    }

    @Override
    @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
    @RequiresPermission(allOf = {
            Manifest.permission.STATUS_BAR_SERVICE,
            Manifest.permission.MANAGE_ACCESSIBILITY
    })
    public void notifyQuickSettingsTilesChanged(
            @UserIdInt int userId, List<ComponentName> tileComponentNames) {
        mSecurityPolicy.enforceCallingPermission(
            @UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
        if (!android.view.accessibility.Flags.a11yQsShortcut()) {
            return;
        }

        mContext.enforceCallingPermission(
                Manifest.permission.STATUS_BAR_SERVICE,
                /* function= */ "notifyQuickSettingsTilesChanged");
        mContext.enforceCallingPermission(
                Manifest.permission.MANAGE_ACCESSIBILITY,
                /* function= */ "notifyQuickSettingsTilesChanged");

        if (DEBUG) {
            Slog.d(LOG_TAG, TextUtils.formatSimple(
                    "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
                    userId, tileComponentNames));
        // TODO (b/314843909): in the follow up cl
        }
        final Set<ComponentName> newTileComponentNames = new ArraySet<>(tileComponentNames);
        final Set<ComponentName> addedTiles;
        final Set<ComponentName> removedTiles;
        final Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfo;
        final Map<ComponentName, ComponentName> a11yFeatureToTileService;

        // update in-memory copy of QS_TILES in AccessibilityManager
        // update Settings.Secure.ACCESSIBILITY_QS_TARGETS and its in-memory copy
        // show full device control warning if needed (b/314850435)
        synchronized (mLock) {
            AccessibilityUserState userState = getUserStateLocked(userId);

            tileServiceToA11yServiceInfo = userState.getTileServiceToA11yServiceInfoMapLocked();
            a11yFeatureToTileService = userState.getA11yFeatureToTileService();

            ArraySet<ComponentName> currentTiles = userState.getA11yQsTilesInQsPanel();
            // Find newly added tiles
            addedTiles = newTileComponentNames
                    .stream()
                    .filter(tileComponentName -> !currentTiles.contains(tileComponentName))
                    .collect(Collectors.toSet());
            // Find newly removed tiles
            removedTiles = currentTiles
                    .stream()
                    .filter(tileComponentName -> !newTileComponentNames.contains(tileComponentName))
                    .collect(Collectors.toSet());

            if (addedTiles.isEmpty() && removedTiles.isEmpty()) {
                return;
            }

            userState.updateA11yTilesInQsPanelLocked(newTileComponentNames);
        }

        List<String> a11yFeaturesToEnable = new ArrayList<>();
        List<String> a11yFeaturesToRemove = new ArrayList<>();
        // Find the framework features to configure the qs shortcut on/off
        for (Map.Entry<ComponentName, ComponentName> frameworkFeatureWithTile :
                ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.entrySet()) {
            String a11yFeature = frameworkFeatureWithTile.getKey().flattenToString();
            ComponentName tile = frameworkFeatureWithTile.getValue();
            if (addedTiles.contains(tile)) {
                a11yFeaturesToEnable.add(a11yFeature);
            } else if (removedTiles.contains(tile)) {
                a11yFeaturesToRemove.add(a11yFeature);
            }
        }
        // Find the accessibility service/activity to configure the qs shortcut on/off
        for (Map.Entry<ComponentName, ComponentName> a11yFeatureWithTileService :
                a11yFeatureToTileService.entrySet()) {
            String a11yFeature = a11yFeatureWithTileService.getKey().flattenToString();
            ComponentName tileService = a11yFeatureWithTileService.getValue();
            if (addedTiles.contains(tileService)) {
                AccessibilityServiceInfo serviceInfo = tileServiceToA11yServiceInfo.getOrDefault(
                        tileService, null);
                if (serviceInfo != null && isAccessibilityServiceWarningRequired(serviceInfo)) {
                    // TODO(b/314850435): show full device control warning if needed after
                    // SysUI QS Panel can update live
                    continue;
                }
                a11yFeaturesToEnable.add(a11yFeature);
            } else if (removedTiles.contains(tileService)) {
                a11yFeaturesToRemove.add(a11yFeature);
            }
        }
        // Turn on/off a11y qs shortcut for the a11y features based on the change in QS Panel
        if (!a11yFeaturesToEnable.isEmpty()) {
            enableShortcutForTargets(/* enable= */ true, UserShortcutType.QUICK_SETTINGS,
                    a11yFeaturesToEnable, userId);
        }

        if (!a11yFeaturesToRemove.isEmpty()) {
            enableShortcutForTargets(/* enable= */ false, UserShortcutType.QUICK_SETTINGS,
                    a11yFeaturesToRemove, userId);
        }
    }

    /**
@@ -3661,18 +3742,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    /**
     * Update the Settings.Secure.ACCESSIBILITY_QS_TARGETS so that it only contains valid content,
     * and a side loaded service can't spoof the package name of the default service.
     * <p>
     * 1. Remove the target if the target is no longer installed on the device <br/>
     * 2. Add the target if the target is enabled and the target's tile is in the QS Panel <br/>
     * </p>
     */
    private void updateAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
        final Set<String> targets =
                userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
        final int lastSize = targets.size();
        if (lastSize == 0) {
        if (!android.view.accessibility.Flags.a11yQsShortcut()) {
            return;
        }

        final Set<String> targets =
                userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);

        // Removes the targets that are no longer installed on the device.
        boolean somethingChanged = targets.removeIf(
                name -> !userState.isShortcutTargetInstalledLocked(name));
        // Add the target if the a11y service is enabled and the tile exist in QS panel
        Set<ComponentName> enabledServices = userState.getEnabledServicesLocked();
        Map<ComponentName, ComponentName> a11yFeatureToTileService =
                userState.getA11yFeatureToTileService();
        Set<ComponentName> currentA11yTilesInQsPanel = userState.getA11yQsTilesInQsPanel();
        for (ComponentName enabledService : enabledServices) {
            ComponentName tileService =
                    a11yFeatureToTileService.getOrDefault(enabledService, null);
            if (tileService != null && currentA11yTilesInQsPanel.contains(tileService)) {
                somethingChanged |= targets.add(enabledService.flattenToString());
            }
        }

        if (!somethingChanged) {
            return;
        }
@@ -3700,14 +3798,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            return;
        }

        final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = List.of(
        final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
        shortcutTypeAndShortcutSetting.add(
                new Pair<>(ACCESSIBILITY_SHORTCUT_KEY,
                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
        shortcutTypeAndShortcutSetting.add(
                new Pair<>(ACCESSIBILITY_BUTTON,
                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
        if (android.view.accessibility.Flags.a11yQsShortcut()) {
            shortcutTypeAndShortcutSetting.add(
                    new Pair<>(UserShortcutType.QUICK_SETTINGS,
                        Settings.Secure.ACCESSIBILITY_QS_TARGETS)
        );
                            Settings.Secure.ACCESSIBILITY_QS_TARGETS));
        }

        final ComponentName serviceName = service.getComponentName();
        for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
+50 −1
Original line number Diff line number Diff line
@@ -66,6 +66,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Class that hold states and settings per user and share between
@@ -104,6 +106,15 @@ class AccessibilityUserState {
    final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
    private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();

    /**
     * The QuickSettings tiles in the QS Panel. This can be different from
     * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the
     * TileService's or the a11y framework tile component names (e.g.
     * {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
     * A11y Feature's component names.
     */
    private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();

    private final ServiceInfoChangeListener mServiceInfoChangeListener;

    private ComponentName mServiceChangingSoftKeyboardMode;
@@ -566,7 +577,9 @@ class AccessibilityUserState {
        pw.println("}");
        pw.append("     button target:{").append(mTargetAssignedToAccessibilityButton);
        pw.println("}");
        pw.append("     qs shortcut targets:" + mAccessibilityQsTargets);
        pw.append("     qs shortcut targets:").append(mAccessibilityQsTargets.toString());
        pw.println();
        pw.append("     a11y tiles in QS panel:").append(mA11yTilesInQsPanel.toString());
        pw.println();
        pw.append("     Bound services:{");
        final int serviceCount = mBoundServices.size();
@@ -1100,10 +1113,46 @@ class AccessibilityUserState {
        return new ArraySet<>(mAccessibilityQsTargets);
    }

    public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) {
        mA11yTilesInQsPanel.clear();
        mA11yTilesInQsPanel.addAll(componentNames);
    }

    /**
     * Returns a copy of the a11y tiles that are in the QuickSettings panel
     */
    public ArraySet<ComponentName> getA11yQsTilesInQsPanel() {
        return new ArraySet<>(mA11yTilesInQsPanel);
    }

    /**
     * Returns a map of AccessibilityService or AccessibilityShortcut to its provided TileService
     */
    public Map<ComponentName, ComponentName> getA11yFeatureToTileService() {
        Map<ComponentName, ComponentName> featureToTileServiceMap = new ArrayMap<>();
        featureToTileServiceMap.putAll(mA11yServiceToTileService);
        featureToTileServiceMap.putAll(mA11yActivityToTileService);
        return featureToTileServiceMap;
    }

    /**
     * Returns a map of TileService's componentName to the AccessibilityServiceInfo it ties to.
     */
    public Map<ComponentName, AccessibilityServiceInfo> getTileServiceToA11yServiceInfoMapLocked() {
        Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfoMap =
                new ArrayMap<>();
        Map<ComponentName, AccessibilityServiceInfo> a11yServiceToServiceInfoMap =
                mInstalledServices.stream().collect(
                        Collectors.toMap(
                                AccessibilityServiceInfo::getComponentName,
                                Function.identity()));
        for (Map.Entry<ComponentName, ComponentName> serviceToTile :
                mA11yServiceToTileService.entrySet()) {
            if (a11yServiceToServiceInfoMap.containsKey(serviceToTile.getKey())) {
                tileServiceToA11yServiceInfoMap.put(serviceToTile.getValue(),
                        a11yServiceToServiceInfoMap.get(serviceToTile.getKey()));
            }
        }
        return tileServiceToA11yServiceInfoMap;
    }
}
+152 −0
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@ import androidx.test.filters.SmallTest;

import com.android.compatibility.common.util.TestUtils;
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -1318,6 +1319,152 @@ public class AccessibilityManagerServiceTest {
        }
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
        mTestableContext.getTestablePermissions().setPermission(
                Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
        mockManageAccessibilityGranted(mTestableContext);

        assertThrows(SecurityException.class,
                () -> mA11yms.notifyQuickSettingsTilesChanged(
                        mA11yms.getCurrentUserState().mUserId,
                        List.of(
                                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
        mockStatusBarServiceGranted(mTestableContext);
        mTestableContext.getTestablePermissions().setPermission(
                Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);

        assertThrows(SecurityException.class,
                () -> mA11yms.notifyQuickSettingsTilesChanged(
                        mA11yms.getCurrentUserState().mUserId,
                        List.of(
                                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
        mockStatusBarServiceGranted(mTestableContext);
        mockManageAccessibilityGranted(mTestableContext);
        List<ComponentName> tiles = List.of(
                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
        );

        mA11yms.notifyQuickSettingsTilesChanged(
                mA11yms.getCurrentUserState().mUserId,
                tiles
        );

        assertThat(
                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
        ).containsExactlyElementsIn(tiles);
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
        notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
        List<ComponentName> tiles =
                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel().stream().toList();

        mA11yms.notifyQuickSettingsTilesChanged(
                mA11yms.getCurrentUserState().mUserId,
                tiles
        );

        assertThat(
                mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
        ).containsExactlyElementsIn(tiles);
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
        mockStatusBarServiceGranted(mTestableContext);
        mockManageAccessibilityGranted(mTestableContext);
        setupShortcutTargetServices();
        ComponentName tile = new ComponentName(
                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);

        mA11yms.notifyQuickSettingsTilesChanged(
                mA11yms.getCurrentUserState().mUserId,
                List.of(tile)
        );

        assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
        mockStatusBarServiceGranted(mTestableContext);
        mockManageAccessibilityGranted(mTestableContext);
        setupShortcutTargetServices();
        final AccessibilityUserState userState = mA11yms.getCurrentUserState();
        userState.mAccessibilityButtonTargets.clear();
        userState.mAccessibilityButtonTargets.add(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
        ComponentName tile = new ComponentName(
                TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
                TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);

        mA11yms.notifyQuickSettingsTilesChanged(
                mA11yms.getCurrentUserState().mUserId,
                List.of(tile)
        );

        assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
                .contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
        mockStatusBarServiceGranted(mTestableContext);
        mockManageAccessibilityGranted(mTestableContext);
        List<ComponentName> tiles = List.of(
                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
        );

        mA11yms.notifyQuickSettingsTilesChanged(
                mA11yms.getCurrentUserState().mUserId,
                tiles
        );

        assertThat(
                mA11yms.getCurrentUserState().getA11yQsTargets()
        ).containsExactlyElementsIn(List.of(
                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
        );
    }

    @Test
    @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
    public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
        notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
        Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
        qsTiles.remove(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME);

        mA11yms.notifyQuickSettingsTilesChanged(
                mA11yms.getCurrentUserState().mUserId,
                qsTiles.stream().toList()
        );

        assertThat(
                mA11yms.getCurrentUserState().getA11yQsTargets()
        ).doesNotContain(
                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
    }

    private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
            ComponentName componentName) {
        return mockAccessibilityServiceInfo(
@@ -1367,6 +1514,11 @@ public class AccessibilityManagerServiceTest {
                PackageManager.PERMISSION_GRANTED);
    }

    private void mockStatusBarServiceGranted(TestableContext context) {
        context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
                PackageManager.PERMISSION_GRANTED);
    }

    private void assertStartActivityWithExpectedComponentName(Context mockContext,
            String componentName) {
        verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
+71 −0
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_E

import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;

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

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -45,6 +47,8 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.graphics.Color;
import android.platform.test.annotations.RequiresFlagsDisabled;
@@ -59,6 +63,7 @@ import android.view.Display;
import androidx.test.InstrumentationRegistry;

import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.util.test.FakeSettingsProvider;

import org.junit.After;
@@ -68,6 +73,9 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Map;
import java.util.Set;

/** Tests for AccessibilityUserState */
public class AccessibilityUserStateTest {

@@ -431,7 +439,70 @@ public class AccessibilityUserStateTest {

        assertEquals(focusStrokeWidthValue, mUserState.getFocusStrokeWidthLocked());
        assertEquals(focusColorValue, mUserState.getFocusColorLocked());
    }

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

        mUserState.updateA11yQsTargetLocked(newTargets);

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

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

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

        assertThat(mUserState.getA11yQsTargets()).isNotEmpty();
    }

    @Test
    public void updateA11yTilesInQsPanelLocked_valueUpdated() {
        Set<ComponentName> newTargets = Set.of(
                AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
                AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
        );

        mUserState.updateA11yTilesInQsPanelLocked(newTargets);

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

    @Test
    public void getA11yQsTilesInQsPanel_returnsCopiedData() {
        updateA11yTilesInQsPanelLocked_valueUpdated();

        Set<ComponentName> targets = mUserState.getA11yQsTilesInQsPanel();
        targets.clear();

        assertThat(mUserState.getA11yQsTilesInQsPanel()).isNotEmpty();
    }

    @Test
    public void getTileServiceToA11yServiceInfoMapLocked() {
        final ComponentName tileComponent =
                new ComponentName(COMPONENT_NAME.getPackageName(), "FakeTileService");
        ServiceInfo serviceInfo = new ServiceInfo();
        serviceInfo.packageName = tileComponent.getPackageName();
        serviceInfo.name = COMPONENT_NAME.getClassName();
        ResolveInfo resolveInfo = new ResolveInfo();
        resolveInfo.serviceInfo = serviceInfo;
        when(mMockServiceInfo.getTileServiceName()).thenReturn(tileComponent.getClassName());
        when(mMockServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
        mUserState.mInstalledServices.add(mMockServiceInfo);
        mUserState.updateTileServiceMapForAccessibilityServiceLocked();

        Map<ComponentName, AccessibilityServiceInfo> actual =
                mUserState.getTileServiceToA11yServiceInfoMapLocked();

        assertThat(actual).containsExactly(tileComponent, mMockServiceInfo);
    }

    private int getSecureIntForUser(String key, int userId) {