Merge "Migrates users to the AccessibilityMenu in system."
This commit is contained in:
committed by
Android (Google) Code Review
commit
6c884751d4
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user