BroadcastQueue: remove matching broadcasts.

There are common broadcasts that should remove any still-pending
broadcasts of a particular flavor.  For example, sending SCREEN_ON
should remove any still-pending SCREEN_OFF broadcasts.

This change adds a BroadcastOption to support this use-case, along
with tests to validate, and starts wiring up the option to a handful
of straightforward broadcasts.

For this to work effectively, we need to actually remove skipped
events from our per-process queues, which means we need a more
robust way of finishing ordered broadcasts.  This change factors
out the "finishing" of each broadcast to a local setDeliveryState()
method which can then be invoked when a broadcast is removed.

Bug: 245771249
Test: atest FrameworksMockingServicesTests:BroadcastQueueTest
Change-Id: I8c81893841026074ef78d88f9ecc82518164e50e
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 5b61f9a..22f70ce 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -57,6 +57,8 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManagerInternal;
@@ -289,6 +291,7 @@
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     IBinder.DeathRecipient mListenerDeathRecipient;
     Intent mTimeTickIntent;
+    Bundle mTimeTickOptions;
     IAlarmListener mTimeTickTrigger;
     PendingIntent mDateChangeSender;
     boolean mInteractive = true;
@@ -1909,7 +1912,9 @@
                     Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND
                             | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-
+            mTimeTickOptions = BroadcastOptions
+                    .makeRemovingMatchingFilter(new IntentFilter(Intent.ACTION_TIME_TICK))
+                    .toBundle();
             mTimeTickTrigger = new IAlarmListener.Stub() {
                 @Override
                 public void doAlarm(final IAlarmCompleteListener callback) throws RemoteException {
@@ -1921,8 +1926,8 @@
                     // takes care of this automatically, but we're using the direct internal
                     // interface here rather than that client-side wrapper infrastructure.
                     mHandler.post(() -> {
-                        getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL);
-
+                        getContext().sendBroadcastAsUser(mTimeTickIntent, UserHandle.ALL, null,
+                                mTimeTickOptions);
                         try {
                             callback.alarmComplete(this);
                         } catch (RemoteException e) { /* local method call */ }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index dfef279..8ae16df6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4667,7 +4667,7 @@
      * @hide
      */
     public static void broadcastStickyIntent(Intent intent, int userId) {
-        broadcastStickyIntent(intent, AppOpsManager.OP_NONE, userId);
+        broadcastStickyIntent(intent, AppOpsManager.OP_NONE, null, userId);
     }
 
     /**
@@ -4676,11 +4676,20 @@
      * @hide
      */
     public static void broadcastStickyIntent(Intent intent, int appOp, int userId) {
+        broadcastStickyIntent(intent, appOp, null, userId);
+    }
+
+    /**
+     * Convenience for sending a sticky broadcast.  For internal use only.
+     *
+     * @hide
+     */
+    public static void broadcastStickyIntent(Intent intent, int appOp, Bundle options, int userId) {
         try {
             getService().broadcastIntentWithFeature(
                     null, null, intent, null, null, Activity.RESULT_OK, null, null,
                     null /*requiredPermissions*/, null /*excludedPermissions*/,
-                    null /*excludedPackages*/, appOp, null, false, true, userId);
+                    null /*excludedPackages*/, appOp, options, false, true, userId);
         } catch (RemoteException ex) {
         }
     }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index aa5fa5b..c2df802 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -27,12 +27,15 @@
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerExemptionManager.TempAllowListType;
 
+import java.util.Objects;
+
 /**
  * Helper class for building an options Bundle that can be used with
  * {@link android.content.Context#sendBroadcast(android.content.Intent)
@@ -55,6 +58,7 @@
     private boolean mRequireCompatChangeEnabled = true;
     private boolean mIsAlarmBroadcast = false;
     private long mIdForResponseEvent;
+    private @Nullable IntentFilter mRemoveMatchingFilter;
 
     /**
      * Change ID which is invalid.
@@ -180,11 +184,25 @@
     private static final String KEY_ID_FOR_RESPONSE_EVENT =
             "android:broadcast.idForResponseEvent";
 
+    /**
+     * Corresponds to {@link #setRemoveMatchingFilter}.
+     */
+    private static final String KEY_REMOVE_MATCHING_FILTER =
+            "android:broadcast.removeMatchingFilter";
+
     public static BroadcastOptions makeBasic() {
         BroadcastOptions opts = new BroadcastOptions();
         return opts;
     }
 
+    /** {@hide} */
+    public static @NonNull BroadcastOptions makeRemovingMatchingFilter(
+            @NonNull IntentFilter removeMatchingFilter) {
+        BroadcastOptions opts = new BroadcastOptions();
+        opts.setRemoveMatchingFilter(removeMatchingFilter);
+        return opts;
+    }
+
     private BroadcastOptions() {
         super();
         resetTemporaryAppAllowlist();
@@ -216,6 +234,8 @@
         mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
         mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
+        mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
+                IntentFilter.class);
     }
 
     /**
@@ -596,6 +616,29 @@
     }
 
     /**
+     * When enqueuing this broadcast, remove all pending broadcasts previously
+     * sent by this app which match the given filter.
+     * <p>
+     * For example, sending {@link Intent#ACTION_SCREEN_ON} would typically want
+     * to remove any pending {@link Intent#ACTION_SCREEN_OFF} broadcasts.
+     *
+     * @hide
+     */
+    public void setRemoveMatchingFilter(@NonNull IntentFilter removeMatchingFilter) {
+        mRemoveMatchingFilter = Objects.requireNonNull(removeMatchingFilter);
+    }
+
+    /** @hide */
+    public void clearRemoveMatchingFilter() {
+        mRemoveMatchingFilter = null;
+    }
+
+    /** @hide */
+    public @Nullable IntentFilter getRemoveMatchingFilter() {
+        return mRemoveMatchingFilter;
+    }
+
+    /**
      * Returns the created options as a Bundle, which can be passed to
      * {@link android.content.Context#sendBroadcast(android.content.Intent)
      * Context.sendBroadcast(Intent)} and related methods.
@@ -640,6 +683,9 @@
         if (mIdForResponseEvent != 0) {
             b.putLong(KEY_ID_FOR_RESPONSE_EVENT, mIdForResponseEvent);
         }
+        if (mRemoveMatchingFilter != null) {
+            b.putParcelable(KEY_REMOVE_MATCHING_FILTER, mRemoveMatchingFilter);
+        }
         return b.isEmpty() ? null : b;
     }
 }
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index b96d33c..4278b3e 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,9 +22,12 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.hardware.health.HealthInfo;
 import android.hardware.health.V2_1.BatteryCapacityLevel;
@@ -185,6 +188,17 @@
     private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
     private long mLastBatteryLevelChangedSentMs;
 
+    private Bundle mBatteryChangedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_BATTERY_CHANGED)).toBundle();
+    private Bundle mPowerConnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_POWER_DISCONNECTED)).toBundle();
+    private Bundle mPowerDisconnectedOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_POWER_CONNECTED)).toBundle();
+    private Bundle mBatteryLowOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_BATTERY_OKAY)).toBundle();
+    private Bundle mBatteryOkayOptions = BroadcastOptions.makeRemovingMatchingFilter(
+            new IntentFilter(Intent.ACTION_BATTERY_LOW)).toBundle();
+
     private MetricsLogger mMetricsLogger;
 
     public BatteryService(Context context) {
@@ -606,7 +620,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mPowerConnectedOptions);
                     }
                 });
             }
@@ -617,7 +632,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mPowerDisconnectedOptions);
                     }
                 });
             }
@@ -630,7 +646,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mBatteryLowOptions);
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
@@ -642,7 +659,8 @@
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL, null,
+                                mBatteryOkayOptions);
                     }
                 });
             }
@@ -712,7 +730,8 @@
                     + ", info:" + mHealthInfo.toString());
         }
 
-        mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL));
+        mHandler.post(() -> ActivityManager.broadcastStickyIntent(intent, AppOpsManager.OP_NONE,
+                mBatteryChangedOptions, UserHandle.USER_ALL));
     }
 
     private void sendBatteryLevelChangedIntentLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5ea3a01..ec30097 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17354,6 +17354,8 @@
                     bOptions.setTemporaryAppAllowlist(mInternal.getBootTimeTempAllowListDuration(),
                             TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
                             PowerExemptionManager.REASON_LOCALE_CHANGED, "");
+                    bOptions.setRemoveMatchingFilter(
+                            new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
                     broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
                             null, null, OP_NONE, bOptions.toBundle(), false, false, MY_PID,
                             SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(),
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 70fb7a3..77eefb4 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.am.BroadcastQueue.checkState;
 
-import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
@@ -28,12 +27,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
-import com.android.server.am.BroadcastRecord.DeliveryState;
 
 import java.util.ArrayDeque;
 import java.util.Iterator;
 import java.util.Objects;
-import java.util.function.BiPredicate;
 
 /**
  * Queue of pending {@link BroadcastRecord} entries intended for delivery to a
@@ -46,7 +43,11 @@
  * Internally each queue consists of a pending broadcasts which are waiting to
  * be dispatched, and a single active broadcast which is currently being
  * dispatched.
+ * <p>
+ * This entire class is marked as {@code NotThreadSafe} since it's the
+ * responsibility of the caller to always interact with a relevant lock held.
  */
+// @NotThreadSafe
 class BroadcastProcessQueue {
     final @NonNull BroadcastConstants constants;
     final @NonNull String processName;
@@ -155,24 +156,42 @@
     }
 
     /**
-     * Skip any broadcasts matching the given predicate, marking them as
-     * {@link BroadcastRecord#DELIVERY_SKIPPED}. Typically used when a package
-     * or components have been disabled.
-     * <p>
-     * Note that we carefully preserve the broadcast in our queue to ensure that
-     * we follow our normal flow for "finishing" a broadcast, which is where we
-     * handle things like ordered broadcasts.
+     * Functional interface that tests a {@link BroadcastRecord} that has been
+     * previously enqueued in {@link BroadcastProcessQueue}.
      */
-    public boolean skipMatchingBroadcasts(@NonNull BiPredicate<BroadcastRecord, Object> predicate) {
+    @FunctionalInterface
+    public interface BroadcastPredicate {
+        public boolean test(@NonNull BroadcastRecord r, int index);
+    }
+
+    /**
+     * Functional interface that consumes a {@link BroadcastRecord} that has
+     * been previously enqueued in {@link BroadcastProcessQueue}.
+     */
+    @FunctionalInterface
+    public interface BroadcastConsumer {
+        public void accept(@NonNull BroadcastRecord r, int index);
+    }
+
+    /**
+     * Remove any broadcasts matching the given predicate.
+     * <p>
+     * Predicates that choose to remove a broadcast <em>must</em> finish
+     * delivery of the matched broadcast, to ensure that situations like ordered
+     * broadcasts are handled consistently.
+     */
+    public boolean removeMatchingBroadcasts(@NonNull BroadcastPredicate predicate,
+            @NonNull BroadcastConsumer consumer) {
         boolean didSomething = false;
         final Iterator<SomeArgs> it = mPending.iterator();
         while (it.hasNext()) {
             final SomeArgs args = it.next();
             final BroadcastRecord record = (BroadcastRecord) args.arg1;
             final int index = args.argi1;
-            final Object receiver = record.receivers.get(index);
-            if (predicate.test(record, receiver)) {
-                record.setDeliveryState(index, BroadcastRecord.DELIVERY_SKIPPED);
+            if (predicate.test(record, index)) {
+                consumer.accept(record, index);
+                args.recycle();
+                it.remove();
                 didSomething = true;
             }
         }
@@ -263,78 +282,48 @@
         mActiveViaColdStart = false;
     }
 
-    public void traceStartingBegin() {
+    public void traceProcessStartingBegin() {
         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 traceTrackName, toShortString() + " starting", hashCode());
     }
 
-    public void traceRunningBegin() {
+    public void traceProcessRunningBegin() {
         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 traceTrackName, toShortString() + " running", hashCode());
     }
 
-    public void traceEnd() {
+    public void traceProcessEnd() {
         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                 traceTrackName, hashCode());
     }
 
-    public @DeliveryState int getActiveDeliveryState() {
-        checkState(isActive(), "isActive");
-        return mActive.delivery[mActiveIndex];
-    }
-
-    public void setActiveDeliveryState(@DeliveryState int deliveryState) {
-        checkState(isActive(), "isActive");
-
-        // Emit tracing events for the broadcast we're dispatching; the cookie
-        // here is unique within the track
+    public void traceActiveBegin() {
         final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
-        switch (deliveryState) {
-            case BroadcastRecord.DELIVERY_SCHEDULED:
-                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                        traceTrackName, mActive.toShortString() + " scheduled", cookie);
-                break;
-            case BroadcastRecord.DELIVERY_DELIVERED:
-            case BroadcastRecord.DELIVERY_SKIPPED:
-            case BroadcastRecord.DELIVERY_TIMEOUT:
-            case BroadcastRecord.DELIVERY_FAILURE:
-                // Only end trace events previously started by us
-                if (getActiveDeliveryState() == BroadcastRecord.DELIVERY_SCHEDULED) {
-                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                            traceTrackName, cookie);
-                }
-                break;
-        }
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                traceTrackName, mActive.toShortString() + " scheduled", cookie);
+    }
 
-        mActive.setDeliveryState(mActiveIndex, deliveryState);
+    public void traceActiveEnd() {
+        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                traceTrackName, cookie);
     }
 
     /**
-     * Return the delay between the currently active broadcast being enqueued
-     * and being scheduled to run for this process.
+     * Return the broadcast being actively dispatched in this process.
      */
-    public @DurationMillisLong long getActiveScheduledDelay() {
-        checkState(isActive(), "isActive");
-        return mActive.scheduledTime[mActiveIndex] - mActive.enqueueTime;
-    }
-
-    /**
-     * Return the delay between the currently active broadcast being scheduled
-     * to run and reaching a terminal state for this process.
-     */
-    public @DurationMillisLong long getActiveDeliveredDelay() {
-        checkState(isActive(), "isActive");
-        return mActive.duration[mActiveIndex];
-    }
-
     public @NonNull BroadcastRecord getActive() {
         checkState(isActive(), "isActive");
         return mActive;
     }
 
-    public @NonNull Object getActiveReceiver() {
+    /**
+     * Return the index into {@link BroadcastRecord#receivers} of the receiver
+     * being actively dispatched in this process.
+     */
+    public int getActiveIndex() {
         checkState(isActive(), "isActive");
-        return mActive.receivers.get(mActiveIndex);
+        return mActiveIndex;
     }
 
     public boolean isEmpty() {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d60ad99..a36a9f6 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -21,14 +21,18 @@
 
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
+import static com.android.server.am.BroadcastRecord.deliveryStateToString;
+import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
+import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
 import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
 import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
 
@@ -42,6 +46,8 @@
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -64,6 +70,8 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
+import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
 import com.android.server.am.BroadcastRecord.DeliveryState;
 
 import java.io.FileDescriptor;
@@ -73,7 +81,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
-import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 
 /**
@@ -345,15 +352,15 @@
                     + " from runnable to running; process is " + queue.app);
 
             // Allocate this available permit and start running!
-            final int index = getRunningIndexOf(null);
-            mRunning[index] = queue;
+            final int queueIndex = getRunningIndexOf(null);
+            mRunning[queueIndex] = queue;
             avail--;
 
             // Remove ourselves from linked list of runnable things
             mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
 
             // Emit all trace events for this process into a consistent track
-            queue.traceTrackName = TAG + ".mRunning[" + index + "]";
+            queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
 
             // If we're already warm, boost OOM adjust now; if cold we'll boost
             // it after the app has been started
@@ -365,10 +372,10 @@
             // otherwise we'll wait for the cold start to circle back around
             queue.makeActiveNextPending();
             if (processWarm) {
-                queue.traceRunningBegin();
+                queue.traceProcessRunningBegin();
                 scheduleReceiverWarmLocked(queue);
             } else {
-                queue.traceStartingBegin();
+                queue.traceProcessStartingBegin();
                 scheduleReceiverColdLocked(queue);
             }
 
@@ -401,8 +408,8 @@
             final BroadcastProcessQueue queue = mRunningColdStart;
             mRunningColdStart = null;
 
-            queue.traceEnd();
-            queue.traceRunningBegin();
+            queue.traceProcessEnd();
+            queue.traceProcessRunningBegin();
             scheduleReceiverWarmLocked(queue);
 
             // We might be willing to kick off another cold start
@@ -468,6 +475,17 @@
         // TODO: handle empty receivers to deliver result immediately
         if (r.receivers == null) return;
 
+        final IntentFilter removeMatchingFilter = (r.options != null)
+                ? r.options.getRemoveMatchingFilter() : null;
+        if (removeMatchingFilter != null) {
+            final Predicate<Intent> removeMatching = removeMatchingFilter.asPredicate();
+            skipMatchingBroadcasts(QUEUE_PREDICATE_ANY, (testRecord, testReceiver) -> {
+                // We only allow caller to clear broadcasts they enqueued
+                return (testRecord.callingUid == r.callingUid)
+                        && removeMatching.test(testRecord.intent);
+            });
+        }
+
         r.enqueueTime = SystemClock.uptimeMillis();
         r.enqueueRealTime = SystemClock.elapsedRealtime();
         r.enqueueClockTime = System.currentTimeMillis();
@@ -494,7 +512,8 @@
         queue.setActiveViaColdStart(true);
 
         final BroadcastRecord r = queue.getActive();
-        final Object receiver = queue.getActiveReceiver();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
 
         final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
         final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
@@ -534,15 +553,17 @@
 
         final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
-        final Object receiver = queue.getActiveReceiver();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
 
-        // If someone already skipped us, finish immediately; typically due to a
-        // component being disabled
-        if (queue.getActiveDeliveryState() == BroadcastRecord.DELIVERY_SKIPPED) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+        // If someone already finished this broadcast, finish immediately
+        final int oldDeliveryState = getDeliveryState(r, index);
+        if (isDeliveryStateTerminal(oldDeliveryState)) {
+            finishReceiverLocked(queue, oldDeliveryState);
             return;
         }
 
+        // Consider additional cases where we'd want fo finish immediately
         if (app.isInFullBackup()) {
             finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
             return;
@@ -583,13 +604,13 @@
         }
 
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
-        queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);
+        setDeliveryState(queue, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
 
         final IApplicationThread thread = app.getThread();
         if (thread != null) {
             try {
                 if (receiver instanceof BroadcastFilter) {
-                    notifyScheduleRegisteredReceiver(app, r);
+                    notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
                     thread.scheduleRegisteredReceiver(
                             ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
                             r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
@@ -601,7 +622,7 @@
                         finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
                     }
                 } else {
-                    notifyScheduleReceiver(app, r, receiverIntent);
+                    notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
                     thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
                             null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                             app.mState.getReportedProcState());
@@ -653,7 +674,7 @@
         // receivers as skipped
         if (r.ordered && r.resultAbort) {
             for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) {
-                r.setDeliveryState(i, BroadcastRecord.DELIVERY_SKIPPED);
+                setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
             }
         }
 
@@ -666,44 +687,20 @@
 
         final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
+        final int index = queue.getActiveIndex();
+        final Object receiver = r.receivers.get(index);
 
-        queue.setActiveDeliveryState(deliveryState);
-
-        if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
-            Slog.w(TAG, "Delivery state of " + queue.getActive() + " to " + queue + " changed to "
-                    + BroadcastRecord.deliveryStateToString(deliveryState));
-        }
+        setDeliveryState(queue, r, index, receiver, deliveryState);
 
         if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
             if (app != null && !app.isDebugging()) {
                 mService.appNotResponding(queue.app, TimeoutRecord
-                        .forBroadcastReceiver("Broadcast of " + queue.getActive().toShortString()));
+                        .forBroadcastReceiver("Broadcast of " + r.toShortString()));
             }
         } else {
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT, queue);
         }
 
-        // We carefully only increment this here once we've confidently finished
-        // a given receiver; everyone must come through this path to ensure all
-        // bookkeeping is handled consistently
-        r.finishedCount++;
-        notifyFinishReceiver(queue);
-
-        if (r.ordered) {
-            if (r.finishedCount < r.receivers.size()) {
-                // We just finished an ordered receiver, which means the next
-                // receiver might now be runnable
-                final Object nextReceiver = r.receivers.get(r.finishedCount);
-                final BroadcastProcessQueue nextQueue = getProcessQueue(
-                        getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
-                nextQueue.invalidateRunnableAt();
-                updateRunnableList(nextQueue);
-            } else {
-                // Everything finished, so deliver final result
-                scheduleResultTo(r);
-            }
-        }
-
         // Even if we have more broadcasts, if we've made reasonable progress
         // and someone else is waiting, retire ourselves to avoid starvation
         final boolean shouldRetire = (mRunnableHead != null)
@@ -717,10 +714,10 @@
         } else {
             // We've drained running broadcasts; maybe move back to runnable
             queue.makeActiveIdle();
-            queue.traceEnd();
+            queue.traceProcessEnd();
 
-            final int index = getRunningIndexOf(queue);
-            mRunning[index] = null;
+            final int queueIndex = getRunningIndexOf(queue);
+            mRunning[queueIndex] = null;
             updateRunnableList(queue);
             enqueueUpdateRunningList();
 
@@ -731,17 +728,67 @@
         }
     }
 
-    private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
-            (q) -> true;
-    private static final BiPredicate<BroadcastRecord, Object> BROADCAST_PREDICATE_ANY =
-            (r, o) -> true;
+    /**
+     * Set the delivery state on the given broadcast, then apply any additional
+     * bookkeeping related to ordered broadcasts.
+     */
+    private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
+            @NonNull BroadcastRecord r, int index, @NonNull Object receiver,
+            @DeliveryState int newDeliveryState) {
+        final int oldDeliveryState = getDeliveryState(r, index);
+
+        if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
+            Slog.w(TAG, "Delivery state of " + r + " to " + receiver + " changed from "
+                    + deliveryStateToString(oldDeliveryState) + " to "
+                    + deliveryStateToString(newDeliveryState));
+        }
+
+        r.setDeliveryState(index, newDeliveryState);
+
+        // Emit any relevant tracing results when we're changing the delivery
+        // state as part of running from a queue
+        if (queue != null) {
+            if (newDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED) {
+                queue.traceActiveBegin();
+            } else if ((oldDeliveryState == BroadcastRecord.DELIVERY_SCHEDULED)
+                    && isDeliveryStateTerminal(newDeliveryState)) {
+                queue.traceActiveEnd();
+            }
+        }
+
+        // If we're moving into a terminal state, we might have internal
+        // bookkeeping to update for ordered broadcasts
+        if (!isDeliveryStateTerminal(oldDeliveryState)
+                && isDeliveryStateTerminal(newDeliveryState)) {
+            r.finishedCount++;
+            notifyFinishReceiver(queue, r, index, receiver);
+
+            if (r.ordered) {
+                if (r.finishedCount < r.receivers.size()) {
+                    // We just finished an ordered receiver, which means the
+                    // next receiver might now be runnable
+                    final Object nextReceiver = r.receivers.get(r.finishedCount);
+                    final BroadcastProcessQueue nextQueue = getProcessQueue(
+                            getReceiverProcessName(nextReceiver), getReceiverUid(nextReceiver));
+                    nextQueue.invalidateRunnableAt();
+                    updateRunnableList(nextQueue);
+                } else {
+                    // Everything finished, so deliver final result
+                    scheduleResultTo(r);
+                }
+            }
+        }
+    }
+
+    private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
+        return r.getDeliveryState(index);
+    }
 
     @Override
     public boolean cleanupDisabledPackageReceiversLocked(@Nullable String packageName,
             @Nullable Set<String> filterByClasses, int userId) {
         final Predicate<BroadcastProcessQueue> queuePredicate;
-        final BiPredicate<BroadcastRecord, Object> broadcastPredicate;
-        // TODO: apply packageName filter inside broadcastPredicate
+        final BroadcastPredicate broadcastPredicate;
         if (packageName != null) {
             // Caller provided a package and user ID, so we're focused on queues
             // belonging to a specific UID
@@ -754,15 +801,21 @@
             // If caller provided a set of classes, filter to skip only those;
             // otherwise we skip all broadcasts
             if (filterByClasses != null) {
-                broadcastPredicate = (r, o) -> {
-                    if (o instanceof ResolveInfo) {
-                        return filterByClasses.contains(((ResolveInfo) o).activityInfo.name);
+                broadcastPredicate = (r, i) -> {
+                    final Object receiver = r.receivers.get(i);
+                    if (receiver instanceof ResolveInfo) {
+                        final ActivityInfo info = ((ResolveInfo) receiver).activityInfo;
+                        return packageName.equals(info.packageName)
+                                && filterByClasses.contains(info.name);
                     } else {
                         return false;
                     }
                 };
             } else {
-                broadcastPredicate = BROADCAST_PREDICATE_ANY;
+                broadcastPredicate = (r, i) -> {
+                    final Object receiver = r.receivers.get(i);
+                    return packageName.equals(getReceiverPackageName(receiver));
+                };
             }
         } else {
             // Caller is cleaning up an entire user ID; skip all broadcasts
@@ -771,7 +824,25 @@
             };
             broadcastPredicate = BROADCAST_PREDICATE_ANY;
         }
+        return skipMatchingBroadcasts(queuePredicate, broadcastPredicate);
+    }
 
+    private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY =
+            (q) -> true;
+    private static final BroadcastPredicate BROADCAST_PREDICATE_ANY =
+            (r, i) -> true;
+
+    /**
+     * Typical consumer that will skip the given broadcast, usually as a result
+     * of it matching a predicate.
+     */
+    private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
+        setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+    };
+
+    private boolean skipMatchingBroadcasts(
+            @NonNull Predicate<BroadcastProcessQueue> queuePredicate,
+            @NonNull BroadcastPredicate broadcastPredicate) {
         // Note that we carefully preserve any "skipped" broadcasts in their
         // queues so that we follow our normal flow for "finishing" a broadcast,
         // which is where we handle things like ordered broadcasts.
@@ -780,7 +851,8 @@
             BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
             while (leaf != null) {
                 if (queuePredicate.test(leaf)) {
-                    didSomething |= leaf.skipMatchingBroadcasts(broadcastPredicate);
+                    didSomething |= leaf.removeMatchingBroadcasts(broadcastPredicate,
+                            mBroadcastConsumerSkip);
                 }
                 leaf = leaf.processNameNext;
             }
@@ -909,7 +981,7 @@
      * a registered receiver, typically for internal bookkeeping.
      */
     private void notifyScheduleRegisteredReceiver(@NonNull ProcessRecord app,
-            @NonNull BroadcastRecord r) {
+            @NonNull BroadcastRecord r, @NonNull BroadcastFilter receiver) {
         reportUsageStatsBroadcastDispatched(app, r);
     }
 
@@ -918,22 +990,25 @@
      * a manifest receiver, typically for internal bookkeeping.
      */
     private void notifyScheduleReceiver(@NonNull ProcessRecord app,
-            @NonNull BroadcastRecord r, @NonNull Intent receiverIntent) {
+            @NonNull BroadcastRecord r, @NonNull ResolveInfo receiver) {
         reportUsageStatsBroadcastDispatched(app, r);
 
+        final String receiverPackageName = receiver.activityInfo.packageName;
+        app.addPackage(receiverPackageName,
+                receiver.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
+
         final boolean targetedBroadcast = r.intent.getComponent() != null;
-        final boolean targetedSelf = Objects.equals(r.callerPackage,
-                receiverIntent.getComponent().getPackageName());
+        final boolean targetedSelf = Objects.equals(r.callerPackage, receiverPackageName);
         if (targetedBroadcast && !targetedSelf) {
-            mService.mUsageStatsService.reportEvent(receiverIntent.getComponent().getPackageName(),
+            mService.mUsageStatsService.reportEvent(receiverPackageName,
                     r.userId, Event.APP_COMPONENT_USED);
         }
 
-        mService.notifyPackageUse(receiverIntent.getComponent().getPackageName(),
+        mService.notifyPackageUse(receiverPackageName,
                 PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
 
         mService.mPackageManagerInt.setPackageStoppedState(
-                receiverIntent.getComponent().getPackageName(), false, r.userId);
+                receiverPackageName, false, r.userId);
     }
 
     private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
@@ -961,26 +1036,30 @@
      * Inform other parts of OS that the given broadcast was just finished,
      * typically for internal bookkeeping.
      */
-    private void notifyFinishReceiver(@NonNull BroadcastProcessQueue queue) {
-        final BroadcastRecord r = queue.getActive();
-
+    private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue,
+            @NonNull BroadcastRecord r, int index, @NonNull Object receiver) {
         // Report statistics for each individual receiver
-        final int uid = queue.uid;
+        final int uid = getReceiverUid(receiver);
         final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid;
         final String actionName = ActivityManagerService.getShortAction(r.intent.getAction());
-        final int receiverType = (queue.getActiveReceiver() instanceof BroadcastFilter)
+        final int receiverType = (receiver instanceof BroadcastFilter)
                 ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
                 : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-        final int procStartType = queue.getActiveViaColdStart()
-                ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
-                : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+        final int type;
+        if (queue == null) {
+            type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
+        } else if (queue.getActiveViaColdStart()) {
+            type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+        } else {
+            type = BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
+        }
         // With the new per-process queues, there's no delay between being
         // "dispatched" and "scheduled", so we report no "receive delay"
-        final long dispatchDelay = queue.getActiveScheduledDelay();
+        final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime;
         final long receiveDelay = 0;
-        final long finishDelay = queue.getActiveDeliveredDelay();
+        final long finishDelay = r.duration[index];
         FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
-                receiverType, procStartType, dispatchDelay, receiveDelay, finishDelay);
+                receiverType, type, dispatchDelay, receiveDelay, finishDelay);
 
         final boolean recordFinished = (r.finishedCount == r.receivers.size());
         if (recordFinished) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 74f32e3..ae7f2a5 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -23,7 +23,9 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_CHANGE_ID;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import static com.android.server.am.BroadcastQueue.checkState;
 
+import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -168,6 +170,22 @@
         }
     }
 
+    /**
+     * Return if the given delivery state is "terminal", where no additional
+     * delivery state changes will be made.
+     */
+    static boolean isDeliveryStateTerminal(@DeliveryState int deliveryState) {
+        switch (deliveryState) {
+            case DELIVERY_DELIVERED:
+            case DELIVERY_SKIPPED:
+            case DELIVERY_TIMEOUT:
+            case DELIVERY_FAILURE:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // the manifest receiver that is currently running.
@@ -546,6 +564,10 @@
         }
     }
 
+    @DeliveryState int getDeliveryState(int index) {
+        return delivery[index];
+    }
+
     boolean isForeground() {
         return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
     }
@@ -611,7 +633,7 @@
         }
     }
 
-    static String getReceiverProcessName(@NonNull Object receiver) {
+    static @NonNull String getReceiverProcessName(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
             return ((BroadcastFilter) receiver).receiverList.app.processName;
         } else /* if (receiver instanceof ResolveInfo) */ {
@@ -619,6 +641,14 @@
         }
     }
 
+    static @NonNull String getReceiverPackageName(@NonNull Object receiver) {
+        if (receiver instanceof BroadcastFilter) {
+            return ((BroadcastFilter) receiver).receiverList.app.info.packageName;
+        } else /* if (receiver instanceof ResolveInfo) */ {
+            return ((ResolveInfo) receiver).activityInfo.packageName;
+        }
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index dad9584..9336be5 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -20,10 +20,12 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManagerInternal;
 import android.media.AudioManager;
@@ -32,6 +34,7 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.BatteryStats;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IWakeLockCallback;
 import android.os.Looper;
@@ -137,7 +140,9 @@
     private final NotifierHandler mHandler;
     private final Executor mBackgroundExecutor;
     private final Intent mScreenOnIntent;
+    private final Bundle mScreenOnOptions;
     private final Intent mScreenOffIntent;
+    private final Bundle mScreenOffOptions;
 
     // True if the device should suspend when the screen is off due to proximity.
     private final boolean mSuspendWhenScreenOffDueToProximityConfig;
@@ -199,10 +204,14 @@
         mScreenOnIntent.addFlags(
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        mScreenOnOptions = BroadcastOptions.makeRemovingMatchingFilter(
+                new IntentFilter(Intent.ACTION_SCREEN_OFF)).toBundle();
         mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
         mScreenOffIntent.addFlags(
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        mScreenOffOptions = BroadcastOptions.makeRemovingMatchingFilter(
+                new IntentFilter(Intent.ACTION_SCREEN_ON)).toBundle();
 
         mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
@@ -788,7 +797,8 @@
 
         if (mActivityManagerInternal.isSystemReady()) {
             mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
-                    mWakeUpBroadcastDone, mHandler, 0, null, null);
+                    AppOpsManager.OP_NONE, mScreenOnOptions, mWakeUpBroadcastDone, mHandler,
+                    0, null, null);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
             sendNextBroadcast();
@@ -811,7 +821,8 @@
 
         if (mActivityManagerInternal.isSystemReady()) {
             mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
-                    mGoToSleepBroadcastDone, mHandler, 0, null, null);
+                    AppOpsManager.OP_NONE, mScreenOffOptions, mGoToSleepBroadcastDone, mHandler,
+                    0, null, null);
         } else {
             EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
             sendNextBroadcast();
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 2b1f4a3..e09b80e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -37,12 +37,14 @@
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.HandlerThread;
 import android.os.UserHandle;
 import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -88,6 +90,11 @@
         doReturn(4L).when(mQueue4).getRunnableAt();
     }
 
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+    }
+
     private static void assertOrphan(BroadcastProcessQueue queue) {
         assertNull(queue.runnableAtNext);
         assertNull(queue.runnableAtPrev);
@@ -291,4 +298,35 @@
         assertTrue(queue.isRunnable());
         assertEquals(airplaneRecord.enqueueTime, queue.getRunnableAt());
     }
+
+    /**
+     * Verify that sending a broadcast that removes any matching pending
+     * broadcasts is applied as expected.
+     */
+    @Test
+    public void testRemoveMatchingFilter() {
+        final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
+        final BroadcastOptions optionsOn = BroadcastOptions.makeBasic();
+        optionsOn.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
+        final Intent screenOff = new Intent(Intent.ACTION_SCREEN_OFF);
+        final BroadcastOptions optionsOff = BroadcastOptions.makeBasic();
+        optionsOff.setRemoveMatchingFilter(new IntentFilter(Intent.ACTION_SCREEN_ON));
+
+        // Halt all processing so that we get a consistent view
+        mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, optionsOn));
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOff, optionsOff));
+
+        // Marching through the queue we should only have one SCREEN_OFF
+        // broadcast, since that's the last state we dispatched
+        final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_SCREEN_OFF, queue.getActive().intent.getAction());
+        assertTrue(queue.isEmpty());
+    }
 }
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 adb62d0..cf5d113 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -654,37 +654,28 @@
                     receiverApp.mState.getReportedProcState());
             verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
 
-            // Confirm that app was thawed
             if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
                 // Nuance: the default implementation doesn't ask for manifest
                 // cold-started apps to be thawed, but the modern stack does
             } else {
+                // Confirm that app was thawed
                 verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp),
                         eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+
+                // Confirm that we added package to process
+                verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+                        anyLong(), any());
             }
+
+            // Confirm that we've reported package as being used
+            verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
+                    eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
+
+            // Confirm that we unstopped manifest receivers
+            verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
+                    eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
         }
 
-        // Confirm that we've reported relevant packages as being used, but
-        // only called for manifest receivers
-        verify(mAms, never()).notifyPackageUse(eq(PACKAGE_RED),
-                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
-        verify(mAms, atLeastOnce()).notifyPackageUse(eq(PACKAGE_GREEN),
-                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
-        verify(mAms, atLeastOnce()).notifyPackageUse(eq(PACKAGE_BLUE),
-                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
-        verify(mAms, atLeastOnce()).notifyPackageUse(eq(PACKAGE_YELLOW),
-                eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
-
-        // Confirm that we unstopped manifest receivers
-        verify(mAms.mPackageManagerInt, never()).setPackageStoppedState(eq(PACKAGE_RED),
-                eq(false), eq(UserHandle.USER_SYSTEM));
-        verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(eq(PACKAGE_GREEN),
-                eq(false), eq(UserHandle.USER_SYSTEM));
-        verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(eq(PACKAGE_BLUE),
-                eq(false), eq(UserHandle.USER_SYSTEM));
-        verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(eq(PACKAGE_YELLOW),
-                eq(false), eq(UserHandle.USER_SYSTEM));
-
         // Confirm that we've reported expected usage events
         verify(mAms.mUsageStatsService).reportBroadcastDispatched(eq(callerApp.uid),
                 eq(PACKAGE_YELLOW), eq(UserHandle.SYSTEM), eq(42L), anyLong(), anyInt());