Skip to content
Snippets Groups Projects
Commit 0fb119c6 authored by Riley Jones's avatar Riley Jones
Browse files

A11yManagerService turns off button targets for services whose packages have forcibly stopped.

Change involves reorganizing relevant code so that it is testable.
Exposed code is documented.

Feature flag:
-namespace: accessibility
-flag: com.android.server.accessibility.disable_continuous_shortcut_on_force_stop

Bug: 198018180
Test: atest A11yManagerServiceTest
Change-Id: Ic54591590af402115607f2d0f524276d1ec365f7
parent 170208c1
No related branches found
No related tags found
No related merge requests found
......@@ -42,6 +42,13 @@ flag {
bug: "295327792"
}
flag {
name: "disable_continuous_shortcut_on_force_stop"
namespace: "accessibility"
description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
bug: "198018180"
}
flag {
name: "deprecate_package_list_observer"
namespace: "accessibility"
......
......@@ -16,6 +16,7 @@
package com.android.server.accessibility;
import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
......@@ -182,6 +183,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
......@@ -650,6 +652,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
/**
* Returns the lock object for any synchronized test blocks.
* Should not be used outside of testing.
* @return lock object.
*/
@VisibleForTesting
Object getLock() {
return mLock;
}
AccessibilityUserState getCurrentUserState() {
synchronized (mLock) {
return getCurrentUserStateLocked();
......@@ -746,6 +758,62 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
/**
* Handles a package or packages being force stopped.
* Will disable any relevant services,
* and remove any button targets of continuous services,
* denoted by {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON}.
* If the result is {@code true},
* then {@link AccessibilityManagerService#onUserStateChangedLocked(
* AccessibilityUserState, boolean)} should be called afterwards.
*
* @param packages list of packages that have stopped.
* @param userState user state to be read & modified.
* @return {@code true} if a service was enabled or a button target was removed,
* {@code false} otherwise.
*/
@VisibleForTesting
boolean onPackagesForceStoppedLocked(
String[] packages, AccessibilityUserState userState) {
final List<String> continuousServicePackages =
userState.mInstalledServices.stream().filter(service ->
(service.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
== FLAG_REQUEST_ACCESSIBILITY_BUTTON
).map(service -> service.getComponentName().flattenToString()).toList();
boolean enabledServicesChanged = false;
final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
final ComponentName comp = it.next();
final String compPkg = comp.getPackageName();
for (String pkg : packages) {
if (compPkg.equals(pkg)) {
it.remove();
userState.getBindingServicesLocked().remove(comp);
userState.getCrashedServicesLocked().remove(comp);
enabledServicesChanged = true;
}
}
}
if (enabledServicesChanged) {
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mEnabledServices, userState.mUserId);
}
boolean buttonTargetsChanged = userState.mAccessibilityButtonTargets.removeIf(
target -> continuousServicePackages.stream().anyMatch(
pkg -> Objects.equals(target, pkg)));
if (buttonTargetsChanged) {
persistColonDelimitedSetToSettingLocked(
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
userState.mUserId,
userState.mAccessibilityButtonTargets, str -> str);
}
return enabledServicesChanged || buttonTargetsChanged;
}
@VisibleForTesting
PackageMonitor getPackageMonitor() {
return mPackageMonitor;
......@@ -850,6 +918,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
/**
* Handles instances in which a package or packages have forcibly stopped.
*
* @param intent intent containing package event information.
* @param uid linux process user id (different from Android user id).
* @param packages array of package names that have stopped.
* @param doit whether to try and handle the stop or just log the trace.
*
* @return {@code true} if package should be restarted, {@code false} otherwise.
*/
@Override
public boolean onHandleForceStop(Intent intent, String[] packages,
int uid, boolean doit) {
......@@ -867,26 +945,36 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return false;
}
final AccessibilityUserState userState = getUserStateLocked(userId);
final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
final ComponentName comp = it.next();
final String compPkg = comp.getPackageName();
for (String pkg : packages) {
if (compPkg.equals(pkg)) {
if (!doit) {
return true;
if (Flags.disableContinuousShortcutOnForceStop()) {
if (doit && onPackagesForceStoppedLocked(packages, userState)) {
onUserStateChangedLocked(userState);
return false;
} else {
return true;
}
} else {
final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
final ComponentName comp = it.next();
final String compPkg = comp.getPackageName();
for (String pkg : packages) {
if (compPkg.equals(pkg)) {
if (!doit) {
return true;
}
it.remove();
userState.getBindingServicesLocked().remove(comp);
userState.getCrashedServicesLocked().remove(comp);
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mEnabledServices, userId);
onUserStateChangedLocked(userState);
}
it.remove();
userState.getBindingServicesLocked().remove(comp);
userState.getCrashedServicesLocked().remove(comp);
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mEnabledServices, userId);
onUserStateChangedLocked(userState);
}
}
return false;
}
return false;
}
}
};
......@@ -2452,7 +2540,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* @param userId The user id.
* @param outComponentNames The output component names.
*/
private void readComponentNamesFromSettingLocked(String settingName, int userId,
@VisibleForTesting
void readComponentNamesFromSettingLocked(String settingName, int userId,
Set<ComponentName> outComponentNames) {
readColonDelimitedSettingToSet(settingName, userId,
str -> ComponentName.unflattenFromString(str), outComponentNames);
......@@ -2481,7 +2570,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
componentName -> componentName.flattenToShortString());
}
private <T> void readColonDelimitedSettingToSet(String settingName, int userId,
/**
* Reads a colon delimited setting,
* passes the values through a function,
* then stores the values in a provided set.
*
* @param settingName Name of setting.
* @param userId user id corresponding to setting.
* @param toItem function mapping values to the output set.
* @param outSet output set to write to.
* @param <T> type of output set.
*/
@VisibleForTesting
<T> void readColonDelimitedSettingToSet(String settingName, int userId,
Function<String, T> toItem, Set<T> outSet) {
final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
settingName, userId);
......@@ -3472,7 +3573,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return true;
}
final boolean requestA11yButton = (serviceInfo.flags
& AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
& FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) {
// An a11y service targeting sdk version > Q and request A11y button and is assigned
// to a11y btn should be in the enabled list.
......@@ -3773,7 +3874,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final int targetSdk = installedServiceInfo.getResolveInfo()
.serviceInfo.applicationInfo.targetSdkVersion;
final boolean requestA11yButton = (installedServiceInfo.flags
& AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
& FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
// Turns on / off the accessibility service
if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
|| (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
......
......@@ -16,6 +16,7 @@
package com.android.server.accessibility;
import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
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;
......@@ -68,6 +69,7 @@ import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableContext;
import android.util.ArraySet;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
......@@ -599,6 +601,74 @@ public class AccessibilityManagerServiceTest {
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
}
@Test
public void testPackagesForceStopped_disablesRelevantService() {
final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
info_a.setComponentName(COMPONENT_NAME);
final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
info_b.setComponentName(new ComponentName("package", "class"));
AccessibilityUserState userState = mA11yms.getCurrentUserState();
userState.mInstalledServices.clear();
userState.mInstalledServices.add(info_a);
userState.mInstalledServices.add(info_b);
userState.mEnabledServices.clear();
userState.mEnabledServices.add(info_a.getComponentName());
userState.mEnabledServices.add(info_b.getComponentName());
synchronized (mA11yms.getLock()) {
mA11yms.onPackagesForceStoppedLocked(
new String[]{info_a.getComponentName().getPackageName()}, userState);
}
//Assert user state change
userState = mA11yms.getCurrentUserState();
assertThat(userState.mEnabledServices).containsExactly(info_b.getComponentName());
//Assert setting change
final Set<ComponentName> componentsFromSetting = new ArraySet<>();
mA11yms.readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mUserId, componentsFromSetting);
assertThat(componentsFromSetting).containsExactly(info_b.getComponentName());
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_DISABLE_CONTINUOUS_SHORTCUT_ON_FORCE_STOP)
public void testPackagesForceStopped_fromContinuousService_removesButtonTarget() {
final AccessibilityServiceInfo info_a = new AccessibilityServiceInfo();
info_a.setComponentName(COMPONENT_NAME);
info_a.flags = FLAG_REQUEST_ACCESSIBILITY_BUTTON;
final AccessibilityServiceInfo info_b = new AccessibilityServiceInfo();
info_b.setComponentName(new ComponentName("package", "class"));
AccessibilityUserState userState = mA11yms.getCurrentUserState();
userState.mInstalledServices.clear();
userState.mInstalledServices.add(info_a);
userState.mInstalledServices.add(info_b);
userState.mAccessibilityButtonTargets.clear();
userState.mAccessibilityButtonTargets.add(info_a.getComponentName().flattenToString());
userState.mAccessibilityButtonTargets.add(info_b.getComponentName().flattenToString());
// despite force stopping both packages, only the first service has the relevant flag,
// so only the first should be removed.
synchronized (mA11yms.getLock()) {
mA11yms.onPackagesForceStoppedLocked(
new String[]{
info_a.getComponentName().getPackageName(),
info_b.getComponentName().getPackageName()},
userState);
}
//Assert user state change
userState = mA11yms.getCurrentUserState();
assertThat(userState.mAccessibilityButtonTargets).containsExactly(
info_b.getComponentName().flattenToString());
//Assert setting change
final Set<String> targetsFromSetting = new ArraySet<>();
mA11yms.readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
userState.mUserId, str -> str, targetsFromSetting);
assertThat(targetsFromSetting).containsExactly(info_b.getComponentName().flattenToString());
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
// Test old behavior to validate lock detection for the old (locked access) case.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment