Merge "Properly handle activity start tokens for unordered broadcasts" into qt-dev
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ee9b561..1c2f51b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -437,6 +437,19 @@
         return next;
     }
 
+    private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
+        // the receiver had run for less than allowed bg activity start timeout,
+        // so allow the process to still start activities from bg for some more time
+        String msgToken = (app.toShortString() + r.toString()).intern();
+        // first, if there exists a past scheduled request to remove this token, drop
+        // that request - we don't want the token to be swept from under our feet...
+        mHandler.removeCallbacksAndMessages(msgToken);
+        // ...then schedule the removal of the token after the extended timeout
+        mHandler.postAtTime(() -> {
+            app.removeAllowBackgroundActivityStartsToken(r);
+        }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
+    }
+
     public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
             String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
         final int state = r.state;
@@ -453,17 +466,8 @@
                 // just remove the token for this process now and we're done
                 r.curApp.removeAllowBackgroundActivityStartsToken(r);
             } else {
-                // the receiver had run for less than allowed bg activity start timeout,
-                // so allow the process to still start activities from bg for some more time
-                String msgToken = (r.curApp.toShortString() + r.toString()).intern();
-                // first, if there exists a past scheduled request to remove this token, drop
-                // that request - we don't want the token to be swept from under our feet...
-                mHandler.removeCallbacksAndMessages(msgToken);
-                // ...then schedule the removal of the token after the extended timeout
-                final ProcessRecord app = r.curApp;
-                mHandler.postAtTime(() -> {
-                    app.removeAllowBackgroundActivityStartsToken(r);
-                }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
+                // It gets more time; post the removal to happen at the appropriate moment
+                postActivityStartTokenRemoval(r.curApp, r);
             }
         }
         // If we're abandoning this broadcast before any receivers were actually spun up,
@@ -810,19 +814,29 @@
                 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                         new Intent(r.intent), r.resultCode, r.resultData,
                         r.resultExtras, r.ordered, r.initialSticky, r.userId);
+                // parallel broadcasts are fire-and-forget, not bookended by a call to
+                // finishReceiverLocked(), so we manage their activity-start token here
+                if (r.allowBackgroundActivityStarts && !r.ordered) {
+                    postActivityStartTokenRemoval(filter.receiverList.app, r);
+                }
             }
             if (ordered) {
                 r.state = BroadcastRecord.CALL_DONE_RECEIVE;
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
+            // Clean up ProcessRecord state related to this broadcast attempt
+            if (filter.receiverList.app != null) {
+                filter.receiverList.app.removeAllowBackgroundActivityStartsToken(r);
+                if (ordered) {
+                    filter.receiverList.app.curReceivers.remove(r);
+                }
+            }
+            // And BroadcastRecord state related to ordered delivery, if appropriate
             if (ordered) {
                 r.receiver = null;
                 r.curFilter = null;
                 filter.receiverList.curBroadcast = null;
-                if (filter.receiverList.app != null) {
-                    filter.receiverList.app.curReceivers.remove(r);
-                }
             }
         }
     }
@@ -1283,6 +1297,8 @@
             } else {
                 if (filter.receiverList != null) {
                     maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
+                    // r is guaranteed ordered at this point, so we know finishReceiverLocked()
+                    // will get a callback and handle the activity start token lifecycle.
                 }
                 if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
                     scheduleTempWhitelistLocked(filter.owningUid,