From 19a301ed8486a3866e134d24ad962d6d4138b196 Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Mon, 6 Feb 2023 10:50:30 -0800 Subject: [PATCH 1/2] Extracts out A11yMenu Settings migration logic to AccessibilityUtils. Settings migrations should be done by SettingsProvider. Bug: 261252772 Test: atest AccessibilityUtilsTest Change-Id: Iaabb0eb3b6f21b7752da2a324ccf4d5955847833 --- .../accessibility/AccessibilityManager.java | 11 -- .../util/AccessibilityUtils.java | 71 +++++++++++ .../accessibility/AccessibilityUtilsTest.java | 106 ++++++++++++++-- .../AccessibilityManagerService.java | 111 ++++------------- .../AccessibilityManagerServiceTest.java | 114 ------------------ 5 files changed, 190 insertions(+), 223 deletions(-) diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 79695186ace7c..d405c0b5b24fb 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -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(); diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java index 6a976efa5ef92..3a8f427d54bc5 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java @@ -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}. + * + *

+ * Requirements: + *

  • There are exactly two installed accessibility service components with class name + * {@link #MENU_SERVICE_RELATIVE_CLASS_NAME}.
  • + *
  • Exactly one of these components is equal to {@link #ACCESSIBILITY_MENU_IN_SYSTEM}.
  • + *

    + * + * @return The {@link ComponentName} of the service that is not {@link + * #ACCESSIBILITY_MENU_IN_SYSTEM}, + * or null if the above requirements are not met. + */ + @Nullable + public static ComponentName getAccessibilityMenuComponentToMigrate( + PackageManager packageManager, int userId) { + final Set menuComponentNames = findA11yMenuComponentNames(packageManager, + userId); + Optional 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 findA11yMenuComponentNames( + PackageManager packageManager, int userId) { + Set 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; + } } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java index 045b3a27456f6..3ea7f471e4bda 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java @@ -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; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 776405d23035e..945cd3f72b1a9 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -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. + * + *

    + * SettingsProvider is responsible for migrating users off of Menu-outside-system, + * which it performs in its initialization before AccessibilityManagerService is started. + *

    */ - @VisibleForTesting - void migrateAccessibilityMenuIfNecessaryLocked(AccessibilityUserState userState) { - final Set 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. - 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 findA11yMenuComponentNamesLocked() { - Set 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 menuComponentNames) { - Optional menuOutsideSystem = menuComponentNames.stream().filter( - name -> !name.equals(ACCESSIBILITY_MENU_IN_SYSTEM)).findFirst(); - if (menuOutsideSystem.isEmpty()) { - return null; - } - return menuOutsideSystem.get(); - } - - /** - * Replaces toRemove 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); + final ComponentName menuToMigrate = + AccessibilityUtils.getAccessibilityMenuComponentToMigrate(mPackageManager, userId); + if (menuToMigrate != null) { + mPackageManager.setComponentEnabledSetting( + menuToMigrate, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); } } @@ -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 diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 4d1d2b22ed345..77831dda9d9c9 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -16,19 +16,13 @@ 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; @@ -99,7 +93,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.List; /** * APCT tests for {@link AccessibilityManagerService}. @@ -523,113 +516,6 @@ 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); From 8e94007250678350e770f2f67974ffb851000c45 Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Mon, 6 Feb 2023 11:21:59 -0800 Subject: [PATCH 2/2] Migrates to the new component name for A11yMenu in SettingsProvider. Bug: 261252772 Test: Enable Menu-in-AAS on device, with shortcuts enabled; Ensure device's SettingsProvider database version is smaller than SETTINGS_VERSION; 'adb sync' the new SettingsProvider and reboot; Observe that all A11yMenu settings are migrated; Change-Id: Ia8abb81694569d9701c8c02bf61676d35035020c --- .../providers/settings/SettingsProvider.java | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index a9c0f00788ae8..f1413e54f16de 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -33,6 +33,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; +import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM; import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX; import android.Manifest; @@ -107,6 +108,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; +import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; @@ -3712,7 +3714,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 213; + private static final int SETTINGS_VERSION = 214; private final int mUserId; @@ -5646,6 +5648,27 @@ public class SettingsProvider extends ContentProvider { currentVersion = 213; } + if (currentVersion == 213) { + final ComponentName accessibilityMenuToMigrate = + AccessibilityUtils.getAccessibilityMenuComponentToMigrate( + getContext().getPackageManager(), userId); + if (accessibilityMenuToMigrate != null) { + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final String toRemove = accessibilityMenuToMigrate.flattenToString(); + final String toAdd = ACCESSIBILITY_MENU_IN_SYSTEM.flattenToString(); + // Migrate the accessibility shortcuts and enabled state. + migrateColonDelimitedStringSettingLocked(secureSettings, + Secure.ACCESSIBILITY_BUTTON_TARGETS, toRemove, toAdd); + migrateColonDelimitedStringSettingLocked(secureSettings, + Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, toRemove, toAdd); + migrateColonDelimitedStringSettingLocked(secureSettings, + Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, toRemove, toAdd); + migrateColonDelimitedStringSettingLocked(secureSettings, + Secure.ENABLED_ACCESSIBILITY_SERVICES, toRemove, toAdd); + } + currentVersion = 214; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { @@ -5867,6 +5890,22 @@ public class SettingsProvider extends ContentProvider { return items; } + @GuardedBy("mLock") + private void migrateColonDelimitedStringSettingLocked(SettingsState settingsState, + String setting, String toRemove, String toAdd) { + final Set componentNames = transformColonDelimitedStringToSet( + settingsState.getSettingLocked(setting).getValue()); + if (componentNames != null && componentNames.contains(toRemove)) { + componentNames.remove(toRemove); + componentNames.add(toAdd); + settingsState.insertSettingLocked( + setting, + TextUtils.join(":", componentNames), + null /* tag */, false /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + private boolean isAccessibilityButtonInNavigationBarOn(SettingsState secureSettings) { return hasValueInA11yButtonTargets(secureSettings) && !isGestureNavigateEnabled(); }