Merge "Migrates users to the AccessibilityMenu in system."

This commit is contained in:
Daniel Norman
2023-01-24 23:33:00 +00:00
committed by Android (Google) Code Review
3 changed files with 221 additions and 0 deletions

View File

@@ -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();

View File

@@ -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

View File

@@ -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);