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

Commit 19a301ed authored by Daniel Norman's avatar Daniel Norman
Browse files

Extracts out A11yMenu Settings migration logic to AccessibilityUtils.

Settings migrations should be done by SettingsProvider.

Bug: 261252772
Test: atest AccessibilityUtilsTest
Change-Id: Iaabb0eb3b6f21b7752da2a324ccf4d5955847833
parent 6e3d589b
Loading
Loading
Loading
Loading
+0 −11
Original line number Diff line number Diff line
@@ -255,17 +255,6 @@ 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();

+71 −0
Original line number Diff line number Diff line
@@ -19,11 +19,16 @@ package com.android.internal.accessibility.util;
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
@@ -33,6 +38,8 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;

import com.android.internal.annotations.VisibleForTesting;

import libcore.util.EmptyArray;

import java.lang.annotation.Retention;
@@ -40,6 +47,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
@@ -66,6 +74,19 @@ public final class AccessibilityUtils {
    /** Specifies some parcelable spans has been changed. */
    public static final int PARCELABLE_SPAN = 2;

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

    /**
     * {@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"
                            + MENU_SERVICE_RELATIVE_CLASS_NAME);

    /**
     * Returns the set of enabled accessibility services for userId. If there are no
     * services, it returns the unmodifiable {@link Collections#emptySet()}.
@@ -244,4 +265,54 @@ public final class AccessibilityUtils {
        }
        return true;
    }

    /**
     * Finds the {@link ComponentName} of the AccessibilityMenu accessibility service that the
     * device should be migrated off. Devices using this service should be migrated to
     * {@link #ACCESSIBILITY_MENU_IN_SYSTEM}.
     *
     * <p>
     * Requirements:
     * <li>There are exactly two installed accessibility service components with class name
     * {@link #MENU_SERVICE_RELATIVE_CLASS_NAME}.</li>
     * <li>Exactly one of these components is equal to {@link #ACCESSIBILITY_MENU_IN_SYSTEM}.</li>
     * </p>
     *
     * @return The {@link ComponentName} of the service that is not {@link
     * #ACCESSIBILITY_MENU_IN_SYSTEM},
     * or <code>null</code> if the above requirements are not met.
     */
    @Nullable
    public static ComponentName getAccessibilityMenuComponentToMigrate(
            PackageManager packageManager, int userId) {
        final Set<ComponentName> menuComponentNames = findA11yMenuComponentNames(packageManager,
                userId);
        Optional<ComponentName> menuOutsideSystem = menuComponentNames.stream().filter(
                name -> !name.equals(ACCESSIBILITY_MENU_IN_SYSTEM)).findFirst();
        final boolean shouldMigrateToMenuInSystem = menuComponentNames.size() == 2
                && menuComponentNames.contains(ACCESSIBILITY_MENU_IN_SYSTEM)
                && menuOutsideSystem.isPresent();
        return shouldMigrateToMenuInSystem ? menuOutsideSystem.get() : null;
    }

    /**
     * Returns all {@link ComponentName}s whose class name ends in {@link
     * #MENU_SERVICE_RELATIVE_CLASS_NAME}.
     **/
    private static Set<ComponentName> findA11yMenuComponentNames(
            PackageManager packageManager, int userId) {
        Set<ComponentName> result = new ArraySet<>();
        final PackageManager.ResolveInfoFlags flags = PackageManager.ResolveInfoFlags.of(
                PackageManager.MATCH_DISABLED_COMPONENTS
                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
        for (ResolveInfo resolveInfo : packageManager.queryIntentServicesAsUser(
                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId)) {
            final ComponentName componentName = resolveInfo.serviceInfo.getComponentName();
            if (componentName.getClassName().endsWith(MENU_SERVICE_RELATIVE_CLASS_NAME)) {
                result.add(componentName);
            }
        }
        return result;
    }
}
+98 −8
Original line number Diff line number Diff line
@@ -16,8 +16,19 @@

package com.android.internal.accessibility;

import static junit.framework.Assert.assertEquals;
import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
import static com.android.internal.accessibility.util.AccessibilityUtils.MENU_SERVICE_RELATIVE_CLASS_NAME;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.text.ParcelableSpan;
import android.text.SpannableString;
import android.text.style.LocaleSpan;
@@ -26,9 +37,13 @@ import androidx.test.runner.AndroidJUnit4;

import com.android.internal.accessibility.util.AccessibilityUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.List;
import java.util.Locale;

/**
@@ -36,6 +51,15 @@ import java.util.Locale;
 */
@RunWith(AndroidJUnit4.class)
public class AccessibilityUtilsTest {
    private static final int USER_ID = 123;
    @Mock
    private PackageManager mMockPackageManager;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void textOrSpanChanged_stringChange_returnTextChange() {
        final CharSequence beforeText = "a";
@@ -44,7 +68,7 @@ public class AccessibilityUtilsTest {

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.TEXT, type);
        assertThat(type).isEqualTo(AccessibilityUtils.TEXT);
    }

    @Test
@@ -55,7 +79,7 @@ public class AccessibilityUtilsTest {

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.NONE, type);
        assertThat(type).isEqualTo(AccessibilityUtils.NONE);
    }

    @Test
@@ -68,7 +92,7 @@ public class AccessibilityUtilsTest {

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.NONE, type);
        assertThat(type).isEqualTo(AccessibilityUtils.NONE);
    }

    @Test
@@ -81,7 +105,7 @@ public class AccessibilityUtilsTest {

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
        assertThat(type).isEqualTo(AccessibilityUtils.PARCELABLE_SPAN);
    }

    @Test
@@ -96,7 +120,7 @@ public class AccessibilityUtilsTest {

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
        assertThat(type).isEqualTo(AccessibilityUtils.PARCELABLE_SPAN);
    }

    @Test
@@ -110,7 +134,7 @@ public class AccessibilityUtilsTest {

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.NONE, type);
        assertThat(type).isEqualTo(AccessibilityUtils.NONE);
    }

    @Test
@@ -124,6 +148,72 @@ public class AccessibilityUtilsTest {

        @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                beforeText, afterText);
        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
        assertThat(type).isEqualTo(AccessibilityUtils.PARCELABLE_SPAN);
    }

    @Test
    public void getAccessibilityMenuComponentToMigrate_isNull_whenNoMenuComponents() {
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(USER_ID))).thenReturn(List.of());

        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
                mMockPackageManager, USER_ID);

        assertThat(result).isNull();
    }

    @Test
    public void getAccessibilityMenuComponentToMigrate_isNull_whenTooManyMenuComponents() {
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(USER_ID))).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))));

        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
                mMockPackageManager, USER_ID);

        assertThat(result).isNull();
    }

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

        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
                mMockPackageManager, USER_ID);

        assertThat(result).isNull();
    }

    @Test
    public void getAccessibilityMenuComponentToMigrate_returnsMenuOutsideSystem() {
        ComponentName menuOutsideSystem = ComponentName.createRelative("external1",
                MENU_SERVICE_RELATIVE_CLASS_NAME);
        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
                eq(USER_ID))).thenReturn(List.of(
                createResolveInfo(menuOutsideSystem),
                createResolveInfo(ACCESSIBILITY_MENU_IN_SYSTEM)));

        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
                mMockPackageManager, USER_ID);

        assertThat(result).isEqualTo(menuOutsideSystem);
    }

    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;
    }
}
+21 −90
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ 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;
@@ -138,6 +137,7 @@ import com.android.internal.accessibility.AccessibilityShortcutController.Framew
import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
@@ -172,7 +172,6 @@ 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;
@@ -218,9 +217,6 @@ 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();
@@ -484,6 +480,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
        registerBroadcastReceivers();
        new AccessibilityContentObserver(mMainHandler).register(
                mContext.getContentResolver());
        disableAccessibilityMenuToMigrateIfNeeded();
    }

    @Override
@@ -855,91 +852,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
    }

    /**
     * Migrates the Accessibility Menu to the version provided by the system build,
     * if necessary based on presence of the service on the device.
     * Disables the component returned by
     * {@link AccessibilityUtils#getAccessibilityMenuComponentToMigrate} so that it does not appear
     * in Settings or other places that query for installed accessibility services.
     *
     * <p>
     * SettingsProvider is responsible for migrating users off of Menu-outside-system,
     * which it performs in its initialization before AccessibilityManagerService is started.
     * </p>
     */
    @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;
    private void disableAccessibilityMenuToMigrateIfNeeded() {
        int userId;
        synchronized (mLock) {
            userId = mCurrentUserId;
        }

        // Hide Menu-outside-system so that it does not appear in Settings.
        final ComponentName menuToMigrate =
                AccessibilityUtils.getAccessibilityMenuComponentToMigrate(mPackageManager, userId);
        if (menuToMigrate != null) {
            mPackageManager.setComponentEnabledSetting(
                menuOutsideSystem,
                    menuToMigrate,
                    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);
        }
    }

@@ -1712,8 +1645,6 @@ 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
+0 −114

File changed.

Preview size limit exceeded, changes collapsed.