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

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
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line Diff line number Diff line
@@ -42,6 +42,13 @@ flag {
    bug: "295327792"
    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 {
flag {
    name: "deprecate_package_list_observer"
    name: "deprecate_package_list_observer"
    namespace: "accessibility"
    namespace: "accessibility"
+121 −20
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.server.accessibility;
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;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_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.Iterator;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
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() {
    AccessibilityUserState getCurrentUserState() {
        synchronized (mLock) {
        synchronized (mLock) {
            return getCurrentUserStateLocked();
            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
    @VisibleForTesting
    PackageMonitor getPackageMonitor() {
    PackageMonitor getPackageMonitor() {
        return mPackageMonitor;
        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
            @Override
            public boolean onHandleForceStop(Intent intent, String[] packages,
            public boolean onHandleForceStop(Intent intent, String[] packages,
                    int uid, boolean doit) {
                    int uid, boolean doit) {
@@ -867,6 +945,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                        return false;
                        return false;
                    }
                    }
                    final AccessibilityUserState userState = getUserStateLocked(userId);
                    final AccessibilityUserState userState = getUserStateLocked(userId);

                    if (Flags.disableContinuousShortcutOnForceStop()) {
                        if (doit && onPackagesForceStoppedLocked(packages, userState)) {
                            onUserStateChangedLocked(userState);
                            return false;
                        } else {
                            return true;
                        }
                    } else {
                        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
                        final Iterator<ComponentName> it = userState.mEnabledServices.iterator();
                        while (it.hasNext()) {
                        while (it.hasNext()) {
                            final ComponentName comp = it.next();
                            final ComponentName comp = it.next();
@@ -889,6 +976,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                        return false;
                        return false;
                    }
                    }
                }
                }
            }
        };
        };


        // package changes
        // package changes
@@ -2452,7 +2540,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
     * @param userId The user id.
     * @param userId The user id.
     * @param outComponentNames The output component names.
     * @param outComponentNames The output component names.
     */
     */
    private void readComponentNamesFromSettingLocked(String settingName, int userId,
    @VisibleForTesting
    void readComponentNamesFromSettingLocked(String settingName, int userId,
            Set<ComponentName> outComponentNames) {
            Set<ComponentName> outComponentNames) {
        readColonDelimitedSettingToSet(settingName, userId,
        readColonDelimitedSettingToSet(settingName, userId,
                str -> ComponentName.unflattenFromString(str), outComponentNames);
                str -> ComponentName.unflattenFromString(str), outComponentNames);
@@ -2481,7 +2570,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                componentName -> componentName.flattenToShortString());
                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) {
            Function<String, T> toItem, Set<T> outSet) {
        final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
        final String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                settingName, userId);
                settingName, userId);
@@ -3472,7 +3573,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
                return true;
                return true;
            }
            }
            final boolean requestA11yButton = (serviceInfo.flags
            final boolean requestA11yButton = (serviceInfo.flags
                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
                    & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
            if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) {
            if (requestA11yButton && !userState.mEnabledServices.contains(componentName)) {
                // An a11y service targeting sdk version > Q and request A11y button and is assigned
                // An a11y service targeting sdk version > Q and request A11y button and is assigned
                // to a11y btn should be in the enabled list.
                // to a11y btn should be in the enabled list.
@@ -3773,7 +3874,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
            final int targetSdk = installedServiceInfo.getResolveInfo()
            final int targetSdk = installedServiceInfo.getResolveInfo()
                    .serviceInfo.applicationInfo.targetSdkVersion;
                    .serviceInfo.applicationInfo.targetSdkVersion;
            final boolean requestA11yButton = (installedServiceInfo.flags
            final boolean requestA11yButton = (installedServiceInfo.flags
                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
                    & FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
            // Turns on / off the accessibility service
            // Turns on / off the accessibility service
            if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
            if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
                    || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
                    || (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
+70 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.server.accessibility;
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_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
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_NONE;
@@ -68,6 +69,7 @@ import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.provider.Settings;
import android.testing.TestableContext;
import android.testing.TestableContext;
import android.util.ArraySet;
import android.view.Display;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.DisplayInfo;
@@ -599,6 +601,74 @@ public class AccessibilityManagerServiceTest {
                ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
                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
    @Test
    @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
    @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
    // Test old behavior to validate lock detection for the old (locked access) case.
    // Test old behavior to validate lock detection for the old (locked access) case.