Merge changes from topic "oct28b"

* changes:
  BroadcastQueue: skip ANRs when assuming success.
  BroadcastQueue: better state transition logging.
  BroadcastQueue: return reasons from skip policy.
diff --git a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java b/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
index a413f7b..6b01a9f 100644
--- a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
+++ b/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
@@ -22,6 +22,8 @@
 import android.os.Looper;
 import android.os.Trace;
 
+import com.android.server.am.BroadcastLoopers;
+
 import java.util.concurrent.Executor;
 
 /**
@@ -45,6 +47,7 @@
             sInstance = new JobSchedulerBackgroundThread();
             sInstance.start();
             final Looper looper = sInstance.getLooper();
+            BroadcastLoopers.addLooper(looper);
             looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
             looper.setSlowLogThresholdMs(
                     SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
diff --git a/services/core/java/com/android/server/ServiceThread.java b/services/core/java/com/android/server/ServiceThread.java
index 6d8e49c..3ea4b86 100644
--- a/services/core/java/com/android/server/ServiceThread.java
+++ b/services/core/java/com/android/server/ServiceThread.java
@@ -22,6 +22,8 @@
 import android.os.Process;
 import android.os.StrictMode;
 
+import com.android.server.am.BroadcastLoopers;
+
 /**
  * Special handler thread that we create for system services that require their own loopers.
  */
@@ -46,6 +48,14 @@
         super.run();
     }
 
+    @Override
+    protected void onLooperPrepared() {
+        // Almost all service threads are used for dispatching broadcast
+        // intents, so register ourselves to ensure that "wait-for-broadcast"
+        // shell commands are able to drain any pending broadcasts
+        BroadcastLoopers.addLooper(getLooper());
+    }
+
     protected static Handler makeSharedHandler(Looper looper) {
         return new Handler(looper, /*callback=*/ null, /* async=*/ false, /* shared=*/ true);
     }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 72876f6..c280719 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -147,6 +147,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.am.BroadcastLoopers;
 import com.android.server.pm.Installer;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.storage.AppFuseBridge;
@@ -1809,6 +1810,7 @@
 
         HandlerThread hthread = new HandlerThread(TAG);
         hthread.start();
+        BroadcastLoopers.addLooper(hthread.getLooper());
         mHandler = new StorageManagerServiceHandler(hthread.getLooper());
 
         // Add OBB Action Handler to StorageManagerService thread.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5feb3bf..c954957 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2509,6 +2509,8 @@
         Watchdog.getInstance().addMonitor(this);
         Watchdog.getInstance().addThread(mHandler);
 
+        BroadcastLoopers.addLooper(BackgroundThread.getHandler().getLooper());
+
         // bind background threads to little cores
         // this is expected to fail inside of framework tests because apps can't touch cpusets directly
         // make sure we've already adjusted system_server's internal view of itself first
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index bebb484..b828720 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -25,6 +25,8 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
@@ -37,6 +39,7 @@
 public class BroadcastLoopers {
     private static final String TAG = "BroadcastLoopers";
 
+    @GuardedBy("sLoopers")
     private static final ArraySet<Looper> sLoopers = new ArraySet<>();
 
     /**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5750619..95cefea 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -144,14 +144,6 @@
         mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
     }
 
-    // TODO: add support for replacing pending broadcasts
-    // TODO: add support for merging pending broadcasts
-
-    // TODO: consider reordering foreground broadcasts within queue
-
-    // TODO: pause queues when background services are running
-    // TODO: pause queues when processes are frozen
-
     /**
      * Map from UID to per-process broadcast queues. If a UID hosts more than
      * one process, each additional process is stored as a linked list using
@@ -516,7 +508,8 @@
         if (queue != null) {
             // If queue was running a broadcast, fail it
             if (queue.isActive()) {
-                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                        "onApplicationCleanupLocked");
             }
 
             // Skip any pending registered receivers, since the old process
@@ -668,7 +661,8 @@
         // Ignore registered receivers from a previous PID
         if (receiver instanceof BroadcastFilter) {
             mRunningColdStart = null;
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED,
+                    "BroadcastFilter for cold app");
             return;
         }
 
@@ -690,7 +684,8 @@
                 hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
         if (queue.app == null) {
             mRunningColdStart = null;
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "startProcessLocked failed");
             return;
         }
     }
@@ -721,33 +716,37 @@
         // If someone already finished this broadcast, finish immediately
         final int oldDeliveryState = getDeliveryState(r, index);
         if (isDeliveryStateTerminal(oldDeliveryState)) {
-            finishReceiverLocked(queue, oldDeliveryState);
+            finishReceiverLocked(queue, oldDeliveryState, "already terminal state");
             return;
         }
 
         // Consider additional cases where we'd want to finish immediately
         if (app.isInFullBackup()) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
             return;
         }
         if (mSkipPolicy.shouldSkip(r, receiver)) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
             return;
         }
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
             return;
         }
 
         // Ignore registered receivers from a previous PID
         if ((receiver instanceof BroadcastFilter)
                 && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED,
+                    "BroadcastFilter for mismatched PID");
             return;
         }
 
-        if (mService.mProcessesReady && !r.timeoutExempt) {
+        // Skip ANR tracking early during boot, when requested, or when we
+        // immediately assume delivery success
+        final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+        if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
             queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
 
             final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
@@ -775,7 +774,8 @@
         }
 
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
-        setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
+        setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
+                "scheduleReceiverWarmLocked");
 
         final IApplicationThread thread = app.getOnewayThread();
         if (thread != null) {
@@ -789,8 +789,9 @@
 
                     // TODO: consider making registered receivers of unordered
                     // broadcasts report results to detect ANRs
-                    if (!r.ordered) {
-                        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+                    if (assumeDelivered) {
+                        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED,
+                                "assuming delivered");
                     }
                 } else {
                     notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
@@ -804,10 +805,11 @@
                 logw(msg);
                 app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
                 app.setKilled(true);
-                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
             }
         } else {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "missing IApplicationThread");
         }
     }
 
@@ -851,7 +853,8 @@
     }
 
     private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
-        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
+        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
+                "deliveryTimeoutHardLocked");
     }
 
     @Override
@@ -878,16 +881,16 @@
             if (r.resultAbort) {
                 for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
                     setDeliveryState(null, null, r, i, r.receivers.get(i),
-                            BroadcastRecord.DELIVERY_SKIPPED);
+                            BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
                 }
             }
         }
 
-        return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+        return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
     }
 
     private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
-            @DeliveryState int deliveryState) {
+            @DeliveryState int deliveryState, @NonNull String reason) {
         checkState(queue.isActive(), "isActive");
 
         final ProcessRecord app = queue.app;
@@ -895,7 +898,7 @@
         final int index = queue.getActiveIndex();
         final Object receiver = r.receivers.get(index);
 
-        setDeliveryState(queue, app, r, index, receiver, deliveryState);
+        setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
 
         if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
             r.anrCount++;
@@ -942,7 +945,7 @@
      */
     private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
             @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
-            @NonNull Object receiver, @DeliveryState int newDeliveryState) {
+            @NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) {
         final int oldDeliveryState = getDeliveryState(r, index);
 
         // Only apply state when we haven't already reached a terminal state;
@@ -970,7 +973,7 @@
                 logw("Delivery state of " + r + " to " + receiver
                         + " via " + app + " changed from "
                         + deliveryStateToString(oldDeliveryState) + " to "
-                        + deliveryStateToString(newDeliveryState));
+                        + deliveryStateToString(newDeliveryState) + " because " + reason);
             }
 
             r.terminalCount++;
@@ -1060,7 +1063,8 @@
      * of it matching a predicate.
      */
     private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
-        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+                "mBroadcastConsumerSkip");
     };
 
     /**
@@ -1068,7 +1072,8 @@
      * cancelled, usually as a result of it matching a predicate.
      */
     private final BroadcastConsumer mBroadcastConsumerSkipAndCanceled = (r, i) -> {
-        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+                "mBroadcastConsumerSkipAndCanceled");
         r.resultCode = Activity.RESULT_CANCELED;
         r.resultData = null;
         r.resultExtras = null;
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 60fddf0..481ab17 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -21,6 +21,7 @@
 import static com.android.server.am.BroadcastQueue.TAG;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -42,6 +43,8 @@
 
 import com.android.internal.util.ArrayUtils;
 
+import java.util.Objects;
+
 /**
  * Policy logic that decides if delivery of a particular {@link BroadcastRecord}
  * should be skipped for a given {@link ResolveInfo} or {@link BroadcastFilter}.
@@ -51,8 +54,8 @@
 public class BroadcastSkipPolicy {
     private final ActivityManagerService mService;
 
-    public BroadcastSkipPolicy(ActivityManagerService service) {
-        mService = service;
+    public BroadcastSkipPolicy(@NonNull ActivityManagerService service) {
+        mService = Objects.requireNonNull(service);
     }
 
     /**
@@ -60,18 +63,39 @@
      * the given {@link BroadcastFilter} or {@link ResolveInfo}.
      */
     public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull Object target) {
-        if (target instanceof BroadcastFilter) {
-            return shouldSkip(r, (BroadcastFilter) target);
+        final String msg = shouldSkipMessage(r, target);
+        if (msg != null) {
+            Slog.w(TAG, msg);
+            return true;
         } else {
-            return shouldSkip(r, (ResolveInfo) target);
+            return false;
+        }
+    }
+
+    /**
+     * Determine if the given {@link BroadcastRecord} is eligible to be sent to
+     * the given {@link BroadcastFilter} or {@link ResolveInfo}.
+     *
+     * @return message indicating why the argument should be skipped, otherwise
+     *         {@code null} if it can proceed.
+     */
+    public @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target) {
+        if (target instanceof BroadcastFilter) {
+            return shouldSkipMessage(r, (BroadcastFilter) target);
+        } else {
+            return shouldSkipMessage(r, (ResolveInfo) target);
         }
     }
 
     /**
      * Determine if the given {@link BroadcastRecord} is eligible to be sent to
      * the given {@link ResolveInfo}.
+     *
+     * @return message indicating why the argument should be skipped, otherwise
+     *         {@code null} if it can proceed.
      */
-    public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) {
+    private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+            @NonNull ResolveInfo info) {
         final BroadcastOptions brOptions = r.options;
         final ComponentName component = new ComponentName(
                 info.activityInfo.applicationInfo.packageName,
@@ -82,58 +106,52 @@
                         < brOptions.getMinManifestReceiverApiLevel() ||
                 info.activityInfo.applicationInfo.targetSdkVersion
                         > brOptions.getMaxManifestReceiverApiLevel())) {
-            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+            return "Target SDK mismatch: receiver " + info.activityInfo
                     + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
                     + " but delivery restricted to ["
                     + brOptions.getMinManifestReceiverApiLevel() + ", "
                     + brOptions.getMaxManifestReceiverApiLevel()
-                    + "] broadcasting " + broadcastDescription(r, component));
-            return true;
+                    + "] broadcasting " + broadcastDescription(r, component);
         }
         if (brOptions != null &&
                 !brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+            return "Compat change filtered: broadcasting " + broadcastDescription(r, component)
                     + " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            return true;
+                    + r.options.getRequireCompatChangeId();
         }
         if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
                 component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
-                    + broadcastDescription(r, component));
-            return true;
+            return "Association not allowed: broadcasting "
+                    + broadcastDescription(r, component);
         }
         if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                 r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Firewall blocked: broadcasting "
-                    + broadcastDescription(r, component));
-            return true;
+            return "Firewall blocked: broadcasting "
+                    + broadcastDescription(r, component);
         }
         int perm = checkComponentPermission(info.activityInfo.permission,
                 r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
                 info.activityInfo.exported);
         if (perm != PackageManager.PERMISSION_GRANTED) {
             if (!info.activityInfo.exported) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
+                return "Permission Denial: broadcasting "
                         + broadcastDescription(r, component)
-                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
+                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid;
             } else {
-                Slog.w(TAG, "Permission Denial: broadcasting "
+                return "Permission Denial: broadcasting "
                         + broadcastDescription(r, component)
-                        + " requires " + info.activityInfo.permission);
+                        + " requires " + info.activityInfo.permission;
             }
-            return true;
         } else if (info.activityInfo.permission != null) {
             final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
             if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
                     r.callingUid, r.callerPackage, r.callerFeatureId,
                     "Broadcast delivered to " + info.activityInfo.name)
                     != AppOpsManager.MODE_ALLOWED) {
-                Slog.w(TAG, "Appop Denial: broadcasting "
+                return "Appop Denial: broadcasting "
                         + broadcastDescription(r, component)
                         + " requires appop " + AppOpsManager.permissionToOp(
-                                info.activityInfo.permission));
-                return true;
+                                info.activityInfo.permission);
             }
         }
 
@@ -142,38 +160,34 @@
                     android.Manifest.permission.INTERACT_ACROSS_USERS,
                     info.activityInfo.applicationInfo.uid)
                             != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+                return "Permission Denial: Receiver " + component.flattenToShortString()
                         + " requests FLAG_SINGLE_USER, but app does not hold "
-                        + android.Manifest.permission.INTERACT_ACROSS_USERS);
-                return true;
+                        + android.Manifest.permission.INTERACT_ACROSS_USERS;
             }
         }
         if (info.activityInfo.applicationInfo.isInstantApp()
                 && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent
                     + " to " + component.flattenToShortString()
                     + " due to sender " + r.callerPackage
                     + " (uid " + r.callingUid + ")"
-                    + " Instant Apps do not support manifest receivers");
-            return true;
+                    + " Instant Apps do not support manifest receivers";
         }
         if (r.callerInstantApp
                 && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
                 && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent
                     + " to " + component.flattenToShortString()
                     + " requires receiver have visibleToInstantApps set"
                     + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return true;
+                    + " (uid " + r.callingUid + ")";
         }
         if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
             // If the target process is crashing, just skip it.
-            Slog.w(TAG, "Skipping deliver ordered [" + r.queue.toString() + "] " + r
-                    + " to " + r.curApp + ": process crashing");
-            return true;
+            return "Skipping deliver ordered [" + r.queue.toString() + "] " + r
+                    + " to " + r.curApp + ": process crashing";
         }
 
         boolean isAvailable = false;
@@ -183,15 +197,13 @@
                     UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
         } catch (Exception e) {
             // all such failures mean we skip this receiver
-            Slog.w(TAG, "Exception getting recipient info for "
-                    + info.activityInfo.packageName, e);
+            return "Exception getting recipient info for "
+                    + info.activityInfo.packageName;
         }
         if (!isAvailable) {
-            Slog.w(TAG,
-                    "Skipping delivery to " + info.activityInfo.packageName + " / "
+            return "Skipping delivery to " + info.activityInfo.packageName + " / "
                     + info.activityInfo.applicationInfo.uid
-                    + " : package no longer available");
-            return true;
+                    + " : package no longer available";
         }
 
         // If permissions need a review before any of the app components can run, we drop
@@ -201,10 +213,8 @@
         if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
                 info.activityInfo.packageName, UserHandle.getUserId(
                         info.activityInfo.applicationInfo.uid))) {
-            Slog.w(TAG,
-                    "Skipping delivery: permission review required for "
-                            + broadcastDescription(r, component));
-            return true;
+            return "Skipping delivery: permission review required for "
+                            + broadcastDescription(r, component);
         }
 
         final int allowed = mService.getAppStartModeLOSP(
@@ -216,10 +226,9 @@
             // to it and the app is in a state that should not receive it
             // (depending on how getAppStartModeLOSP has determined that).
             if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
-                Slog.w(TAG, "Background execution disabled: receiving "
+                return "Background execution disabled: receiving "
                         + r.intent + " to "
-                        + component.flattenToShortString());
-                return true;
+                        + component.flattenToShortString();
             } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                     || (r.intent.getComponent() == null
                         && r.intent.getPackage() == null
@@ -228,10 +237,9 @@
                         && !isSignaturePerm(r.requiredPermissions))) {
                 mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                         component.getPackageName());
-                Slog.w(TAG, "Background execution not allowed: receiving "
+                return "Background execution not allowed: receiving "
                         + r.intent + " to "
-                        + component.flattenToShortString());
-                return true;
+                        + component.flattenToShortString();
             }
         }
 
@@ -239,10 +247,8 @@
                 && !mService.mUserController
                 .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
                         0 /* flags */)) {
-            Slog.w(TAG,
-                    "Skipping delivery to " + info.activityInfo.packageName + " / "
-                            + info.activityInfo.applicationInfo.uid + " : user is not running");
-            return true;
+            return "Skipping delivery to " + info.activityInfo.packageName + " / "
+                            + info.activityInfo.applicationInfo.uid + " : user is not running";
         }
 
         if (r.excludedPermissions != null && r.excludedPermissions.length > 0) {
@@ -268,13 +274,15 @@
                                 info.activityInfo.applicationInfo.uid,
                                 info.activityInfo.packageName)
                             == AppOpsManager.MODE_ALLOWED)) {
-                        return true;
+                        return "Skipping delivery to " + info.activityInfo.packageName
+                                + " due to excluded permission " + excludedPermission;
                     }
                 } else {
                     // When there is no app op associated with the permission,
                     // skip when permission is granted.
                     if (perm == PackageManager.PERMISSION_GRANTED) {
-                        return true;
+                        return "Skipping delivery to " + info.activityInfo.packageName
+                                + " due to excluded permission " + excludedPermission;
                     }
                 }
             }
@@ -283,13 +291,12 @@
         // Check that the receiver does *not* belong to any of the excluded packages
         if (r.excludedPackages != null && r.excludedPackages.length > 0) {
             if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
+                return "Skipping delivery of excluded package "
                         + r.intent + " to "
                         + component.flattenToShortString()
                         + " excludes package " + component.getPackageName()
                         + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                return true;
+                        + " (uid " + r.callingUid + ")";
             }
         }
 
@@ -307,95 +314,94 @@
                     perm = PackageManager.PERMISSION_DENIED;
                 }
                 if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
+                    return "Permission Denial: receiving "
                             + r.intent + " to "
                             + component.flattenToShortString()
                             + " requires " + requiredPermission
                             + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    return true;
+                            + " (uid " + r.callingUid + ")";
                 }
                 int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
                 if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
                     if (!noteOpForManifestReceiver(appOp, r, info, component)) {
-                        return true;
+                        return "Skipping delivery to " + info.activityInfo.packageName
+                                + " due to required appop " + appOp;
                     }
                 }
             }
         }
         if (r.appOp != AppOpsManager.OP_NONE) {
             if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
-                return true;
+                return "Skipping delivery to " + info.activityInfo.packageName
+                        + " due to required appop " + r.appOp;
             }
         }
 
-        return false;
+        return null;
     }
 
     /**
      * Determine if the given {@link BroadcastRecord} is eligible to be sent to
      * the given {@link BroadcastFilter}.
+     *
+     * @return message indicating why the argument should be skipped, otherwise
+     *         {@code null} if it can proceed.
      */
-    public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull BroadcastFilter filter) {
+    private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+            @NonNull BroadcastFilter filter) {
         if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
+            return "Compat change filtered: broadcasting " + r.intent.toString()
                     + " to uid " + filter.owningUid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            return true;
+                    + r.options.getRequireCompatChangeId();
         }
         if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
                 filter.packageName, filter.owningUid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
+            return "Association not allowed: broadcasting "
                     + r.intent.toString()
                     + " from " + r.callerPackage + " (pid=" + r.callingPid
                     + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            return true;
+                    + filter;
         }
         if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                 r.callingPid, r.resolvedType, filter.receiverList.uid)) {
-            Slog.w(TAG, "Firewall blocked: broadcasting "
+            return "Firewall blocked: broadcasting "
                     + r.intent.toString()
                     + " from " + r.callerPackage + " (pid=" + r.callingPid
                     + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            return true;
+                    + filter;
         }
         // Check that the sender has permission to send to this receiver
         if (filter.requiredPermission != null) {
             int perm = checkComponentPermission(filter.requiredPermission,
                     r.callingPid, r.callingUid, -1, true);
             if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
+                return "Permission Denial: broadcasting "
                         + r.intent.toString()
                         + " from " + r.callerPackage + " (pid="
                         + r.callingPid + ", uid=" + r.callingUid + ")"
                         + " requires " + filter.requiredPermission
-                        + " due to registered receiver " + filter);
-                return true;
+                        + " due to registered receiver " + filter;
             } else {
                 final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
                 if (opCode != AppOpsManager.OP_NONE
                         && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
                         r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
                         != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: broadcasting "
+                    return "Appop Denial: broadcasting "
                             + r.intent.toString()
                             + " from " + r.callerPackage + " (pid="
                             + r.callingPid + ", uid=" + r.callingUid + ")"
                             + " requires appop " + AppOpsManager.permissionToOp(
                                     filter.requiredPermission)
-                            + " due to registered receiver " + filter);
-                    return true;
+                            + " due to registered receiver " + filter;
                 }
             }
         }
 
         if ((filter.receiverList.app == null || filter.receiverList.app.isKilled()
                 || filter.receiverList.app.mErrorState.isCrashing())) {
-            Slog.w(TAG, "Skipping deliver [" + r.queue.toString() + "] " + r
-                    + " to " + filter.receiverList + ": process gone or crashing");
-            return true;
+            return "Skipping deliver [" + r.queue.toString() + "] " + r
+                    + " to " + filter.receiverList + ": process gone or crashing";
         }
 
         // Ensure that broadcasts are only sent to other Instant Apps if they are marked as
@@ -405,28 +411,26 @@
 
         if (!visibleToInstantApps && filter.instantApp
                 && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent.toString()
                     + " to " + filter.receiverList.app
                     + " (pid=" + filter.receiverList.pid
                     + ", uid=" + filter.receiverList.uid + ")"
                     + " due to sender " + r.callerPackage
                     + " (uid " + r.callingUid + ")"
-                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
-            return true;
+                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS";
         }
 
         if (!filter.visibleToInstantApp && r.callerInstantApp
                 && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent.toString()
                     + " to " + filter.receiverList.app
                     + " (pid=" + filter.receiverList.pid
                     + ", uid=" + filter.receiverList.uid + ")"
                     + " requires receiver be visible to instant apps"
                     + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return true;
+                    + " (uid " + r.callingUid + ")";
         }
 
         // Check that the receiver has the required permission(s) to receive this broadcast.
@@ -436,15 +440,14 @@
                 int perm = checkComponentPermission(requiredPermission,
                         filter.receiverList.pid, filter.receiverList.uid, -1, true);
                 if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
+                    return "Permission Denial: receiving "
                             + r.intent.toString()
                             + " to " + filter.receiverList.app
                             + " (pid=" + filter.receiverList.pid
                             + ", uid=" + filter.receiverList.uid + ")"
                             + " requires " + requiredPermission
                             + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    return true;
+                            + " (uid " + r.callingUid + ")";
                 }
                 int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
                 if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
@@ -452,7 +455,7 @@
                         filter.receiverList.uid, filter.packageName, filter.featureId,
                         "Broadcast delivered to registered receiver " + filter.receiverId)
                         != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: receiving "
+                    return "Appop Denial: receiving "
                             + r.intent.toString()
                             + " to " + filter.receiverList.app
                             + " (pid=" + filter.receiverList.pid
@@ -460,8 +463,7 @@
                             + " requires appop " + AppOpsManager.permissionToOp(
                             requiredPermission)
                             + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    return true;
+                            + " (uid " + r.callingUid + ")";
                 }
             }
         }
@@ -469,14 +471,13 @@
             int perm = checkComponentPermission(null,
                     filter.receiverList.pid, filter.receiverList.uid, -1, true);
             if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: security check failed when receiving "
+                return "Permission Denial: security check failed when receiving "
                         + r.intent.toString()
                         + " to " + filter.receiverList.app
                         + " (pid=" + filter.receiverList.pid
                         + ", uid=" + filter.receiverList.uid + ")"
                         + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                return true;
+                        + " (uid " + r.callingUid + ")";
             }
         }
         // Check that the receiver does *not* have any excluded permissions
@@ -496,7 +497,7 @@
                                     filter.receiverList.uid,
                                     filter.packageName)
                                     == AppOpsManager.MODE_ALLOWED)) {
-                        Slog.w(TAG, "Appop Denial: receiving "
+                        return "Appop Denial: receiving "
                                 + r.intent.toString()
                                 + " to " + filter.receiverList.app
                                 + " (pid=" + filter.receiverList.pid
@@ -504,22 +505,20 @@
                                 + " excludes appop " + AppOpsManager.permissionToOp(
                                 excludedPermission)
                                 + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        return true;
+                                + " (uid " + r.callingUid + ")";
                     }
                 } else {
                     // When there is no app op associated with the permission,
                     // skip when permission is granted.
                     if (perm == PackageManager.PERMISSION_GRANTED) {
-                        Slog.w(TAG, "Permission Denial: receiving "
+                        return "Permission Denial: receiving "
                                 + r.intent.toString()
                                 + " to " + filter.receiverList.app
                                 + " (pid=" + filter.receiverList.pid
                                 + ", uid=" + filter.receiverList.uid + ")"
                                 + " excludes " + excludedPermission
                                 + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        return true;
+                                + " (uid " + r.callingUid + ")";
                     }
                 }
             }
@@ -528,15 +527,14 @@
         // Check that the receiver does *not* belong to any of the excluded packages
         if (r.excludedPackages != null && r.excludedPackages.length > 0) {
             if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
+                return "Skipping delivery of excluded package "
                         + r.intent.toString()
                         + " to " + filter.receiverList.app
                         + " (pid=" + filter.receiverList.pid
                         + ", uid=" + filter.receiverList.uid + ")"
                         + " excludes package " + filter.packageName
                         + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                return true;
+                        + " (uid " + r.callingUid + ")";
             }
         }
 
@@ -546,15 +544,14 @@
                 filter.receiverList.uid, filter.packageName, filter.featureId,
                 "Broadcast delivered to registered receiver " + filter.receiverId)
                 != AppOpsManager.MODE_ALLOWED) {
-            Slog.w(TAG, "Appop Denial: receiving "
+            return "Appop Denial: receiving "
                     + r.intent.toString()
                     + " to " + filter.receiverList.app
                     + " (pid=" + filter.receiverList.pid
                     + ", uid=" + filter.receiverList.uid + ")"
                     + " requires appop " + AppOpsManager.opToName(r.appOp)
                     + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return true;
+                    + " (uid " + r.callingUid + ")";
         }
 
         // Ensure that broadcasts are only sent to other apps if they are explicitly marked as
@@ -562,15 +559,14 @@
         if (!filter.exported && checkComponentPermission(null, r.callingPid,
                 r.callingUid, filter.receiverList.uid, filter.exported)
                 != PackageManager.PERMISSION_GRANTED) {
-            Slog.w(TAG, "Exported Denial: sending "
+            return "Exported Denial: sending "
                     + r.intent.toString()
                     + ", action: " + r.intent.getAction()
                     + " from " + r.callerPackage
                     + " (uid=" + r.callingUid + ")"
                     + " due to receiver " + filter.receiverList.app
                     + " (uid " + filter.receiverList.uid + ")"
-                    + " not specifying RECEIVER_EXPORTED");
-            return true;
+                    + " not specifying RECEIVER_EXPORTED";
         }
 
         // If permissions need a review before any of the app components can run, we drop
@@ -579,10 +575,10 @@
         // broadcast.
         if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
                 filter.owningUserId)) {
-            return true;
+            return "Skipping delivery to " + filter.packageName + " due to permissions review";
         }
 
-        return false;
+        return null;
     }
 
     private static String broadcastDescription(BroadcastRecord r, ComponentName component) {
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 e1a4c1d..de59603 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -280,13 +280,13 @@
         constants.TIMEOUT = 100;
         constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
         final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
-            public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) {
+            public boolean shouldSkip(BroadcastRecord r, Object o) {
                 // Ignored
                 return false;
             }
-            public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) {
+            public String shouldSkipMessage(BroadcastRecord r, Object o) {
                 // Ignored
-                return false;
+                return null;
             }
         };
         final BroadcastHistory emptyHistory = new BroadcastHistory(constants) {