Merge "Fix user cleanup in modern broadcast queue."
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8f241b2..15dfd72 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1341,7 +1341,7 @@
             @NonNull BroadcastPredicate broadcastPredicate,
             @NonNull BroadcastConsumer broadcastConsumer, boolean andRemove) {
         boolean didSomething = false;
-        for (int i = 0; i < mProcessQueues.size(); i++) {
+        for (int i = mProcessQueues.size() - 1; i >= 0; i--) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
                 if (queuePredicate.test(leaf)) {
@@ -1363,7 +1363,7 @@
     private void forEachMatchingQueue(
             @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
             @NonNull Consumer<BroadcastProcessQueue> queueConsumer) {
-        for (int i = 0; i < mProcessQueues.size(); i++) {
+        for (int i = mProcessQueues.size() - 1; i >= 0; i--) {
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
                 if (queuePredicate.test(leaf)) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 89077e3..77127c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -25,7 +25,10 @@
 import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_RESULT_TO;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
+import static com.android.server.am.BroadcastQueueTest.CLASS_BLUE;
 import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN;
+import static com.android.server.am.BroadcastQueueTest.CLASS_RED;
+import static com.android.server.am.BroadcastQueueTest.CLASS_YELLOW;
 import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE;
 import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN;
 import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED;
@@ -981,6 +984,40 @@
         assertTrue(queue.isEmpty());
     }
 
+    @Test
+    public void testCleanupDisabledPackageReceiversLocked() {
+        final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT);
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+        final BroadcastRecord record1 = makeBroadcastRecord(userPresent, List.of(
+                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                makeManifestReceiver(PACKAGE_RED, CLASS_BLUE),
+                makeManifestReceiver(PACKAGE_YELLOW, CLASS_RED),
+                makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)
+        ));
+        final BroadcastRecord record2 = makeBroadcastRecord(timeTick, List.of(
+                makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+                makeManifestReceiver(PACKAGE_RED, CLASS_RED),
+                makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)
+        ));
+
+        // Halt all processing so that we get a consistent view
+        mHandlerThread.getLooper().getQueue().postSyncBarrier();
+        mImpl.enqueueBroadcastLocked(record1);
+        mImpl.enqueueBroadcastLocked(record2);
+
+        mImpl.cleanupDisabledPackageReceiversLocked(null, null, UserHandle.USER_SYSTEM);
+
+        // Verify that all receivers have been marked as "skipped".
+        for (BroadcastRecord record : new BroadcastRecord[] {record1, record2}) {
+            for (int i = 0; i < record.receivers.size(); ++i) {
+                final String errMsg = "Unexpected delivery state for record:" + record
+                        + "; receiver=" + record.receivers.get(i);
+                assertEquals(errMsg, BroadcastRecord.DELIVERY_SKIPPED, record.getDeliveryState(i));
+            }
+        }
+    }
+
     private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
         final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
         packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 6800d52..b6a53ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -805,14 +806,27 @@
                     null, null, null, null, null, null, userId, null));
     }
 
+    private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app,
+            int userId) throws Exception {
+        verify(app.getThread(), mode).scheduleReceiverList(
+                manifestReceiver(null,
+                        null, null, null, null, null, null, userId, null));
+    }
+
     private void verifyScheduleRegisteredReceiver(ProcessRecord app,
-            Intent intent)
-            throws Exception {
+            Intent intent) throws Exception {
         verify(app.getThread()).scheduleReceiverList(
             registeredReceiver(null, filterEqualsIgnoringComponent(intent),
                     null, null, null, null, null, UserHandle.USER_SYSTEM, null));
     }
 
+    private void verifyScheduleRegisteredReceiver(VerificationMode mode, ProcessRecord app,
+            int userId) throws Exception {
+        verify(app.getThread(), mode).scheduleReceiverList(
+                registeredReceiver(null, null,
+                        null, null, null, null, null, userId, null));
+    }
+
     static final int USER_GUEST = 11;
 
     static final String PACKAGE_ANDROID = "android";
@@ -1222,6 +1236,45 @@
                 new ComponentName(PACKAGE_GREEN, CLASS_BLUE));
     }
 
+    @Test
+    public void testCleanup_userRemoved() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(
+                PACKAGE_RED, PACKAGE_RED, ProcessBehavior.NORMAL,
+                USER_GUEST);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        final Intent timeZone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        try (SyncBarrier b = new SyncBarrier()) {
+            enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST, new ArrayList<>(
+                    List.of(makeRegisteredReceiver(callerApp),
+                            makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
+                            makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
+                            makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
+            enqueueBroadcast(makeBroadcastRecord(timeZone, callerApp, USER_GUEST, new ArrayList<>(
+                    List.of(makeRegisteredReceiver(callerApp),
+                            makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
+                            makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
+                            makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
+
+            synchronized (mAms) {
+                mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
+            }
+        }
+
+        waitForIdle();
+        // Legacy stack does not remove registered receivers as part of
+        // cleanUpDisabledPackageReceiversLocked() call, so verify this only on modern queue.
+        if (mImpl == Impl.MODERN) {
+            verifyScheduleReceiver(never(), callerApp, USER_GUEST);
+            verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
+        }
+        for (String pkg : new String[] {
+                PACKAGE_GREEN, PACKAGE_BLUE, PACKAGE_YELLOW
+        }) {
+            assertNull(mAms.getProcessRecordLocked(pkg, getUidForPackage(pkg, USER_GUEST)));
+        }
+    }
+
     /**
      * Verify that killing a running process skips registered receivers.
      */