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

Commit 6c884751 authored by Daniel Norman's avatar Daniel Norman Committed by Android (Google) Code Review
Browse files

Merge "Migrates users to the AccessibilityMenu in system."

parents 80bf9e74 ad959bc4
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -230,6 +230,17 @@ public final class AccessibilityManager {
     */
    public static final int FLAG_CONTENT_CONTROLS = 4;


    /**
     * {@link ComponentName} for the Accessibility Menu {@link AccessibilityService} as provided
     * inside the system build, used for automatic migration to this version of the service.
     * @hide
     */
    public static final ComponentName ACCESSIBILITY_MENU_IN_SYSTEM =
            new ComponentName("com.android.systemui.accessibility.accessibilitymenu",
                    "com.android.systemui.accessibility.accessibilitymenu"
                            + ".AccessibilityMenuService");

    @UnsupportedAppUsage
    static final Object sInstanceSync = new Object();

+96 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAG
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_MENU_IN_SYSTEM;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
@@ -170,6 +171,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -215,6 +217,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    private static final String SET_PIP_ACTION_REPLACEMENT =
            "setPictureInPictureActionReplacingConnection";

    @VisibleForTesting
    static final String MENU_SERVICE_RELATIVE_CLASS_NAME = ".AccessibilityMenuService";

    private static final char COMPONENT_NAME_SEPARATOR = ':';

    private static final int OWN_PROCESS_ID = android.os.Process.myPid();
@@ -823,6 +828,95 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                Context.RECEIVER_EXPORTED);
    }

    /**
     * Migrates the Accessibility Menu to the version provided by the system build,
     * if necessary based on presence of the service on the device.
     */
    @VisibleForTesting
    void migrateAccessibilityMenuIfNecessaryLocked(AccessibilityUserState userState) {
        final Set<ComponentName> menuComponentNames = findA11yMenuComponentNamesLocked();
        final ComponentName menuOutsideSystem = getA11yMenuOutsideSystem(menuComponentNames);
        final boolean shouldMigrateToMenuInSystem = menuComponentNames.size() == 2
                && menuComponentNames.contains(ACCESSIBILITY_MENU_IN_SYSTEM)
                && menuOutsideSystem != null;

        if (!shouldMigrateToMenuInSystem) {
            if (menuComponentNames.size() == 1) {
                // If only one Menu package exists then reset its component to the default state.
                mPackageManager.setComponentEnabledSetting(
                        menuComponentNames.stream().findFirst().get(),
                        PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
                        PackageManager.DONT_KILL_APP);
            }
            return;
        }

        // Hide Menu-outside-system so that it does not appear in Settings.
        mPackageManager.setComponentEnabledSetting(
                menuOutsideSystem,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
        // Migrate the accessibility shortcuts.
        migrateA11yMenuInSettingLocked(userState,
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, menuOutsideSystem);
        migrateA11yMenuInSettingLocked(userState,
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, menuOutsideSystem);
        migrateA11yMenuInSettingLocked(userState,
                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, menuOutsideSystem);
        // If Menu-outside-system is currently enabled by the user then automatically
        // disable it and enable Menu-in-system.
        migrateA11yMenuInSettingLocked(userState,
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, menuOutsideSystem);
    }

    /**
     * Returns all {@link ComponentName}s whose class name ends in {@link
     * #MENU_SERVICE_RELATIVE_CLASS_NAME}.
     **/
    private Set<ComponentName> findA11yMenuComponentNamesLocked() {
        Set<ComponentName> result = new ArraySet<>();
        final var flags =
                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DISABLED_COMPONENTS
                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId)) {
            final ComponentName componentName = resolveInfo.serviceInfo.getComponentName();
            if (componentName.getClassName().endsWith(MENU_SERVICE_RELATIVE_CLASS_NAME)) {
                result.add(componentName);
            }
        }
        return result;
    }

    /**
     * Returns the first {@link ComponentName} in the provided set that is not equal to {@link
     * AccessibilityManager#ACCESSIBILITY_MENU_IN_SYSTEM}.
     */
    private static ComponentName getA11yMenuOutsideSystem(Set<ComponentName> menuComponentNames) {
        Optional<ComponentName> menuOutsideSystem = menuComponentNames.stream().filter(
                name -> !name.equals(ACCESSIBILITY_MENU_IN_SYSTEM)).findFirst();
        if (menuOutsideSystem.isEmpty()) {
            return null;
        }
        return menuOutsideSystem.get();
    }

    /**
     * Replaces <code>toRemove</code> with {@link AccessibilityManager#ACCESSIBILITY_MENU_IN_SYSTEM}
     * in the requested setting, if present already.
     */
    private void migrateA11yMenuInSettingLocked(AccessibilityUserState userState, String setting,
            ComponentName toRemove) {
        mTempComponentNameSet.clear();
        readComponentNamesFromSettingLocked(setting, userState.mUserId, mTempComponentNameSet);
        if (mTempComponentNameSet.contains(toRemove)) {
            mTempComponentNameSet.remove(toRemove);
            mTempComponentNameSet.add(ACCESSIBILITY_MENU_IN_SYSTEM);
            persistComponentNamesToSettingLocked(setting, mTempComponentNameSet, userState.mUserId);
        }
    }

    // Called only during settings restore; currently supports only the owner user
    // TODO: b/22388012
    private void restoreLegacyDisplayMagnificationNavBarIfNeededLocked(String newSetting,
@@ -1592,6 +1686,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            mCurrentUserId = userId;
            AccessibilityUserState userState = getCurrentUserStateLocked();

            migrateAccessibilityMenuIfNecessaryLocked(userState);

            readConfigurationForUserStateLocked(userState);
            mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
            // Even if reading did not yield change, we have to update
+114 −0
Original line number Diff line number Diff line
@@ -16,13 +16,19 @@

package com.android.server.accessibility;

import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_MENU_IN_SYSTEM;

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.server.accessibility.AccessibilityManagerService.MENU_SERVICE_RELATIVE_CLASS_NAME;

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

@@ -93,6 +99,7 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;

/**
 * APCT tests for {@link AccessibilityManagerService}.
@@ -514,6 +521,113 @@ public class AccessibilityManagerServiceTest {
                ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
    }

    @Test
    public void testMigrateA11yMenu_ResetSingularComponentToDefaultState() {
        final ComponentName componentName =
                ComponentName.createRelative("external", MENU_SERVICE_RELATIVE_CLASS_NAME);
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(
                List.of(createResolveInfo(componentName)));

        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());

        verify(mMockPackageManager).setComponentEnabledSetting(componentName,
                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
                PackageManager.DONT_KILL_APP);
    }

    @Test
    public void testMigrateA11yMenu_DoNothing_WhenNoMenuComponents() {
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of());

        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());

        verify(mMockPackageManager, never()).setComponentEnabledSetting(any(),
                anyInt(), anyInt());
    }

    @Test
    public void testMigrateA11yMenu_DoNothing_WhenTooManyMenuComponents() {
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of(
                createResolveInfo(ComponentName.createRelative("external1",
                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
                createResolveInfo(ComponentName.createRelative("external2",
                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
                createResolveInfo(ComponentName.createRelative("external3",
                        MENU_SERVICE_RELATIVE_CLASS_NAME))));

        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());

        verify(mMockPackageManager, never()).setComponentEnabledSetting(any(),
                anyInt(), anyInt());
    }

    @Test
    public void testMigrateA11yMenu_DoNothing_WhenNoMenuInSystem() {
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of(
                createResolveInfo(ComponentName.createRelative("external1",
                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
                createResolveInfo(ComponentName.createRelative("external2",
                        MENU_SERVICE_RELATIVE_CLASS_NAME))));

        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());

        verify(mMockPackageManager, never()).setComponentEnabledSetting(any(),
                anyInt(), anyInt());
    }

    @Test
    public void testMigrateA11yMenu_PerformsMigration() {
        final ComponentName menuOutsideSystem =
                ComponentName.createRelative("external", MENU_SERVICE_RELATIVE_CLASS_NAME);
        final String[] migratedSettings = {
                ACCESSIBILITY_BUTTON_TARGETS,
                ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
                ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
                ENABLED_ACCESSIBILITY_SERVICES
        };
        // Start the user with Menu-outside-system enabled,
        for (String setting : migratedSettings) {
            Settings.Secure.putStringForUser(
                    mTestableContext.getContentResolver(),
                    setting,
                    menuOutsideSystem.flattenToShortString(),
                    mA11yms.getCurrentUserIdLocked());
        }
        // and both Menu versions present.
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of(
                createResolveInfo(menuOutsideSystem),
                createResolveInfo(ACCESSIBILITY_MENU_IN_SYSTEM)));

        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());

        // Menu-outside-system should be disabled,
        verify(mMockPackageManager).setComponentEnabledSetting(menuOutsideSystem,
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
        // and all settings should migrated to Menu-in-system.
        for (String setting : migratedSettings) {
            ComponentName componentName = ComponentName.unflattenFromString(
                    Settings.Secure.getStringForUser(
                            mTestableContext.getContentResolver(),
                            setting,
                            mA11yms.getCurrentUserIdLocked()));
            assertThat(componentName).isEqualTo(ACCESSIBILITY_MENU_IN_SYSTEM);
        }
    }

    private static ResolveInfo createResolveInfo(ComponentName componentName) {
        ResolveInfo resolveInfo = new ResolveInfo();
        resolveInfo.serviceInfo = new ServiceInfo();
        resolveInfo.serviceInfo.packageName = componentName.getPackageName();
        resolveInfo.serviceInfo.name = componentName.getClassName();
        return resolveInfo;
    }

    private void mockManageAccessibilityGranted(TestableContext context) {
        context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
                PackageManager.PERMISSION_GRANTED);