Adjusts the logic of onPackageForceStop to return the proper values

The fxn should return true only if any force-stopped packages are enabled
and doit=false (this indicates something else should handle the stop).
In all other cases, the fxn should return false.

Test: atest AccessibilityManagerService
Bug: 337392123
Flag: aconfig manager_package_monitor_logic_fix
Change-Id: I4a0ed813011e4b0cb7363b35305e0a5e20391a67
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 82579d8..0df92b4 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -138,6 +138,16 @@
 }
 
 flag {
+    name: "manager_package_monitor_logic_fix"
+    namespace: "accessibility"
+    description: "Corrects the return values of the HandleForceStop function"
+    bug: "337392123"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "pinch_zoom_zero_min_span"
     namespace: "accessibility"
     description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c70b641..40d3e9a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -697,7 +697,7 @@
 
     /**
      * Returns the lock object for any synchronized test blocks.
-     * Should not be used outside of testing.
+     * External classes should only use for testing.
      * @return lock object.
      */
     @VisibleForTesting
@@ -801,7 +801,7 @@
      *
      * @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,
+     * @return {@code true} if the lists of enabled services or buttons were changed,
      * {@code false} otherwise.
      */
     @VisibleForTesting
@@ -824,6 +824,7 @@
                     userState.getBindingServicesLocked().remove(comp);
                     userState.getCrashedServicesLocked().remove(comp);
                     enabledServicesChanged = true;
+                    break;
                 }
             }
         }
@@ -851,132 +852,14 @@
         return mPackageMonitor;
     }
 
+    @VisibleForTesting
+    void setPackageMonitor(PackageMonitor monitor) {
+        mPackageMonitor = monitor;
+    }
+
     private void registerBroadcastReceivers() {
-        mPackageMonitor = new PackageMonitor(true) {
-            @Override
-            public void onSomePackagesChanged() {
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER);
-                }
-
-                final int userId = getChangingUserId();
-                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
-                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
-                parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
-                parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
-                synchronized (mLock) {
-                    // Only the profile parent can install accessibility services.
-                    // Therefore we ignore packages from linked profiles.
-                    if (userId != mCurrentUserId) {
-                        return;
-                    }
-                    onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
-                            parsedAccessibilityShortcutInfos);
-                }
-            }
-
-            @Override
-            public void onPackageUpdateFinished(String packageName, int uid) {
-                // The package should already be removed from mBoundServices, and added into
-                // mBindingServices in binderDied() during updating. Remove services from  this
-                // package from mBindingServices, and then update the user state to re-bind new
-                // versions of them.
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onPackageUpdateFinished",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
-                            "packageName=" + packageName + ";uid=" + uid);
-                }
-                final int userId = getChangingUserId();
-                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
-                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
-                parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
-                parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
-                synchronized (mLock) {
-                    if (userId != mCurrentUserId) {
-                        return;
-                    }
-                    final AccessibilityUserState userState = getUserStateLocked(userId);
-                    final boolean reboundAService = userState.getBindingServicesLocked().removeIf(
-                            component -> component != null
-                                    && component.getPackageName().equals(packageName))
-                            || userState.mCrashedServices.removeIf(component -> component != null
-                                    && component.getPackageName().equals(packageName));
-                    // Reloads the installed services info to make sure the rebound service could
-                    // get a new one.
-                    userState.mInstalledServices.clear();
-                    final boolean configurationChanged;
-                    configurationChanged = readConfigurationForUserStateLocked(userState,
-                            parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
-                    if (reboundAService || configurationChanged) {
-                        onUserStateChangedLocked(userState);
-                    }
-                    // Passing 0 for restoreFromSdkInt to have this migration check execute each
-                    // time. It can make sure a11y button settings are correctly if there's an a11y
-                    // service updated and modifies the a11y button configuration.
-                    migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName,
-                            /* restoreFromSdkInt = */0);
-                }
-            }
-
-            @Override
-            public void onPackageRemoved(String packageName, int uid) {
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
-                            "packageName=" + packageName + ";uid=" + uid);
-                }
-
-                synchronized (mLock) {
-                    final int userId = getChangingUserId();
-                    // Only the profile parent can install accessibility services.
-                    // Therefore we ignore packages from linked profiles.
-                    if (userId != mCurrentUserId) {
-                        return;
-                    }
-                    onPackageRemovedLocked(packageName);
-                }
-            }
-
-            /**
-             * 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) {
-                if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop",
-                            FLAGS_PACKAGE_BROADCAST_RECEIVER,
-                            "intent=" + intent + ";packages=" + Arrays.toString(packages)
-                            + ";uid=" + uid + ";doit=" + doit);
-                }
-                synchronized (mLock) {
-                    final int userId = getChangingUserId();
-                    // Only the profile parent can install accessibility services.
-                    // Therefore we ignore packages from linked profiles.
-                    if (userId != mCurrentUserId) {
-                        return false;
-                    }
-                    final AccessibilityUserState userState = getUserStateLocked(userId);
-
-                    if (doit && onPackagesForceStoppedLocked(packages, userState)) {
-                        onUserStateChangedLocked(userState);
-                        return false;
-                    } else {
-                        return true;
-                    }
-                }
-            }
-        };
-
         // package changes
+        mPackageMonitor = new ManagerPackageMonitor(this);
         mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);
 
         // user change and unlock
@@ -992,7 +875,9 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_USER_BROADCAST_RECEIVER)) {
-                    mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", FLAGS_USER_BROADCAST_RECEIVER,
+                    mTraceManager.logTrace(
+                            LOG_TAG + ".BR.onReceive",
+                            FLAGS_USER_BROADCAST_RECEIVER,
                             "context=" + context + ";intent=" + intent);
                 }
 
@@ -1045,7 +930,8 @@
                 setNonA11yToolNotificationToMatchSafetyCenter();
             }
         };
-        mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler,
+        mContext.registerReceiverAsUser(
+                receiver, UserHandle.ALL, filter, null, mMainHandler,
                 Context.RECEIVER_EXPORTED);
 
         if (!android.companion.virtual.flags.Flags.vdmPublicApis()) {
@@ -6223,6 +6109,162 @@
         }
     }
 
+    @VisibleForTesting
+    public static class ManagerPackageMonitor extends PackageMonitor {
+        private final AccessibilityManagerService mManagerService;
+        public ManagerPackageMonitor(AccessibilityManagerService managerService) {
+            super(/* supportsPackageRestartQuery = */ true);
+            mManagerService = managerService;
+        }
+
+        @Override
+        public void onSomePackagesChanged() {
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER);
+            }
+
+            final int userId = getChangingUserId();
+            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService
+                    .parseAccessibilityServiceInfos(userId);
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = mManagerService
+                    .parseAccessibilityShortcutInfos(userId);
+            synchronized (mManagerService.getLock()) {
+                // Only the profile parent can install accessibility services.
+                // Therefore we ignore packages from linked profiles.
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return;
+                }
+                mManagerService.onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+                        parsedAccessibilityShortcutInfos);
+            }
+        }
+
+        @Override
+        public void onPackageUpdateFinished(String packageName, int uid) {
+            // The package should already be removed from mBoundServices, and added into
+            // mBindingServices in binderDied() during updating. Remove services from  this
+            // package from mBindingServices, and then update the user state to re-bind new
+            // versions of them.
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(
+                        LOG_TAG + ".PM.onPackageUpdateFinished",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER,
+                        "packageName=" + packageName + ";uid=" + uid);
+            }
+            final int userId = getChangingUserId();
+            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService
+                    .parseAccessibilityServiceInfos(userId);
+            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos =
+                    mManagerService.parseAccessibilityShortcutInfos(userId);
+            synchronized (mManagerService.getLock()) {
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return;
+                }
+                final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId);
+                final boolean reboundAService = userState.getBindingServicesLocked().removeIf(
+                        component -> component != null
+                                && component.getPackageName().equals(packageName))
+                        || userState.mCrashedServices.removeIf(component -> component != null
+                        && component.getPackageName().equals(packageName));
+                // Reloads the installed services info to make sure the rebound service could
+                // get a new one.
+                userState.mInstalledServices.clear();
+                final boolean configurationChanged;
+                configurationChanged = mManagerService.readConfigurationForUserStateLocked(
+                        userState, parsedAccessibilityServiceInfos,
+                        parsedAccessibilityShortcutInfos);
+                if (reboundAService || configurationChanged) {
+                    mManagerService.onUserStateChangedLocked(userState);
+                }
+                // Passing 0 for restoreFromSdkInt to have this migration check execute each
+                // time. It can make sure a11y button settings are correctly if there's an a11y
+                // service updated and modifies the a11y button configuration.
+                mManagerService.migrateAccessibilityButtonSettingsIfNecessaryLocked(
+                        userState, packageName, /* restoreFromSdkInt = */0);
+            }
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER,
+                        "packageName=" + packageName + ";uid=" + uid);
+            }
+
+            synchronized (mManagerService.getLock()) {
+                final int userId = getChangingUserId();
+                // Only the profile parent can install accessibility services.
+                // Therefore we ignore packages from linked profiles.
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return;
+                }
+                mManagerService.onPackageRemovedLocked(packageName);
+            }
+        }
+
+        /**
+         * 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 doit == {@code false}
+         * and at least one of the provided packages is enabled.
+         * In any other case, returns {@code false}.
+         * This is to indicate whether further action is necessary.
+         */
+        @Override
+        public boolean onHandleForceStop(Intent intent, String[] packages,
+                int uid, boolean doit) {
+            if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes(
+                    FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+                mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop",
+                        FLAGS_PACKAGE_BROADCAST_RECEIVER,
+                        "intent=" + intent + ";packages=" + Arrays.toString(packages)
+                                + ";uid=" + uid + ";doit=" + doit);
+            }
+            synchronized (mManagerService.getLock()) {
+                final int userId = getChangingUserId();
+                // Only the profile parent can install accessibility services.
+                // Therefore we ignore packages from linked profiles.
+                if (userId != mManagerService.getCurrentUserIdLocked()) {
+                    return false;
+                }
+                final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId);
+
+                if (Flags.managerPackageMonitorLogicFix()) {
+                    if (!doit) {
+                        // if we're not handling the stop here, then we only need to know
+                        // if any of the force-stopped packages are currently enabled.
+                        return userState.mEnabledServices.stream().anyMatch(
+                                (comp) -> Arrays.stream(packages).anyMatch(
+                                        (pkg) -> pkg.equals(comp.getPackageName()))
+                        );
+                    } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
+                        mManagerService.onUserStateChangedLocked(userState);
+                    }
+                    return false;
+                } else {
+                    // this old logic did not properly indicate when base packageMonitor's routine
+                    // should handle stopping the package.
+                    if (doit && mManagerService.onPackagesForceStoppedLocked(packages, userState)) {
+                        mManagerService.onUserStateChangedLocked(userState);
+                        return false;
+                    } else {
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+
     void sendPendingWindowStateChangedEventsForAvailableWindowLocked(int windowId) {
         final int eventSize =  mSendWindowStateChangedEventRunnables.size();
         for (int i = eventSize - 1; i >= 0; i--) {
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 cb4fc75..ca15aa2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -30,7 +30,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,6 +44,7 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -102,6 +105,7 @@
 import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.accessibility.util.ShortcutUtils;
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.content.PackageMonitor;
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController;
@@ -1620,6 +1624,67 @@
                 .containsExactlyElementsIn(Set.of(daltonizerTile));
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
+    public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() {
+        setupShortcutTargetServices();
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mEnabledServices.addAll(
+                userState.mInstalledServices.stream().map(
+                        (AccessibilityServiceInfo::getComponentName)).toList());
+        String[] packages = userState.mEnabledServices.stream().map(
+                ComponentName::getPackageName).toList().toArray(new String[0]);
+
+        PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
+        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        mA11yms.setPackageMonitor(monitor);
+
+        assertTrue(mA11yms.getPackageMonitor().onHandleForceStop(
+                new Intent(),
+                packages,
+                UserHandle.USER_SYSTEM,
+                false
+        ));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
+    public void onHandleForceStop_doIt_packageEnabled_returnsFalse() {
+        setupShortcutTargetServices();
+        AccessibilityUserState userState = mA11yms.getCurrentUserState();
+        userState.mEnabledServices.addAll(
+                userState.mInstalledServices.stream().map(
+                        (AccessibilityServiceInfo::getComponentName)).toList());
+        String[] packages = userState.mEnabledServices.stream().map(
+                ComponentName::getPackageName).toList().toArray(new String[0]);
+
+        PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
+        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        mA11yms.setPackageMonitor(monitor);
+
+        assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
+                new Intent(),
+                packages,
+                UserHandle.USER_SYSTEM,
+                true
+        ));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX)
+    public void onHandleForceStop_dontDoIt_packageNotEnabled_returnsFalse() {
+        PackageMonitor monitor = spy(mA11yms.getPackageMonitor());
+        when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM);
+        mA11yms.setPackageMonitor(monitor);
+
+        assertFalse(mA11yms.getPackageMonitor().onHandleForceStop(
+                new Intent(),
+                new String[]{ "FOO", "BAR"},
+                UserHandle.USER_SYSTEM,
+                false
+        ));
+    }
+
     private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
             ComponentName componentName) {
         return mockAccessibilityServiceInfo(
@@ -1630,7 +1695,7 @@
             ComponentName componentName,
             boolean isSystemApp, boolean isAlwaysOnService) {
         AccessibilityServiceInfo accessibilityServiceInfo =
-                Mockito.spy(new AccessibilityServiceInfo());
+                spy(new AccessibilityServiceInfo());
         accessibilityServiceInfo.setComponentName(componentName);
         ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class);
         when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo);