Merge "Prepare to restrict PI sender BAL privileges."
diff --git a/core/api/current.txt b/core/api/current.txt
index 4b1335e..a6446ff 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4635,8 +4635,9 @@
     method @Nullable public android.graphics.Rect getLaunchBounds();
     method public int getLaunchDisplayId();
     method public boolean getLockTaskMode();
+    method public int getPendingIntentBackgroundActivityStartMode();
     method public int getSplashScreenStyle();
-    method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
+    method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public boolean isShareIdentityEnabled();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
@@ -4653,13 +4654,17 @@
     method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
     method public android.app.ActivityOptions setLaunchDisplayId(int);
     method public android.app.ActivityOptions setLockTaskEnabled(boolean);
-    method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+    method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+    method @NonNull public android.app.ActivityOptions setPendingIntentBackgroundActivityStartMode(int);
     method @NonNull public android.app.ActivityOptions setShareIdentityEnabled(boolean);
     method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
     field public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+    field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
+    field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
+    field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
   }
 
   public class AlarmManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a77ee08..4e0e4b9 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -808,8 +808,9 @@
     method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
     method @Nullable public String getDeliveryGroupMatchingKey();
     method public int getDeliveryGroupPolicy();
+    method public int getPendingIntentBackgroundActivityStartMode();
     method public boolean isDeferUntilActive();
-    method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
+    method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
@@ -818,7 +819,8 @@
     method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
     method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
     method public void setDontSendToRestrictedApps(boolean);
-    method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+    method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+    method @NonNull public android.app.BroadcastOptions setPendingIntentBackgroundActivityStartMode(int);
     method public void setRequireAllOfPermissions(@Nullable String[]);
     method public void setRequireCompatChange(long, boolean);
     method public void setRequireNoneOfPermissions(@Nullable String[]);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index a381fea..ae032db 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -440,14 +440,14 @@
             IApplicationThread resultToThread, IIntentReceiver resultTo, int resultCode,
             String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
             boolean serialized, boolean sticky, @UserIdInt int userId,
-            boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken,
+            BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList);
 
     public abstract ComponentName startServiceInPackage(int uid, Intent service,
             String resolvedType, boolean fgRequired, String callingPackage,
             @Nullable String callingFeatureId, @UserIdInt int userId,
-            boolean allowBackgroundActivityStarts,
-            @Nullable IBinder backgroundActivityStartsToken) throws TransactionTooLargeException;
+            BackgroundStartPrivileges backgroundStartPrivileges)
+            throws TransactionTooLargeException;
 
     public abstract void disconnectActivityFromServices(Object connectionHolder);
     public abstract void cleanUpServices(@UserIdInt int userId, ComponentName component,
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2214c8e..57214e0 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2402,6 +2402,32 @@
 
     }
 
+    /**
+     * Sets the mode for allowing or denying the senders privileges to start background activities
+     * to the PendingIntent.
+     *
+     * This is typically used in when executing {@link PendingIntent#send(Context, int, Intent,
+     * PendingIntent.OnFinished, Handler, String, Bundle)} or similar
+     * methods. A privileged sender of a PendingIntent should only grant
+     * {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a trusted source
+     * and/or executed on behalf the user.
+     */
+    public @NonNull ActivityOptions setPendingIntentBackgroundActivityStartMode(
+            @BackgroundActivityStartMode int state) {
+        super.setPendingIntentBackgroundActivityStartMode(state);
+        return this;
+    }
+
+    /**
+     * Get the mode for allowing or denying the senders privileges to start background activities
+     * to the PendingIntent.
+     *
+     * @see #setPendingIntentBackgroundActivityStartMode(int)
+     */
+    public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
+        return super.getPendingIntentBackgroundActivityStartMode();
+    }
+
     /** @hide */
     @Override
     public String toString() {
diff --git a/core/java/android/app/BackgroundStartPrivileges.java b/core/java/android/app/BackgroundStartPrivileges.java
new file mode 100644
index 0000000..76c0ccf
--- /dev/null
+++ b/core/java/android/app/BackgroundStartPrivileges.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Privileges granted to a Process that allows it to execute starts from the background.
+ * @hide
+ */
+public class BackgroundStartPrivileges {
+    /** No privileges. */
+    public static final BackgroundStartPrivileges NONE = new BackgroundStartPrivileges(
+            false, false, null);
+    /** Allow activity starts (and implies allowing foreground service starts).  */
+    public static final BackgroundStartPrivileges ALLOW_BAL = new BackgroundStartPrivileges(
+            true, true, null);
+    /** Allow foreground service starts. */
+    public static final BackgroundStartPrivileges ALLOW_FGS = new BackgroundStartPrivileges(
+            false, true, null);
+
+    private final boolean mAllowsBackgroundActivityStarts;
+    private final boolean mAllowsBackgroundForegroundServiceStarts;
+    private final IBinder mOriginatingToken;
+
+    private BackgroundStartPrivileges(boolean allowsBackgroundActivityStarts,
+            boolean allowsBackgroundForegroundServiceStarts, @Nullable IBinder originatingToken) {
+        Preconditions.checkArgument(
+                !allowsBackgroundActivityStarts || allowsBackgroundForegroundServiceStarts,
+                "backgroundActivityStarts implies bgFgServiceStarts");
+        mAllowsBackgroundActivityStarts = allowsBackgroundActivityStarts;
+        mAllowsBackgroundForegroundServiceStarts = allowsBackgroundForegroundServiceStarts;
+        mOriginatingToken = originatingToken;
+    }
+
+    /**
+     * Return a token that allows background activity starts and attributes it to a specific
+     * originatingToken.
+     */
+    public static BackgroundStartPrivileges allowBackgroundActivityStarts(
+            @Nullable IBinder originatingToken) {
+        if (originatingToken == null) {
+            // try to avoid creating new instances
+            return ALLOW_BAL;
+        }
+        return new BackgroundStartPrivileges(true, true, originatingToken);
+    }
+
+    /**
+     * Merge this {@link BackgroundStartPrivileges} with another {@link BackgroundStartPrivileges}.
+     *
+     * The resulting object will grant the union of the privileges of the merged objects.
+     * The originating tokens is retained only if both {@link BackgroundStartPrivileges} are the
+     * same.
+     *
+     * If one of the merged objects is {@link #NONE} then the other object is returned and the
+     * originating token is NOT cleared.
+     */
+    public @NonNull BackgroundStartPrivileges merge(@Nullable BackgroundStartPrivileges other) {
+        // shortcuts in case
+        if (other == NONE || other == null) {
+            return this;
+        }
+        if (this == NONE) {
+            return other;
+        }
+
+        boolean allowsBackgroundActivityStarts =
+                this.allowsBackgroundActivityStarts() || other.allowsBackgroundActivityStarts();
+        boolean allowsBackgroundFgsStarts =
+                this.allowsBackgroundFgsStarts() || other.allowsBackgroundFgsStarts();
+        if (this.mOriginatingToken == other.mOriginatingToken) {
+            // can reuse this?
+            if (this.mAllowsBackgroundActivityStarts == allowsBackgroundActivityStarts
+                    && this.mAllowsBackgroundForegroundServiceStarts == allowsBackgroundFgsStarts) {
+                return this;
+            }
+            // can reuse other?
+            if (other.mAllowsBackgroundActivityStarts == allowsBackgroundActivityStarts
+                   && other.mAllowsBackgroundForegroundServiceStarts == allowsBackgroundFgsStarts) {
+                return other;
+            }
+            // need to create a new instance (this should never happen)
+            return new BackgroundStartPrivileges(allowsBackgroundActivityStarts,
+                    allowsBackgroundFgsStarts, this.mOriginatingToken);
+        } else {
+            // no originating token -> can use standard instance
+            if (allowsBackgroundActivityStarts) {
+                return ALLOW_BAL;
+            } else if (allowsBackgroundFgsStarts) {
+                return ALLOW_FGS;
+            } else {
+                return NONE;
+            }
+        }
+    }
+
+    /**
+     * Merge a collection of {@link BackgroundStartPrivileges} into a single token.
+     *
+     * The resulting object will grant the union of the privileges of the merged objects.
+     * The originating tokens is retained only if all {@link BackgroundStartPrivileges} are the
+     * same.
+     *
+     * If the list contains {@link #NONE}s these are ignored.
+     */
+    public static @NonNull BackgroundStartPrivileges merge(
+            @Nullable List<BackgroundStartPrivileges> list) {
+        if (list == null || list.isEmpty()) {
+            return NONE;
+        }
+        BackgroundStartPrivileges current = list.get(0);
+        for (int i = list.size(); i-- > 1; ) {
+            current = current.merge(list.get(i));
+        }
+        return current;
+    }
+
+    /**
+     * @return {@code true} if this grants the permission to start background activities from the
+     * background.
+     */
+    public boolean allowsBackgroundActivityStarts() {
+        return mAllowsBackgroundActivityStarts;
+    }
+
+    /**
+     * @return {@code true} this grants the permission to start foreground services from the
+     * background. */
+    public boolean allowsBackgroundFgsStarts() {
+        return mAllowsBackgroundForegroundServiceStarts;
+    }
+
+    /** @return true if this grants any privileges. */
+    public boolean allowsAny() {
+        return mAllowsBackgroundActivityStarts || mAllowsBackgroundForegroundServiceStarts;
+    }
+
+    /** Return true if this grants no privileges. */
+    public boolean allowsNothing() {
+        return !allowsAny();
+    }
+
+    /**
+     * Gets the originating token.
+     *
+     * The originating token is optional information that allows to trace back the origin of this
+     * object. Besides debugging, this is used to e.g. identify privileges created by the
+     * notification service.
+     */
+    public @Nullable IBinder getOriginatingToken() {
+        return mOriginatingToken;
+    }
+
+    @Override
+    public String toString() {
+        return "BackgroundStartPrivileges["
+                + "allowsBackgroundActivityStarts=" + mAllowsBackgroundActivityStarts
+                + ", allowsBackgroundForegroundServiceStarts="
+                + mAllowsBackgroundForegroundServiceStarts
+                + ", originatingToken=" + mOriginatingToken
+                + ']';
+    }
+}
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 5cf10d0..9ecf8ff 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -389,26 +389,6 @@
     }
 
     /**
-     * Set PendingIntent activity is allowed to be started in the background if the caller
-     * can start background activities.
-     * @hide
-     */
-    @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
-    public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
-        super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
-    }
-
-    /**
-     * Get PendingIntent activity is allowed to be started in the background if the caller
-     * can start background activities.
-     * @hide
-     */
-    @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
-    public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
-        return super.isPendingIntentBackgroundActivityLaunchAllowed();
-    }
-
-    /**
      * Return {@link #setTemporaryAppAllowlist}.
      * @hide
      */
@@ -937,6 +917,25 @@
     }
 
     /**
+     * Sets the mode for allowing or denying the senders privileges to start background activities
+     * to the PendingIntent.
+     *
+     * This is typically used when executing {@link PendingIntent#send(Bundle)} or similar
+     * methods. A privileged sender of a PendingIntent should only grant
+     * MODE_BACKGROUND_ACTIVITY_START_ALLOWED if the PendingIntent is from a trusted source and/or
+     * executed on behalf the user.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    @Override // to narrow down the return type
+    public BroadcastOptions setPendingIntentBackgroundActivityStartMode(int state) {
+        super.setPendingIntentBackgroundActivityStartMode(state);
+        return this;
+    }
+
+    /**
      * 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.
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 74db39f..3776369 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -16,21 +16,22 @@
 
 package android.app;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.os.Bundle;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
+ * Base class for {@link ActivityOptions} and {@link BroadcastOptions}.
  * @hide
  */
 public class ComponentOptions {
 
     /**
-     * Default value for KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED.
-     * @hide
-     **/
-    public static final boolean PENDING_INTENT_BAL_ALLOWED_DEFAULT = true;
-
-    /**
      * PendingIntent caller allows activity start even if PendingIntent creator is in background.
      * This only works if the PendingIntent caller is allowed to start background activities,
      * for example if it's in the foreground, or has BAL permission.
@@ -52,10 +53,23 @@
      */
     public static final String KEY_INTERACTIVE = "android:component.isInteractive";
 
-    private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
+    private @Nullable Boolean mPendingIntentBalAllowed = null;
     private boolean mPendingIntentBalAllowedByPermission = false;
     private boolean mIsInteractive = false;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
+            MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+            MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
+            MODE_BACKGROUND_ACTIVITY_START_DENIED})
+    public @interface BackgroundActivityStartMode {}
+    /** No explicit value chosen. The system will decide whether to grant privileges. */
+    public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
+    /** Allow the {@link PendingIntent} to use the background activity start privileges. */
+    public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
+    /** Deny the {@link PendingIntent} to use the background activity start privileges. */
+    public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
+
     ComponentOptions() {
     }
 
@@ -63,12 +77,16 @@
         // If the remote side sent us bad parcelables, they won't get the
         // results they want, which is their loss.
         opts.setDefusable(true);
-        setPendingIntentBackgroundActivityLaunchAllowed(
-                opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
-                        PENDING_INTENT_BAL_ALLOWED_DEFAULT));
+
+        boolean pendingIntentBalAllowedIsSetExplicitly =
+                opts.containsKey(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
+        if (pendingIntentBalAllowedIsSetExplicitly) {
+            mPendingIntentBalAllowed =
+                    opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
+        }
         setPendingIntentBackgroundActivityLaunchAllowedByPermission(
-                opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
-                        false));
+                opts.getBoolean(
+                        KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false));
         mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false);
     }
 
@@ -97,20 +115,74 @@
     /**
      * Set PendingIntent activity is allowed to be started in the background if the caller
      * can start background activities.
+     *
+     * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
+     * of states
      */
-    public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+    @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
         mPendingIntentBalAllowed = allowed;
     }
 
     /**
-     * Get PendingIntent activity is allowed to be started in the background if the caller
-     * can start background activities.
+     * Get PendingIntent activity is allowed to be started in the background if the caller can start
+     * background activities.
+     *
+     * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
+     * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
+     * not match the actual behavior if the value was not explicitly set.
      */
-    public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+    @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+        if (mPendingIntentBalAllowed == null) {
+            // cannot return null, so return the value used up to API level 33 for compatibility
+            return true;
+        }
         return mPendingIntentBalAllowed;
     }
 
     /**
+     * Sets the mode for allowing or denying the senders privileges to start background activities
+     * to the PendingIntent.
+     *
+     * This is typically used in when executing {@link PendingIntent#send(Bundle)} or similar
+     * methods. A privileged sender of a PendingIntent should only grant
+     * {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED} if the PendingIntent is from a trusted source
+     * and/or executed on behalf the user.
+     */
+    public @NonNull ComponentOptions setPendingIntentBackgroundActivityStartMode(
+            @BackgroundActivityStartMode int state) {
+        switch (state) {
+            case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+                mPendingIntentBalAllowed = null;
+                break;
+            case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+                mPendingIntentBalAllowed = true;
+                break;
+            case MODE_BACKGROUND_ACTIVITY_START_DENIED:
+                mPendingIntentBalAllowed = false;
+                break;
+            default:
+                throw new IllegalArgumentException(state + " is not valid");
+        }
+        return this;
+    }
+
+    /**
+     * Gets the mode for allowing or denying the senders privileges to start background activities
+     * to the PendingIntent.
+     *
+     * @see #setPendingIntentBackgroundActivityStartMode(int)
+     */
+    public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
+        if (mPendingIntentBalAllowed == null) {
+            return MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+        } else if (mPendingIntentBalAllowed) {
+            return MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+        } else {
+            return MODE_BACKGROUND_ACTIVITY_START_DENIED;
+        }
+    }
+
+    /**
      * Set PendingIntent activity can be launched from background if caller has BAL permission.
      * @hide
      */
@@ -129,7 +201,9 @@
 
     public Bundle toBundle() {
         Bundle b = new Bundle();
-        b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+        if (mPendingIntentBalAllowed != null) {
+            b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+        }
         if (mPendingIntentBalAllowedByPermission) {
             b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
                     mPendingIntentBalAllowedByPermission);
diff --git a/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java b/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
new file mode 100644
index 0000000..982ad63
--- /dev/null
+++ b/core/tests/coretests/src/android/app/BackgroundStartPrivilegesTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.BackgroundStartPrivileges.ALLOW_BAL;
+import static android.app.BackgroundStartPrivileges.ALLOW_FGS;
+import static android.app.BackgroundStartPrivileges.NONE;
+import static android.app.BackgroundStartPrivileges.allowBackgroundActivityStarts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Binder;
+import android.os.IBinder;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BackgroundStartPrivilegesTest {
+
+    private static final IBinder BINDER_A = new Binder();
+    private static final IBinder BINDER_B = new Binder();
+    private static final BackgroundStartPrivileges BSP_ALLOW_A =
+            allowBackgroundActivityStarts(BINDER_A);
+    private static final BackgroundStartPrivileges BSP_ALLOW_B =
+            allowBackgroundActivityStarts(BINDER_B);
+
+    @Test
+    public void backgroundStartPrivilege_getters_work() {
+        assertThat(ALLOW_BAL.getOriginatingToken()).isNull();
+        assertThat(ALLOW_BAL.allowsBackgroundActivityStarts()).isEqualTo(true);
+        assertThat(ALLOW_BAL.allowsBackgroundFgsStarts()).isEqualTo(true);
+        assertThat(ALLOW_BAL.allowsAny()).isEqualTo(true);
+        assertThat(ALLOW_BAL.allowsNothing()).isEqualTo(false);
+
+        assertThat(ALLOW_FGS.getOriginatingToken()).isNull();
+        assertThat(ALLOW_FGS.allowsBackgroundActivityStarts()).isEqualTo(false);
+        assertThat(ALLOW_FGS.allowsBackgroundFgsStarts()).isEqualTo(true);
+        assertThat(ALLOW_FGS.allowsAny()).isEqualTo(true);
+        assertThat(ALLOW_FGS.allowsNothing()).isEqualTo(false);
+
+        assertThat(NONE.getOriginatingToken()).isNull();
+        assertThat(NONE.allowsBackgroundActivityStarts()).isEqualTo(false);
+        assertThat(NONE.allowsBackgroundFgsStarts()).isEqualTo(false);
+        assertThat(NONE.allowsAny()).isEqualTo(false);
+        assertThat(NONE.allowsNothing()).isEqualTo(true);
+
+        assertThat(BSP_ALLOW_A.getOriginatingToken()).isEqualTo(BINDER_A);
+        assertThat(BSP_ALLOW_A.allowsBackgroundActivityStarts()).isEqualTo(true);
+        assertThat(BSP_ALLOW_A.allowsBackgroundFgsStarts()).isEqualTo(true);
+        assertThat(BSP_ALLOW_A.allowsAny()).isEqualTo(true);
+        assertThat(BSP_ALLOW_A.allowsNothing()).isEqualTo(false);
+    }
+
+    @Test
+    public void backgroundStartPrivilege_toString_returnsSomething() {
+        assertThat(ALLOW_BAL.toString()).isNotEmpty();
+        assertThat(ALLOW_FGS.toString()).isNotEmpty();
+        assertThat(NONE.toString()).isNotEmpty();
+        assertThat(BSP_ALLOW_A.toString()).isNotEmpty();
+    }
+
+    @Test
+    public void backgroundStartPrivilege_mergeAA_resultsInA() {
+        assertThat(BSP_ALLOW_A.merge(BSP_ALLOW_A)).isEqualTo(BSP_ALLOW_A);
+    }
+
+    @Test
+    public void backgroundStartPrivilege_mergeAB_resultsInAllowBal() {
+        assertThat(BSP_ALLOW_A.merge(BSP_ALLOW_B)).isEqualTo(ALLOW_BAL);
+    }
+
+    @Test
+    public void backgroundStartPrivilege_mergeAwithAllowBal_resultsInAllowBal() {
+        assertThat(BSP_ALLOW_A.merge(ALLOW_BAL)).isEqualTo(ALLOW_BAL);
+    }
+
+    @Test
+    public void backgroundStartPrivilege_mergeAwithAllowFgs_resultsInAllowBal() {
+        assertThat(BSP_ALLOW_A.merge(ALLOW_FGS)).isEqualTo(ALLOW_BAL);
+    }
+
+    @Test
+    public void backgroundStartPrivilege_mergeAwithNone_resultsInA() {
+        assertThat(BSP_ALLOW_A.merge(NONE)).isEqualTo(BSP_ALLOW_A);
+    }
+
+    @Test
+    public void backgroundStartPrivilege_mergeManyWithDifferentToken_resultsInAllowBal() {
+        assertThat(BackgroundStartPrivileges.merge(
+                Arrays.asList(BSP_ALLOW_A, BSP_ALLOW_B, NONE, BSP_ALLOW_A, ALLOW_FGS)))
+                .isEqualTo(ALLOW_BAL);
+    }
+
+    @Test
+    public void backgroundStartPrivilege_mergeManyWithSameToken_resultsInAllowBal() {
+        assertThat(BackgroundStartPrivileges.merge(
+                Arrays.asList(BSP_ALLOW_A, BSP_ALLOW_A, BSP_ALLOW_A, BSP_ALLOW_A)))
+                .isEqualTo(BSP_ALLOW_A);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 9154231..6719fdb 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -112,6 +112,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
 import android.app.ForegroundServiceDelegationOptions;
 import android.app.ForegroundServiceStartNotAllowedException;
 import android.app.ForegroundServiceTypePolicy;
@@ -733,13 +734,13 @@
             @Nullable String callingFeatureId, final int userId)
             throws TransactionTooLargeException {
         return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
-                callingPackage, callingFeatureId, userId, false, null);
+                callingPackage, callingFeatureId, userId, BackgroundStartPrivileges.NONE);
     }
 
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
             int callingPid, int callingUid, boolean fgRequired,
             String callingPackage, @Nullable String callingFeatureId, final int userId,
-            boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken)
+            BackgroundStartPrivileges backgroundStartPrivileges)
             throws TransactionTooLargeException {
         if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
                 + " type=" + resolvedType + " args=" + service.getExtras());
@@ -777,7 +778,7 @@
         // the timeout)
         ServiceRecord r = res.record;
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
-                allowBackgroundActivityStarts, false /* isBindService */);
+                backgroundStartPrivileges, false /* isBindService */);
 
         if (!mAm.mUserController.exists(r.userId)) {
             Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -894,8 +895,8 @@
         // The package could be frozen (meaning it's doing surgery), defer the actual
         // start until the package is unfrozen.
         if (deferServiceBringupIfFrozenLocked(r, service, callingPackage, callingFeatureId,
-                callingUid, callingPid, fgRequired, callerFg, userId, allowBackgroundActivityStarts,
-                backgroundActivityStartsToken, false, null)) {
+                callingUid, callingPid, fgRequired, callerFg, userId,
+                backgroundStartPrivileges, false, null)) {
             return null;
         }
 
@@ -916,8 +917,8 @@
         final ComponentName realResult =
                 startServiceInnerLocked(r, service, callingUid, callingPid,
                         getCallingProcessNameLocked(callingUid, callingPid, callingPackage),
-                        fgRequired, callerFg, allowBackgroundActivityStarts,
-                        backgroundActivityStartsToken);
+                        fgRequired, callerFg,
+                        backgroundStartPrivileges);
         if (res.aliasComponent != null
                 && !realResult.getPackageName().startsWith("!")
                 && !realResult.getPackageName().startsWith("?")) {
@@ -937,8 +938,9 @@
 
     private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
             int callingUid, int callingPid, String callingProcessName, boolean fgRequired,
-            boolean callerFg, boolean allowBackgroundActivityStarts,
-            @Nullable IBinder backgroundActivityStartsToken) throws TransactionTooLargeException {
+            boolean callerFg,
+            BackgroundStartPrivileges backgroundStartPrivileges)
+            throws TransactionTooLargeException {
         NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
                 service, callingUid, r.packageName, r.userId);
         if (unscheduleServiceRestartLocked(r, callingUid, false)) {
@@ -1031,8 +1033,8 @@
                         "Not potential delay (user " + r.userId + " not started): " + r);
             }
         }
-        if (allowBackgroundActivityStarts) {
-            r.allowBgActivityStartsOnServiceStart(backgroundActivityStartsToken);
+        if (backgroundStartPrivileges.allowsAny()) {
+            r.allowBgActivityStartsOnServiceStart(backgroundStartPrivileges);
         }
         ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting,
                 callingUid, callingProcessName, wasStartRequested);
@@ -1140,7 +1142,7 @@
     private boolean deferServiceBringupIfFrozenLocked(ServiceRecord s, Intent serviceIntent,
             String callingPackage, @Nullable String callingFeatureId,
             int callingUid, int callingPid, boolean fgRequired, boolean callerFg, int userId,
-            boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken,
+            BackgroundStartPrivileges backgroundStartPrivileges,
             boolean isBinding, IServiceConnection connection) {
         final PackageManagerInternal pm = mAm.getPackageManagerInternal();
         final boolean frozen = pm.isPackageFrozen(s.packageName, callingUid, s.userId);
@@ -1188,7 +1190,7 @@
                         try {
                             startServiceInnerLocked(s, serviceIntent, callingUid, callingPid,
                                     callingProcessName, fgRequired, callerFg,
-                                    allowBackgroundActivityStarts, backgroundActivityStartsToken);
+                                    backgroundStartPrivileges);
                         } catch (TransactionTooLargeException e) {
                             /* ignore - local call */
                         }
@@ -2007,7 +2009,7 @@
                                 resetFgsRestrictionLocked(r);
                                 setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                         r.appInfo.uid, r.intent.getIntent(), r, r.userId,
-                                        false /* allowBackgroundActivityStarts */,
+                                        BackgroundStartPrivileges.NONE,
                                         false /* isBindService */);
                                 final String temp = "startForegroundDelayMs:" + delayMs;
                                 if (r.mInfoAllowStartForeground != null) {
@@ -2027,7 +2029,7 @@
                         // started. Check for app state again.
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                 r.appInfo.uid, r.intent.getIntent(), r, r.userId,
-                                false /* allowBackgroundActivityStarts */,
+                                BackgroundStartPrivileges.NONE,
                                 false /* isBindService */);
                     }
                     // If the foreground service is not started from TOP process, do not allow it to
@@ -3301,7 +3303,8 @@
         // The package could be frozen (meaning it's doing surgery), defer the actual
         // binding until the package is unfrozen.
         boolean packageFrozen = deferServiceBringupIfFrozenLocked(s, service, callingPackage, null,
-                callingUid, callingPid, false, callerFg, userId, false, null, true, connection);
+                callingUid, callingPid, false, callerFg, userId, BackgroundStartPrivileges.NONE,
+                true, connection);
 
         // If permissions need a review before any of the app components can run,
         // we schedule binding to the service but do not start its process, then
@@ -3400,7 +3403,7 @@
                 }
             }
             setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
-                    false /* allowBackgroundActivityStarts */, true /* isBindService */);
+                    BackgroundStartPrivileges.NONE, true /* isBindService */);
 
             if (s.app != null) {
                 ProcessServiceRecord servicePsr = s.app.mServices;
@@ -7056,7 +7059,7 @@
      */
     private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
-            boolean allowBackgroundActivityStarts, boolean isBindService) {
+            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
         r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
         // Check DeviceConfig flag.
         if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
@@ -7066,7 +7069,7 @@
         if (!r.mAllowWhileInUsePermissionInFgs
                 || (r.mAllowStartForeground == REASON_DENIED)) {
             final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
-                    callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts,
+                    callingPackage, callingPid, callingUid, r, backgroundStartPrivileges,
                     isBindService);
             if (!r.mAllowWhileInUsePermissionInFgs) {
                 r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
@@ -7074,7 +7077,7 @@
             if (r.mAllowStartForeground == REASON_DENIED) {
                 r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
                         allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
-                        userId, isBindService);
+                        backgroundStartPrivileges, isBindService);
             }
         }
     }
@@ -7094,10 +7097,10 @@
         }
         final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                 callingPackage, callingPid, callingUid, null /* serviceRecord */,
-                false /* allowBackgroundActivityStarts */, false);
+                BackgroundStartPrivileges.NONE, false);
         @ReasonCode int allowStartFgs = shouldAllowFgsStartForegroundNoBindingCheckLocked(
                 allowWhileInUse, callingPid, callingUid, callingPackage, null /* targetService */,
-                false /* isBindService */);
+                BackgroundStartPrivileges.NONE);
 
         if (allowStartFgs == REASON_DENIED) {
             if (canBindingClientStartFgsLocked(callingUid) != null) {
@@ -7117,7 +7120,7 @@
      */
     private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage,
             int callingPid, int callingUid, @Nullable ServiceRecord targetService,
-            boolean allowBackgroundActivityStarts, boolean isBindService) {
+            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
         int ret = REASON_DENIED;
 
         final int uidState = mAm.getUidStateLocked(callingUid);
@@ -7138,7 +7141,7 @@
 
         if (ret == REASON_DENIED) {
             // Is the allow activity background start flag on?
-            if (allowBackgroundActivityStarts) {
+            if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
                 ret = REASON_START_ACTIVITY_FLAG;
             }
         }
@@ -7271,12 +7274,13 @@
                                         shouldAllowFgsWhileInUsePermissionLocked(
                                                 clientPackageName,
                                                 clientPid, clientUid, null /* serviceRecord */,
-                                                false /* allowBackgroundActivityStarts */, false);
+                                                BackgroundStartPrivileges.NONE, false);
                                 final @ReasonCode int allowStartFgs =
                                         shouldAllowFgsStartForegroundNoBindingCheckLocked(
                                                 allowWhileInUse2,
                                                 clientPid, clientUid, clientPackageName,
-                                                null /* targetService */, false);
+                                                null /* targetService */,
+                                                BackgroundStartPrivileges.NONE);
                                 if (allowStartFgs != REASON_DENIED) {
                                     return new Pair<>(allowStartFgs, clientPackageName);
                                 } else {
@@ -7308,11 +7312,12 @@
      */
     private @ReasonCode int shouldAllowFgsStartForegroundWithBindingCheckLocked(
             @ReasonCode int allowWhileInUse, String callingPackage, int callingPid,
-            int callingUid, Intent intent, ServiceRecord r, int userId, boolean isBindService) {
+            int callingUid, Intent intent, ServiceRecord r,
+            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
         ActivityManagerService.FgsTempAllowListItem tempAllowListReason =
                 r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid);
         int ret = shouldAllowFgsStartForegroundNoBindingCheckLocked(allowWhileInUse, callingPid,
-                callingUid, callingPackage, r, isBindService);
+                callingUid, callingPackage, r, backgroundStartPrivileges);
 
         String bindFromPackage = null;
         if (ret == REASON_DENIED) {
@@ -7358,7 +7363,8 @@
 
     private @ReasonCode int shouldAllowFgsStartForegroundNoBindingCheckLocked(
             @ReasonCode int allowWhileInUse, int callingPid, int callingUid, String callingPackage,
-            @Nullable ServiceRecord targetService, boolean isBindService) {
+            @Nullable ServiceRecord targetService,
+            BackgroundStartPrivileges backgroundStartPrivileges) {
         int ret = allowWhileInUse;
 
         if (ret == REASON_DENIED) {
@@ -7406,6 +7412,12 @@
         }
 
         if (ret == REASON_DENIED) {
+            if (backgroundStartPrivileges.allowsBackgroundFgsStarts()) {
+                ret = REASON_START_ACTIVITY_FLAG;
+            }
+        }
+
+        if (ret == REASON_DENIED) {
             if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
                     callingPackage)) {
                 ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
@@ -7489,6 +7501,7 @@
                 ret = REASON_OPT_OUT_REQUESTED;
             }
         }
+
         return ret;
     }
 
@@ -7652,7 +7665,7 @@
             String callingPackage) {
         return shouldAllowFgsWhileInUsePermissionLocked(callingPackage, callingPid, callingUid,
                 /* targetService */ null,
-                /* allowBackgroundActivityStarts */ false, false)
+                BackgroundStartPrivileges.NONE, false)
                 != REASON_DENIED;
     }
 
@@ -7759,7 +7772,7 @@
         r.mFgsEnterTime = SystemClock.uptimeMillis();
         r.foregroundServiceType = options.mForegroundServiceTypes;
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
-                false, false);
+                BackgroundStartPrivileges.NONE, false);
         final ProcessServiceRecord psr = callerApp.mServices;
         final boolean newService = psr.startService(r);
         // updateOomAdj.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 09c4eb2..9752e8b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -183,6 +183,7 @@
 import android.app.ApplicationErrorReport;
 import android.app.ApplicationExitInfo;
 import android.app.ApplicationThreadConstants;
+import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.ComponentOptions;
 import android.app.ContentProviderHolder;
@@ -3990,8 +3991,8 @@
                                 null /* resultData */, null /* resultExtras */,
                                 isInstantApp ? permission.ACCESS_INSTANT_APPS : null,
                                 null /* bOptions */, false /* serialized */, false /* sticky */,
-                                resolvedUserId, false /* allowBackgroundActivityStarts */,
-                                null /* backgroundActivityStartsToken */, visibilityAllowList);
+                                resolvedUserId, BackgroundStartPrivileges.NONE,
+                                visibilityAllowList);
                     }
 
                     if (observer != null) {
@@ -4538,8 +4539,7 @@
                 null /* requiredPermissions */, null /* excludedPermissions */,
                 null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
                 false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
-                Binder.getCallingPid(), userId, false /* allowBackgroundActivityStarts */,
-                null /* backgroundActivityStartsToken */,
+                Binder.getCallingPid(), userId, BackgroundStartPrivileges.NONE,
                 broadcastAllowList, null /* filterExtrasForReceiver */);
     }
 
@@ -13774,8 +13774,9 @@
                     BroadcastQueue queue = broadcastQueueForIntent(intent);
                     BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                             null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
-                            receivers, null, null, 0, null, null, false, true, true, -1, false,
-                            null, false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+                            receivers, null, null, 0, null, null, false, true, true, -1,
+                            BackgroundStartPrivileges.NONE,
+                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
                             null /* filterExtrasForReceiver */);
                     queue.enqueueBroadcastLocked(r);
                 }
@@ -14052,8 +14053,7 @@
                 resolvedType, null, resultTo, resultCode, resultData, resultExtras,
                 requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
                 ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
-                false /* allowBackgroundActivityStarts */,
-                null /* tokenNeededForBackgroundActivityStarts */,
+                BackgroundStartPrivileges.NONE,
                 null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
     }
 
@@ -14065,8 +14065,7 @@
             String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
             boolean ordered, boolean sticky, int callingPid, int callingUid,
             int realCallingUid, int realCallingPid, int userId,
-            boolean allowBackgroundActivityStarts,
-            @Nullable IBinder backgroundActivityStartsToken,
+            BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList,
             @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
         final int cookie = BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
@@ -14074,7 +14073,7 @@
                 intent, resolvedType, resultToApp, resultTo, resultCode, resultData, resultExtras,
                 requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
                 ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
-                allowBackgroundActivityStarts, backgroundActivityStartsToken, broadcastAllowList,
+                backgroundStartPrivileges, broadcastAllowList,
                 filterExtrasForReceiver);
         BroadcastQueue.traceEnd(cookie);
         return res;
@@ -14088,8 +14087,7 @@
             String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
             boolean ordered, boolean sticky, int callingPid, int callingUid,
             int realCallingUid, int realCallingPid, int userId,
-            boolean allowBackgroundActivityStarts,
-            @Nullable IBinder backgroundActivityStartsToken,
+            BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList,
             @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
         // Ensure all internal loopers are registered for idle checks
@@ -14210,9 +14208,8 @@
                     Slog.w(TAG, msg);
                     throw new SecurityException(msg);
                 } else {
-                    allowBackgroundActivityStarts = true;
                     // We set the token to null since if it wasn't for it we'd allow anyway here
-                    backgroundActivityStartsToken = null;
+                    backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
                 }
             }
 
@@ -14731,8 +14728,8 @@
                     callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
                     registeredReceivers, resultToApp, resultTo, resultCode, resultData,
-                    resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts,
-                    backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
+                    resultExtras, ordered, sticky, false, userId,
+                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver);
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
             queue.enqueueBroadcastLocked(r);
             registeredReceivers = null;
@@ -14825,8 +14822,8 @@
                     callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
                     receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
-                    ordered, sticky, false, userId, allowBackgroundActivityStarts,
-                    backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
+                    ordered, sticky, false, userId,
+                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver);
 
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
             queue.enqueueBroadcastLocked(r);
@@ -14971,7 +14968,7 @@
                         intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
                         resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
                         appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
-                        callingPid, userId, false, null, null, null);
+                        callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -14983,8 +14980,9 @@
             int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
             ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
             String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
-            boolean serialized, boolean sticky, int userId, boolean allowBackgroundActivityStarts,
-            @Nullable IBinder backgroundActivityStartsToken, @Nullable int[] broadcastAllowList) {
+            boolean serialized, boolean sticky, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList) {
         synchronized(this) {
             intent = verifyBroadcastLocked(intent);
 
@@ -14995,8 +14993,8 @@
                 return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
                         resultToApp, resultTo, resultCode, resultData, resultExtras,
                         requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
-                        uid, realCallingUid, realCallingPid, userId, allowBackgroundActivityStarts,
-                        backgroundActivityStartsToken, broadcastAllowList,
+                        uid, realCallingUid, realCallingPid, userId,
+                        backgroundStartPrivileges, broadcastAllowList,
                         null /* filterExtrasForReceiver */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
@@ -17583,16 +17581,16 @@
                 IApplicationThread resultToThread, IIntentReceiver resultTo, int resultCode,
                 String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
                 boolean serialized, boolean sticky, int userId,
-                boolean allowBackgroundActivityStarts,
-                @Nullable IBinder backgroundActivityStartsToken,
+                BackgroundStartPrivileges backgroundStartPrivileges,
                 @Nullable int[] broadcastAllowList) {
             synchronized (ActivityManagerService.this) {
                 final ProcessRecord resultToApp = getRecordForAppLOSP(resultToThread);
                 return ActivityManagerService.this.broadcastIntentInPackage(packageName, featureId,
                         uid, realCallingUid, realCallingPid, intent, resolvedType, resultToApp,
                         resultTo, resultCode, resultData, resultExtras, requiredPermission,
-                        bOptions, serialized, sticky, userId, allowBackgroundActivityStarts,
-                        backgroundActivityStartsToken, broadcastAllowList);
+                        bOptions, serialized, sticky, userId,
+                        backgroundStartPrivileges,
+                        broadcastAllowList);
             }
         }
 
@@ -17618,8 +17616,7 @@
                             null /*excludedPermissions*/, null /*excludedPackages*/,
                             AppOpsManager.OP_NONE, bOptions /*options*/, serialized,
                             false /*sticky*/, callingPid, callingUid, callingUid, callingPid,
-                            userId, false /*allowBackgroundStarts*/,
-                            null /*tokenNeededForBackgroundActivityStarts*/,
+                            userId, BackgroundStartPrivileges.NONE,
                             appIdAllowList, filterExtrasForReceiver);
                 } finally {
                     Binder.restoreCallingIdentity(origId);
@@ -17646,8 +17643,7 @@
         @Override
         public ComponentName startServiceInPackage(int uid, Intent service, String resolvedType,
                 boolean fgRequired, String callingPackage, @Nullable String callingFeatureId,
-                int userId, boolean allowBackgroundActivityStarts,
-                @Nullable IBinder backgroundActivityStartsToken)
+                int userId, BackgroundStartPrivileges backgroundStartPrivileges)
                 throws TransactionTooLargeException {
             if (DEBUG_SERVICE) {
                 Slog.v(TAG_SERVICE,
@@ -17664,8 +17660,8 @@
                 synchronized (ActivityManagerService.this) {
                     res = mServices.startServiceLocked(null, service,
                             resolvedType, -1, uid, fgRequired, callingPackage,
-                            callingFeatureId, userId, allowBackgroundActivityStarts,
-                            backgroundActivityStartsToken);
+                            callingFeatureId, userId,
+                            backgroundStartPrivileges);
                 }
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index a86efa3..b1a01cc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -561,7 +561,7 @@
         // ...then schedule the removal of the token after the extended timeout
         mHandler.postAtTime(() -> {
             synchronized (mService) {
-                app.removeAllowBackgroundActivityStartsToken(r);
+                app.removeBackgroundStartPrivileges(r);
             }
         }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
     }
@@ -603,11 +603,11 @@
         if (state == BroadcastRecord.IDLE) {
             Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
         }
-        if (r.allowBackgroundActivityStarts && r.curApp != null) {
+        if (r.mBackgroundStartPrivileges.allowsAny() && r.curApp != null) {
             if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
                 // if the receiver has run for more than allowed bg activity start timeout,
                 // just remove the token for this process now and we're done
-                r.curApp.removeAllowBackgroundActivityStartsToken(r);
+                r.curApp.removeBackgroundStartPrivileges(r);
             } else {
                 // It gets more time; post the removal to happen at the appropriate moment
                 postActivityStartTokenRemoval(r.curApp, r);
@@ -840,7 +840,7 @@
             } else {
                 r.receiverTime = SystemClock.uptimeMillis();
                 r.scheduledTime[index] = r.receiverTime;
-                maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
+                maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
                 maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
                 maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
                 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
@@ -852,7 +852,8 @@
                 // parallel broadcasts are fire-and-forget, not bookended by a call to
                 // finishReceiverLocked(), so we manage their activity-start token here
                 if (filter.receiverList.app != null
-                        && r.allowBackgroundActivityStarts && !r.ordered) {
+                        && r.mBackgroundStartPrivileges.allowsAny()
+                        && !r.ordered) {
                     postActivityStartTokenRemoval(filter.receiverList.app, r);
                 }
             }
@@ -863,7 +864,7 @@
             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);
+                filter.receiverList.app.removeBackgroundStartPrivileges(r);
                 if (ordered) {
                     filter.receiverList.app.mReceivers.removeCurReceiver(r);
                     // Something wrong, its oom adj could be downgraded, but not in a hurry.
@@ -1303,7 +1304,7 @@
                 scheduleBroadcastsLocked();
             } else {
                 if (filter.receiverList != null) {
-                    maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
+                    maybeAddBackgroundStartPrivileges(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.
                 }
@@ -1393,7 +1394,7 @@
             try {
                 app.addPackage(info.activityInfo.packageName,
                         info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
-                maybeAddAllowBackgroundActivityStartsToken(app, r);
+                maybeAddBackgroundStartPrivileges(app, r);
                 r.mIsReceiverAppRunning = true;
                 processCurBroadcastLocked(r, app);
                 return;
@@ -1450,7 +1451,7 @@
             return;
         }
 
-        maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
+        maybeAddBackgroundStartPrivileges(r.curApp, r);
         mPendingBroadcast = r;
         mPendingBroadcastRecvIndex = recIdx;
     }
@@ -1537,8 +1538,8 @@
                 mService.getUidStateLocked(targetUid));
     }
 
-    private void maybeAddAllowBackgroundActivityStartsToken(ProcessRecord proc, BroadcastRecord r) {
-        if (r == null || proc == null || !r.allowBackgroundActivityStarts) {
+    private void maybeAddBackgroundStartPrivileges(ProcessRecord proc, BroadcastRecord r) {
+        if (r == null || proc == null || !r.mBackgroundStartPrivileges.allowsAny()) {
             return;
         }
         String msgToken = (proc.toShortString() + r.toString()).intern();
@@ -1546,7 +1547,7 @@
         // that request - we don't want the token to be swept from under our feet...
         mHandler.removeCallbacksAndMessages(msgToken);
         // ...then add the token
-        proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
+        proc.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
     }
 
     final void setBroadcastTimeoutLocked(long timeoutTime) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8fc4a21..99e2ac7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -281,7 +281,7 @@
                     final ProcessRecord app = (ProcessRecord) args.arg1;
                     final BroadcastRecord r = (BroadcastRecord) args.arg2;
                     args.recycle();
-                    app.removeAllowBackgroundActivityStartsToken(r);
+                    app.removeBackgroundStartPrivileges(r);
                 }
                 return true;
             }
@@ -1020,8 +1020,8 @@
                     MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
         }
 
-        if (r.allowBackgroundActivityStarts) {
-            app.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
+        if (r.mBackgroundStartPrivileges.allowsAny()) {
+            app.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
 
             final long timeout = r.isForeground() ? mFgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT
                     : mBgConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT;
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 9679fb8..4304377 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -32,6 +32,7 @@
 import android.annotation.UptimeMillisLong;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.compat.CompatChanges;
 import android.content.ComponentName;
@@ -42,7 +43,6 @@
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.PrintWriterPrinter;
@@ -135,12 +135,8 @@
     int deferredCount;      // number of receivers in deferred state.
     @Nullable BroadcastQueue queue;   // the outbound queue handling this broadcast
 
-    // if set to true, app's process will be temporarily allowed to start activities from background
-    // for the duration of the broadcast dispatch
-    final boolean allowBackgroundActivityStarts;
-    // token used to trace back the grant for activity starts, optional
-    @Nullable
-    final IBinder mBackgroundActivityStartsToken;
+    // Determines the privileges the app's process has in regard to background starts.
+    final BackgroundStartPrivileges mBackgroundStartPrivileges;
 
     // Filter the intent extras by using the rules of the package visibility before broadcasting
     // the intent to the receiver.
@@ -371,8 +367,9 @@
             BroadcastOptions _options, List _receivers,
             ProcessRecord _resultToApp, IIntentReceiver _resultTo, int _resultCode,
             String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
-            boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
-            @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt,
+            boolean _initialSticky, int _userId,
+            @NonNull BackgroundStartPrivileges backgroundStartPrivileges,
+            boolean timeoutExempt,
             @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
         if (_intent == null) {
             throw new NullPointerException("Can't construct with a null intent");
@@ -414,8 +411,7 @@
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
-        this.allowBackgroundActivityStarts = allowBackgroundActivityStarts;
-        mBackgroundActivityStartsToken = backgroundActivityStartsToken;
+        mBackgroundStartPrivileges = backgroundStartPrivileges;
         this.timeoutExempt = timeoutExempt;
         alarm = options != null && options.isAlarmBroadcast();
         pushMessage = options != null && options.isPushMessagingBroadcast();
@@ -479,8 +475,7 @@
         manifestCount = from.manifestCount;
         manifestSkipCount = from.manifestSkipCount;
         queue = from.queue;
-        allowBackgroundActivityStarts = from.allowBackgroundActivityStarts;
-        mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
+        mBackgroundStartPrivileges = from.mBackgroundStartPrivileges;
         timeoutExempt = from.timeoutExempt;
         alarm = from.alarm;
         pushMessage = from.pushMessage;
@@ -521,8 +516,8 @@
                 callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
                 requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
                 splitReceivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
-                ordered, sticky, initialSticky, userId, allowBackgroundActivityStarts,
-                mBackgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
+                ordered, sticky, initialSticky, userId,
+                mBackgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver);
         split.enqueueTime = this.enqueueTime;
         split.enqueueRealTime = this.enqueueRealTime;
         split.enqueueClockTime = this.enqueueClockTime;
@@ -601,7 +596,7 @@
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
                     uid2receiverList.valueAt(i), null /* _resultToApp */, null /* _resultTo */,
                     resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
-                    allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt,
+                    mBackgroundStartPrivileges, timeoutExempt,
                     filterExtrasForReceiver);
             br.enqueueTime = this.enqueueTime;
             br.enqueueRealTime = this.enqueueRealTime;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index fed0b11..874fda3 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -24,6 +24,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
@@ -330,20 +331,42 @@
         return activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission();
     }
 
-    public static boolean isPendingIntentBalAllowedByCaller(
-            @Nullable ActivityOptions activityOptions) {
+    /**
+     * Return the {@link BackgroundStartPrivileges} the activity options grant the PendingIntent to
+     * use caller's BAL permission.
+     */
+    public static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
+            @Nullable ActivityOptions activityOptions, int callingUid) {
         if (activityOptions == null) {
-            return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
+            // since the ActivityOptions were not created by the app itself, determine the default
+            // for the app
+            return getDefaultBackgroundStartPrivileges(callingUid);
         }
-        return isPendingIntentBalAllowedByCaller(activityOptions.toBundle());
+        return getBackgroundStartPrivilegesAllowedByCaller(activityOptions.toBundle(),
+                callingUid);
     }
 
-    private static boolean isPendingIntentBalAllowedByCaller(@Nullable Bundle options) {
-        if (options == null) {
-            return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
+    private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
+            @Nullable Bundle options, int callingUid) {
+        if (options == null || !options.containsKey(
+                        ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
+            return getDefaultBackgroundStartPrivileges(callingUid);
         }
-        return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
-                ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT);
+        return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
+                ? BackgroundStartPrivileges.ALLOW_BAL
+                : BackgroundStartPrivileges.NONE;
+    }
+
+    /**
+     * Default {@link BackgroundStartPrivileges} to be used if the intent sender has not made an
+     * explicit choice.
+     *
+     * @hide
+     */
+    public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(int callingUid) {
+        // TODO: In the next step this will return ALLOW_FGS instead, if the app that sent the
+        // PendingIntent is targeting Android U
+        return BackgroundStartPrivileges.ALLOW_BAL;
     }
 
     @Deprecated
@@ -493,11 +516,6 @@
             if (userId == UserHandle.USER_CURRENT) {
                 userId = controller.mUserController.getCurrentOrTargetUserId();
             }
-            // temporarily allow receivers and services to open activities from background if the
-            // PendingIntent.send() caller was foreground at the time of sendInner() call
-            final boolean allowTrampoline = uid != callingUid
-                    && controller.mAtmInternal.isUidForeground(callingUid)
-                    && isPendingIntentBalAllowedByCaller(options);
 
             // note: we on purpose don't pass in the information about the PendingIntent's creator,
             // like pid or ProcessRecord, to the ActivityTaskManagerInternal calls below, because
@@ -515,8 +533,7 @@
                                     allIntents, allResolvedTypes, resultTo, mergedOptions, userId,
                                     false /* validateIncomingUser */,
                                     this /* originatingPendingIntent */,
-                                    mAllowBgActivityStartsForActivitySender.contains(
-                                            allowlistToken));
+                                    getBackgroundStartPrivilegesForActivitySender(allowlistToken));
                         } else {
                             res = controller.mAtmInternal.startActivityInPackage(uid, callingPid,
                                     callingUid, key.packageName, key.featureId, finalIntent,
@@ -524,8 +541,7 @@
                                     mergedOptions, userId, null, "PendingIntentRecord",
                                     false /* validateIncomingUser */,
                                     this /* originatingPendingIntent */,
-                                    mAllowBgActivityStartsForActivitySender.contains(
-                                            allowlistToken));
+                                    getBackgroundStartPrivilegesForActivitySender(allowlistToken));
                         }
                     } catch (RuntimeException e) {
                         Slog.w(TAG, "Unable to send startActivity intent", e);
@@ -537,17 +553,17 @@
                     break;
                 case ActivityManager.INTENT_SENDER_BROADCAST:
                     try {
-                        final boolean allowedByToken =
-                                mAllowBgActivityStartsForBroadcastSender.contains(allowlistToken);
-                        final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null;
-
+                        final BackgroundStartPrivileges backgroundStartPrivileges =
+                                getBackgroundStartPrivilegesForActivitySender(
+                                        mAllowBgActivityStartsForBroadcastSender, allowlistToken,
+                                        options, callingUid);
                         // If a completion callback has been requested, require
                         // that the broadcast be delivered synchronously
                         int sent = controller.mAmInternal.broadcastIntentInPackage(key.packageName,
                                 key.featureId, uid, callingUid, callingPid, finalIntent,
                                 resolvedType, finishedReceiverThread, finishedReceiver, code, null,
                                 null, requiredPermission, options, (finishedReceiver != null),
-                                false, userId, allowedByToken || allowTrampoline, bgStartsToken,
+                                false, userId, backgroundStartPrivileges,
                                 null /* broadcastAllowList */);
                         if (sent == ActivityManager.BROADCAST_SUCCESS) {
                             sendFinish = false;
@@ -559,14 +575,14 @@
                 case ActivityManager.INTENT_SENDER_SERVICE:
                 case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:
                     try {
-                        final boolean allowedByToken =
-                                mAllowBgActivityStartsForServiceSender.contains(allowlistToken);
-                        final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null;
-
+                        final BackgroundStartPrivileges backgroundStartPrivileges =
+                                getBackgroundStartPrivilegesForActivitySender(
+                                        mAllowBgActivityStartsForServiceSender, allowlistToken,
+                                        options, callingUid);
                         controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType,
                                 key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE,
                                 key.packageName, key.featureId, userId,
-                                allowedByToken || allowTrampoline, bgStartsToken);
+                                backgroundStartPrivileges);
                     } catch (RuntimeException e) {
                         Slog.w(TAG, "Unable to send startService intent", e);
                     } catch (TransactionTooLargeException e) {
@@ -589,6 +605,27 @@
         return res;
     }
 
+    private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender(
+            IBinder allowlistToken) {
+        return mAllowBgActivityStartsForActivitySender.contains(allowlistToken)
+                ? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken)
+                : BackgroundStartPrivileges.NONE;
+    }
+
+    private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender(
+            ArraySet<IBinder> allowedTokenSet, IBinder allowlistToken,
+            Bundle options, int callingUid) {
+        if (allowedTokenSet.contains(allowlistToken)) {
+            return BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken);
+        }
+        // temporarily allow receivers and services to open activities from background if the
+        // PendingIntent.send() caller was foreground at the time of sendInner() call
+        if (uid != callingUid && controller.mAtmInternal.isUidForeground(callingUid)) {
+            return getBackgroundStartPrivilegesAllowedByCaller(options, callingUid);
+        }
+        return BackgroundStartPrivileges.NONE;
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index cf91429..e6cb596 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -25,6 +25,7 @@
 import android.app.ApplicationExitInfo;
 import android.app.ApplicationExitInfo.Reason;
 import android.app.ApplicationExitInfo.SubReason;
+import android.app.BackgroundStartPrivileges;
 import android.app.IApplicationThread;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManagerInternal;
@@ -1290,16 +1291,16 @@
      * {@param originatingToken} if you have one such originating token, this is useful for tracing
      * back the grant in the case of the notification token.
      */
-    void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
-            @Nullable IBinder originatingToken) {
+    void addOrUpdateBackgroundStartPrivileges(Binder entity,
+            BackgroundStartPrivileges backgroundStartPrivileges) {
         Objects.requireNonNull(entity);
-        mWindowProcessController.addOrUpdateAllowBackgroundActivityStartsToken(entity,
-                originatingToken);
+        mWindowProcessController.addOrUpdateBackgroundStartPrivileges(entity,
+                backgroundStartPrivileges);
     }
 
-    void removeAllowBackgroundActivityStartsToken(Binder entity) {
+    void removeBackgroundStartPrivileges(Binder entity) {
         Objects.requireNonNull(entity);
-        mWindowProcessController.removeAllowBackgroundActivityStartsToken(entity);
+        mWindowProcessController.removeBackgroundStartPrivileges(entity);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index def51b0..f667e33 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -21,6 +21,7 @@
 import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.Process.INVALID_UID;
 
+import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -28,6 +29,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.BackgroundStartPrivileges;
 import android.app.IApplicationThread;
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -154,18 +156,20 @@
 
     // any current binding to this service has BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS flag?
     private boolean mIsAllowedBgActivityStartsByBinding;
-    // is this service currently allowed to start activities from background by providing
-    // allowBackgroundActivityStarts=true to startServiceLocked()?
-    private boolean mIsAllowedBgActivityStartsByStart;
     // used to clean up the state of mIsAllowedBgActivityStartsByStart after a timeout
     private Runnable mCleanUpAllowBgActivityStartsByStartCallback;
     private ProcessRecord mAppForAllowingBgActivityStartsByStart;
-    // These are the originating tokens that currently allow bg activity starts by service start.
-    // This is used to trace back the grant when starting activities. We only pass such token to the
-    // ProcessRecord if it's the *only* cause for bg activity starts exemption, otherwise we pass
-    // null.
+    // These are the privileges that currently allow bg activity starts by service start.
+    // Each time the contents of this list change #mBackgroundStartPrivilegesByStartMerged has to
+    // be updated to reflect the merged state. The merged state retains the attribution to the
+    // originating token only if it is the only cause for being privileged.
     @GuardedBy("ams")
-    private List<IBinder> mBgActivityStartsByStartOriginatingTokens = new ArrayList<>();
+    private ArrayList<BackgroundStartPrivileges> mBackgroundStartPrivilegesByStart =
+            new ArrayList<>();
+
+    // merged privileges for mBackgroundStartPrivilegesByStart (for performance)
+    private BackgroundStartPrivileges mBackgroundStartPrivilegesByStartMerged =
+            BackgroundStartPrivileges.NONE;
 
     // allow while-in-use permissions in foreground service or not.
     // while-in-use permissions in FGS started from background might be restricted.
@@ -584,9 +588,9 @@
             pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByBinding=");
             pw.println(mIsAllowedBgActivityStartsByBinding);
         }
-        if (mIsAllowedBgActivityStartsByStart) {
+        if (mBackgroundStartPrivilegesByStartMerged.allowsAny()) {
             pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByStart=");
-            pw.println(mIsAllowedBgActivityStartsByStart);
+            pw.println(mBackgroundStartPrivilegesByStartMerged);
         }
         pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
                 pw.println(mAllowWhileInUsePermissionInFgs);
@@ -822,27 +826,28 @@
             if (mAppForAllowingBgActivityStartsByStart != null) {
                 if (mAppForAllowingBgActivityStartsByStart != proc) {
                     mAppForAllowingBgActivityStartsByStart
-                            .removeAllowBackgroundActivityStartsToken(this);
+                            .removeBackgroundStartPrivileges(this);
                     ams.mHandler.removeCallbacks(mCleanUpAllowBgActivityStartsByStartCallback);
                 }
             }
             // Make sure the cleanup callback knows about the new process.
-            mAppForAllowingBgActivityStartsByStart = mIsAllowedBgActivityStartsByStart
+            mAppForAllowingBgActivityStartsByStart =
+                    mBackgroundStartPrivilegesByStartMerged.allowsAny()
                     ? proc : null;
-            if (mIsAllowedBgActivityStartsByStart
+            if (mBackgroundStartPrivilegesByStartMerged.allowsAny()
                     || mIsAllowedBgActivityStartsByBinding) {
-                proc.addOrUpdateAllowBackgroundActivityStartsToken(this,
-                        getExclusiveOriginatingToken());
+                proc.addOrUpdateBackgroundStartPrivileges(this,
+                        getBackgroundStartPrivilegesWithExclusiveToken());
             } else {
-                proc.removeAllowBackgroundActivityStartsToken(this);
+                proc.removeBackgroundStartPrivileges(this);
             }
         }
         if (app != null && app != proc) {
             // If the old app is allowed to start bg activities because of a service start, leave it
             // that way until the cleanup callback runs. Otherwise we can remove its bg activity
             // start ability immediately (it can't be bound now).
-            if (!mIsAllowedBgActivityStartsByStart) {
-                app.removeAllowBackgroundActivityStartsToken(this);
+            if (mBackgroundStartPrivilegesByStartMerged.allowsNothing()) {
+                app.removeBackgroundStartPrivileges(this);
             }
             app.mServices.updateBoundClientUids();
             app.mServices.updateHostingComonentTypeForBindingsLocked();
@@ -942,9 +947,11 @@
      * timeout. Note that the ability for starting background activities persists for the process
      * even if the service is subsequently stopped.
      */
-    void allowBgActivityStartsOnServiceStart(@Nullable IBinder originatingToken) {
-        mBgActivityStartsByStartOriginatingTokens.add(originatingToken);
-        setAllowedBgActivityStartsByStart(true);
+    void allowBgActivityStartsOnServiceStart(BackgroundStartPrivileges backgroundStartPrivileges) {
+        checkArgument(backgroundStartPrivileges.allowsAny());
+        mBackgroundStartPrivilegesByStart.add(backgroundStartPrivileges);
+        setAllowedBgActivityStartsByStart(
+                backgroundStartPrivileges.merge(mBackgroundStartPrivilegesByStartMerged));
         if (app != null) {
             mAppForAllowingBgActivityStartsByStart = app;
         }
@@ -953,26 +960,31 @@
         if (mCleanUpAllowBgActivityStartsByStartCallback == null) {
             mCleanUpAllowBgActivityStartsByStartCallback = () -> {
                 synchronized (ams) {
-                    mBgActivityStartsByStartOriginatingTokens.remove(0);
-                    if (!mBgActivityStartsByStartOriginatingTokens.isEmpty()) {
+                    mBackgroundStartPrivilegesByStart.remove(0);
+                    if (!mBackgroundStartPrivilegesByStart.isEmpty()) {
+                        // recalculate the merged token
+                        mBackgroundStartPrivilegesByStartMerged =
+                                BackgroundStartPrivileges.merge(mBackgroundStartPrivilegesByStart);
+
                         // There are other callbacks in the queue, let's just update the originating
                         // token
-                        if (mIsAllowedBgActivityStartsByStart) {
+                        if (mBackgroundStartPrivilegesByStartMerged.allowsAny()) {
                             // mAppForAllowingBgActivityStartsByStart can be null here for example
                             // if get 2 calls to allowBgActivityStartsOnServiceStart() without a
                             // process attached to this ServiceRecord, so we need to perform a null
                             // check here.
                             if (mAppForAllowingBgActivityStartsByStart != null) {
                                 mAppForAllowingBgActivityStartsByStart
-                                        .addOrUpdateAllowBackgroundActivityStartsToken(
-                                                this, getExclusiveOriginatingToken());
+                                        .addOrUpdateBackgroundStartPrivileges(this,
+                                                getBackgroundStartPrivilegesWithExclusiveToken());
                             }
                         } else {
                             Slog.wtf(TAG,
                                     "Service callback to revoke bg activity starts by service "
                                             + "start triggered but "
-                                            + "mIsAllowedBgActivityStartsByStart = false. This "
-                                            + "should never happen.");
+                                            + "mBackgroundStartPrivilegesByStartMerged = "
+                                            + mBackgroundStartPrivilegesByStartMerged
+                                            + ". This should never happen.");
                         }
                     } else {
                         // Last callback on the queue
@@ -980,12 +992,12 @@
                             // The process we allowed is still running the service. We remove
                             // the ability by start, but it may still be allowed via bound
                             // connections.
-                            setAllowedBgActivityStartsByStart(false);
+                            setAllowedBgActivityStartsByStart(BackgroundStartPrivileges.NONE);
                         } else if (mAppForAllowingBgActivityStartsByStart != null) {
                             // The process we allowed is not running the service. It therefore can't
                             // be bound so we can unconditionally remove the ability.
                             mAppForAllowingBgActivityStartsByStart
-                                    .removeAllowBackgroundActivityStartsToken(ServiceRecord.this);
+                                    .removeBackgroundStartPrivileges(ServiceRecord.this);
                         }
                         mAppForAllowingBgActivityStartsByStart = null;
                     }
@@ -999,8 +1011,8 @@
                 ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);
     }
 
-    private void setAllowedBgActivityStartsByStart(boolean newValue) {
-        mIsAllowedBgActivityStartsByStart = newValue;
+    private void setAllowedBgActivityStartsByStart(BackgroundStartPrivileges newValue) {
+        mBackgroundStartPrivilegesByStartMerged = newValue;
         updateParentProcessBgActivityStartsToken();
     }
 
@@ -1011,46 +1023,42 @@
      * {@code mIsAllowedBgActivityStartsByBinding}. If either is true, this ServiceRecord
      * should be contributing as a token in parent ProcessRecord.
      *
-     * @see com.android.server.am.ProcessRecord#addOrUpdateAllowBackgroundActivityStartsToken(
-     * Binder, IBinder)
-     * @see com.android.server.am.ProcessRecord#removeAllowBackgroundActivityStartsToken(Binder)
+     * @see com.android.server.am.ProcessRecord#addOrUpdateBackgroundStartPrivileges(Binder,
+     *         BackgroundStartPrivileges)
+     * @see com.android.server.am.ProcessRecord#removeBackgroundStartPrivileges(Binder)
      */
     private void updateParentProcessBgActivityStartsToken() {
         if (app == null) {
             return;
         }
-        if (mIsAllowedBgActivityStartsByStart || mIsAllowedBgActivityStartsByBinding) {
+        if (mBackgroundStartPrivilegesByStartMerged.allowsAny()
+                || mIsAllowedBgActivityStartsByBinding) {
             // if the token is already there it's safe to "re-add it" - we're dealing with
             // a set of Binder objects
-            app.addOrUpdateAllowBackgroundActivityStartsToken(this, getExclusiveOriginatingToken());
+            app.addOrUpdateBackgroundStartPrivileges(this,
+                    getBackgroundStartPrivilegesWithExclusiveToken());
         } else {
-            app.removeAllowBackgroundActivityStartsToken(this);
+            app.removeBackgroundStartPrivileges(this);
         }
     }
 
     /**
-     * Returns the originating token if that's the only reason background activity starts are
-     * allowed. In order for that to happen the service has to be allowed only due to starts, since
-     * bindings are not associated with originating tokens, and all the start tokens have to be the
-     * same and there can't be any null originating token in the queue.
+     * Returns {@link BackgroundStartPrivileges} that represents the privileges a specific
+     * originating token or a generic aggregate token.
      *
-     * Originating tokens are optional, so the caller could provide null when it allows bg activity
-     * starts.
+     * If all privileges are associated with the same token (i.e. the service is only allowed due
+     * to starts) the token will be retained, otherwise (e.g. the privileges were granted by
+     * bindings) the originating token will be empty.
      */
     @Nullable
-    private IBinder getExclusiveOriginatingToken() {
-        if (mIsAllowedBgActivityStartsByBinding
-                || mBgActivityStartsByStartOriginatingTokens.isEmpty()) {
-            return null;
+    private BackgroundStartPrivileges getBackgroundStartPrivilegesWithExclusiveToken() {
+        if (mIsAllowedBgActivityStartsByBinding) {
+            return BackgroundStartPrivileges.ALLOW_BAL;
         }
-        IBinder firstToken = mBgActivityStartsByStartOriginatingTokens.get(0);
-        for (int i = 1, n = mBgActivityStartsByStartOriginatingTokens.size(); i < n; i++) {
-            IBinder token = mBgActivityStartsByStartOriginatingTokens.get(i);
-            if (token != firstToken) {
-                return null;
-            }
+        if (mBackgroundStartPrivilegesByStart.isEmpty()) {
+            return BackgroundStartPrivileges.NONE;
         }
-        return firstToken;
+        return mBackgroundStartPrivilegesByStartMerged;
     }
 
     @GuardedBy("ams")
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 43d3111..a7883cb 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -33,6 +33,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.BackgroundStartPrivileges;
 import android.app.IApplicationThread;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -277,7 +278,7 @@
             String resolvedType, IBinder resultTo, String resultWho, int requestCode,
             int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason,
             boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart) {
+            BackgroundStartPrivileges backgroundStartPrivileges) {
 
         userId = checkTargetUser(userId, validateIncomingUser, realCallingPid, realCallingUid,
                 reason);
@@ -298,7 +299,7 @@
                 .setUserId(userId)
                 .setInTask(inTask)
                 .setOriginatingPendingIntent(originatingPendingIntent)
-                .setAllowBackgroundActivityStart(allowBackgroundActivityStart)
+                .setBackgroundStartPrivileges(backgroundStartPrivileges)
                 .execute();
     }
 
@@ -317,10 +318,11 @@
     final int startActivitiesInPackage(int uid, String callingPackage,
             @Nullable String callingFeatureId, Intent[] intents, String[] resolvedTypes,
             IBinder resultTo, SafeActivityOptions options, int userId, boolean validateIncomingUser,
-            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
+            PendingIntentRecord originatingPendingIntent,
+            BackgroundStartPrivileges backgroundStartPrivileges) {
         return startActivitiesInPackage(uid, 0 /* realCallingPid */, -1 /* realCallingUid */,
                 callingPackage, callingFeatureId, intents, resolvedTypes, resultTo, options, userId,
-                validateIncomingUser, originatingPendingIntent, allowBackgroundActivityStart);
+                validateIncomingUser, originatingPendingIntent, backgroundStartPrivileges);
     }
 
     /**
@@ -340,7 +342,7 @@
             String callingPackage, @Nullable String callingFeatureId, Intent[] intents,
             String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId,
             boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart) {
+            BackgroundStartPrivileges backgroundStartPrivileges) {
 
         final String reason = "startActivityInPackage";
 
@@ -350,14 +352,14 @@
         // TODO: Switch to user app stacks here.
         return startActivities(null, uid, realCallingPid, realCallingUid, callingPackage,
                 callingFeatureId, intents, resolvedTypes, resultTo, options, userId, reason,
-                originatingPendingIntent, allowBackgroundActivityStart);
+                originatingPendingIntent, backgroundStartPrivileges);
     }
 
     int startActivities(IApplicationThread caller, int callingUid, int incomingRealCallingPid,
             int incomingRealCallingUid, String callingPackage, @Nullable String callingFeatureId,
             Intent[] intents, String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options,
             int userId, String reason, PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart) {
+            BackgroundStartPrivileges backgroundStartPrivileges) {
         if (intents == null) {
             throw new NullPointerException("intents is null");
         }
@@ -465,7 +467,7 @@
                         // top one as otherwise an activity below might consume it.
                         .setAllowPendingRemoteAnimationRegistryLookup(top /* allowLookup*/)
                         .setOriginatingPendingIntent(originatingPendingIntent)
-                        .setAllowBackgroundActivityStart(allowBackgroundActivityStart);
+                        .setBackgroundStartPrivileges(backgroundStartPrivileges);
             }
             // Log if the activities to be started have different uids.
             if (startingUidPkgs.size() > 1) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 40432dc..d88f719 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -95,6 +95,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.BackgroundStartPrivileges;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
 import android.app.ProfilerInfo;
@@ -389,7 +390,8 @@
         WaitResult waitResult;
         int filterCallingUid;
         PendingIntentRecord originatingPendingIntent;
-        boolean allowBackgroundActivityStart;
+        BackgroundStartPrivileges backgroundStartPrivileges;
+
         /**
          * The error callback token passed in {@link android.window.WindowContainerTransaction}
          * for TaskFragment operation error handling via
@@ -449,7 +451,7 @@
             allowPendingRemoteAnimationRegistryLookup = true;
             filterCallingUid = UserHandle.USER_NULL;
             originatingPendingIntent = null;
-            allowBackgroundActivityStart = false;
+            backgroundStartPrivileges = BackgroundStartPrivileges.NONE;
             errorCallbackToken = null;
         }
 
@@ -492,7 +494,7 @@
                     = request.allowPendingRemoteAnimationRegistryLookup;
             filterCallingUid = request.filterCallingUid;
             originatingPendingIntent = request.originatingPendingIntent;
-            allowBackgroundActivityStart = request.allowBackgroundActivityStart;
+            backgroundStartPrivileges = request.backgroundStartPrivileges;
             errorCallbackToken = request.errorCallbackToken;
         }
 
@@ -1060,7 +1062,7 @@
                                 realCallingPid,
                                 callerApp,
                                 request.originatingPendingIntent,
-                                request.allowBackgroundActivityStart,
+                                request.backgroundStartPrivileges,
                                 intent,
                                 checkedOptions);
             } finally {
@@ -3182,8 +3184,9 @@
         return this;
     }
 
-    ActivityStarter setAllowBackgroundActivityStart(boolean allowBackgroundActivityStart) {
-        mRequest.allowBackgroundActivityStart = allowBackgroundActivityStart;
+    ActivityStarter setBackgroundStartPrivileges(
+            BackgroundStartPrivileges backgroundStartPrivileges) {
+        mRequest.backgroundStartPrivileges = backgroundStartPrivileges;
         return this;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index c51808a..bd4f1a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppProtoEnums;
+import android.app.BackgroundStartPrivileges;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
@@ -209,14 +210,14 @@
             String callingPackage, @Nullable String callingFeatureId, Intent[] intents,
             String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId,
             boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart);
+            BackgroundStartPrivileges backgroundStartPrivileges);
 
     public abstract int startActivityInPackage(int uid, int realCallingPid, int realCallingUid,
             String callingPackage, @Nullable String callingFeaturId, Intent intent,
             String resolvedType, IBinder resultTo, String resultWho, int requestCode,
             int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason,
             boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart);
+            BackgroundStartPrivileges backgroundStartPrivileges);
 
     /**
      * Callback to be called on certain activity start scenarios.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9a8ef19..070ad2d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -136,6 +136,7 @@
 import android.app.AnrController;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
 import android.app.Dialog;
 import android.app.IActivityClientController;
 import android.app.IActivityController;
@@ -1222,7 +1223,7 @@
         return getActivityStartController().startActivities(caller, -1, 0, -1, callingPackage,
                 callingFeatureId, intents, resolvedTypes, resultTo,
                 SafeActivityOptions.fromBundle(bOptions), userId, reason,
-                null /* originatingPendingIntent */, false /* allowBackgroundActivityStart */);
+                null /* originatingPendingIntent */, BackgroundStartPrivileges.NONE);
     }
 
     @Override
@@ -1527,7 +1528,7 @@
                         // To start the dream from background, we need to start it from a persistent
                         // system process. Here we set the real calling uid to the system server uid
                         .setRealCallingUid(Binder.getCallingUid())
-                        .setAllowBackgroundActivityStart(true)
+                        .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
                         .execute();
                 return true;
             } finally {
@@ -1677,7 +1678,7 @@
                     .setFilterCallingUid(isResolver ? 0 /* system */ : targetUid)
                     // The target may well be in the background, which would normally prevent it
                     // from starting an activity. Here we definitely want the start to succeed.
-                    .setAllowBackgroundActivityStart(true)
+                    .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
                     .execute();
         } catch (SecurityException e) {
             // XXX need to figure out how to propagate to original app.
@@ -1723,7 +1724,7 @@
                 .setProfilerInfo(profilerInfo)
                 .setActivityOptions(bOptions)
                 .setUserId(userId)
-                .setAllowBackgroundActivityStart(true)
+                .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
                 .execute();
     }
 
@@ -1750,7 +1751,7 @@
                     .setResolvedType(resolvedType)
                     .setActivityOptions(bOptions)
                     .setUserId(userId)
-                    .setAllowBackgroundActivityStart(true)
+                    .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
                     .execute();
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -2200,7 +2201,7 @@
                 -1,
                 callerApp,
                 null,
-                false,
+                BackgroundStartPrivileges.NONE,
                 null,
                 null)) {
             if (!isBackgroundActivityStartsEnabled()) {
@@ -5605,7 +5606,7 @@
                     intents, resolvedTypes, null /* resultTo */,
                     SafeActivityOptions.fromBundle(bOptions), userId,
                     false /* validateIncomingUser */, null /* originatingPendingIntent */,
-                    false /* allowBackgroundActivityStart */);
+                    BackgroundStartPrivileges.NONE);
         }
 
         @Override
@@ -5613,12 +5614,12 @@
                 String callingPackage, @Nullable String callingFeatureId, Intent[] intents,
                 String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId,
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
-                boolean allowBackgroundActivityStart) {
+                BackgroundStartPrivileges backgroundStartPrivileges) {
             assertPackageMatchesCallingUid(callingPackage);
             return getActivityStartController().startActivitiesInPackage(uid, realCallingPid,
                     realCallingUid, callingPackage, callingFeatureId, intents, resolvedTypes,
                     resultTo, options, userId, validateIncomingUser, originatingPendingIntent,
-                    allowBackgroundActivityStart);
+                    backgroundStartPrivileges);
         }
 
         @Override
@@ -5627,13 +5628,13 @@
                 String resolvedType, IBinder resultTo, String resultWho, int requestCode,
                 int startFlags, SafeActivityOptions options, int userId, Task inTask, String reason,
                 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent,
-                boolean allowBackgroundActivityStart) {
+                BackgroundStartPrivileges backgroundStartPrivileges) {
             assertPackageMatchesCallingUid(callingPackage);
             return getActivityStartController().startActivityInPackage(uid, realCallingPid,
                     realCallingUid, callingPackage, callingFeatureId, intent, resolvedType,
                     resultTo, resultWho, requestCode, startFlags, options, userId, inTask,
                     reason, validateIncomingUser, originatingPendingIntent,
-                    allowBackgroundActivityStart);
+                    backgroundStartPrivileges);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 919bab8..8149e1c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -90,6 +90,7 @@
 import android.app.ActivityOptions;
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal;
+import android.app.BackgroundStartPrivileges;
 import android.app.IActivityClientController;
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
@@ -2731,7 +2732,7 @@
                     callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
                     null, 0, 0, options, userId, task, "startActivityFromRecents",
                     false /* validateIncomingUser */, null /* originatingPendingIntent */,
-                    false /* allowBackgroundActivityStart */);
+                    BackgroundStartPrivileges.NONE);
         } finally {
             PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
             synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 7bd8c53..de38a20 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -20,6 +20,7 @@
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 
 import android.app.ActivityManager;
+import android.app.BackgroundStartPrivileges;
 import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.content.Intent;
@@ -131,7 +132,7 @@
                         -1,
                         callerApp,
                         null,
-                        false,
+                        BackgroundStartPrivileges.NONE,
                         null,
                         null)) {
                     if (!mService.isBackgroundActivityStartsEnabled()) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 2315795..7aa734b 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -31,6 +31,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.BackgroundStartPrivileges;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -135,12 +136,12 @@
             int realCallingPid,
             WindowProcessController callerApp,
             PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart,
+            BackgroundStartPrivileges backgroundStartPrivileges,
             Intent intent,
             ActivityOptions checkedOptions) {
         return checkBackgroundActivityStart(callingUid, callingPid, callingPackage,
                 realCallingUid, realCallingPid, callerApp, originatingPendingIntent,
-                allowBackgroundActivityStart, intent, checkedOptions) == BAL_BLOCK;
+                backgroundStartPrivileges, intent, checkedOptions) == BAL_BLOCK;
     }
 
     /**
@@ -156,7 +157,7 @@
             int realCallingPid,
             WindowProcessController callerApp,
             PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart,
+            BackgroundStartPrivileges backgroundStartPrivileges,
             Intent intent,
             ActivityOptions checkedOptions) {
         // don't abort for the most important UIDs
@@ -254,10 +255,12 @@
         }
 
         // Legacy behavior allows to use caller foreground state to bypass BAL restriction.
-        final boolean balAllowedByPiSender =
-                PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions);
-
-        if (balAllowedByPiSender && realCallingUid != callingUid) {
+        // The options here are the options passed by the sender and not those on the intent.
+        final BackgroundStartPrivileges balAllowedByPiSender =
+                PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
+                        checkedOptions, realCallingUid);
+        if (balAllowedByPiSender.allowsBackgroundActivityStarts()
+                && realCallingUid != callingUid) {
             final boolean useCallerPermission =
                     PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions);
             if (useCallerPermission
@@ -282,7 +285,8 @@
             }
             // if the realCallingUid is a persistent system process, abort if the IntentSender
             // wasn't allowed to start an activity
-            if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) {
+            if (isRealCallingUidPersistentSystemProcess
+                    && backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
                 return logStartAllowedAndReturnCode(/*background*/ false,
                         callingUid,
                         BAL_ALLOW_PENDING_INTENT,
@@ -338,7 +342,7 @@
         // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
         // caller if caller allows, so that we can make the decision based on its state.
         int callerAppUid = callingUid;
-        if (callerApp == null && balAllowedByPiSender) {
+        if (callerApp == null && balAllowedByPiSender.allowsBackgroundActivityStarts()) {
             callerApp = mService.getProcessController(realCallingPid, realCallingUid);
             callerAppUid = realCallingUid;
         }
@@ -399,8 +403,8 @@
                         + isRealCallingUidPersistentSystemProcess
                         + "; originatingPendingIntent: "
                         + originatingPendingIntent
-                        + "; allowBackgroundActivityStart: "
-                        + allowBackgroundActivityStart
+                        + "; backgroundStartPrivileges: "
+                        + backgroundStartPrivileges
                         + "; intent: "
                         + intent
                         + "; callerApp: "
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 020e9c58..ecc8aad 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -27,8 +28,11 @@
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.BackgroundStartPrivileges;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.SystemClock;
@@ -40,7 +44,9 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.function.IntPredicate;
 
 /**
@@ -63,7 +69,7 @@
      * (can be null) and are used to trace back the grant to the notification token mechanism.
      */
     @GuardedBy("this")
-    private @Nullable ArrayMap<Binder, IBinder> mBackgroundActivityStartTokens;
+    private @Nullable ArrayMap<Binder, BackgroundStartPrivileges> mBackgroundStartPrivileges;
 
     /** Set of UIDs of clients currently bound to this process. */
     @GuardedBy("this")
@@ -153,25 +159,49 @@
     private boolean isBackgroundStartAllowedByToken(int uid, String packageName,
             boolean isCheckingForFgsStart) {
         synchronized (this) {
-            if (mBackgroundActivityStartTokens == null
-                    || mBackgroundActivityStartTokens.isEmpty()) {
+            if (mBackgroundStartPrivileges == null
+                    || mBackgroundStartPrivileges.isEmpty()) {
+                // no tokens to allow anything
                 return false;
             }
             if (isCheckingForFgsStart) {
-                // BG-FGS-start only checks if there is a token.
-                return true;
+                // check if any token allows foreground service starts
+                for (int i = mBackgroundStartPrivileges.size(); i-- > 0; ) {
+                    if (mBackgroundStartPrivileges.valueAt(i).allowsBackgroundFgsStarts()) {
+                        return true;
+                    }
+                }
+                return false;
             }
-
             if (mBackgroundActivityStartCallback == null) {
-                // We have tokens but no callback to decide => allow.
-                return true;
+                // without a callback just check if any token allows background activity starts
+                for (int i = mBackgroundStartPrivileges.size(); i-- > 0; ) {
+                    if (mBackgroundStartPrivileges.valueAt(i)
+                            .allowsBackgroundActivityStarts()) {
+                        return true;
+                    }
+                }
+                return false;
             }
+            List<IBinder> binderTokens = getOriginatingTokensThatAllowBal();
             // The callback will decide.
             return mBackgroundActivityStartCallback.isActivityStartAllowed(
-                    mBackgroundActivityStartTokens.values(), uid, packageName);
+                    binderTokens, uid, packageName);
         }
     }
 
+    private List<IBinder> getOriginatingTokensThatAllowBal() {
+        List<IBinder> originatingTokens = new ArrayList<>();
+        for (int i = mBackgroundStartPrivileges.size(); i-- > 0; ) {
+            BackgroundStartPrivileges privilege =
+                    mBackgroundStartPrivileges.valueAt(i);
+            if (privilege.allowsBackgroundActivityStarts()) {
+                originatingTokens.add(privilege.getOriginatingToken());
+            }
+        }
+        return originatingTokens;
+    }
+
     private boolean isBoundByForegroundUid() {
         synchronized (this) {
             if (mBoundClientUids != null) {
@@ -204,29 +234,34 @@
 
     /**
      * Allows background activity starts using token {@code entity}. Optionally, you can provide
-     * {@code originatingToken} if you have one such originating token, this is useful for tracing
-     * back the grant in the case of the notification token.
+     * {@code originatingToken} in the {@link BackgroundStartPrivileges} if you have one such
+     * originating token, this is useful for tracing back the grant in the case of the notification
+     * token.
      *
      * If {@code entity} is already added, this method will update its {@code originatingToken}.
      */
-    void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
-            @Nullable IBinder originatingToken) {
+    void addOrUpdateAllowBackgroundStartPrivileges(
+            Binder entity, BackgroundStartPrivileges backgroundStartPrivileges) {
+        requireNonNull(entity, "entity");
+        requireNonNull(backgroundStartPrivileges, "backgroundStartPrivileges");
+        checkArgument(backgroundStartPrivileges.allowsAny());
         synchronized (this) {
-            if (mBackgroundActivityStartTokens == null) {
-                mBackgroundActivityStartTokens = new ArrayMap<>();
+            if (mBackgroundStartPrivileges == null) {
+                mBackgroundStartPrivileges = new ArrayMap<>();
             }
-            mBackgroundActivityStartTokens.put(entity, originatingToken);
+            mBackgroundStartPrivileges.put(entity, backgroundStartPrivileges);
         }
     }
 
     /**
      * Removes token {@code entity} that allowed background activity starts added via {@link
-     * #addOrUpdateAllowBackgroundActivityStartsToken(Binder, IBinder)}.
+     * #addOrUpdateAllowBackgroundStartPrivileges(Binder, BackgroundStartPrivileges)}.
      */
-    void removeAllowBackgroundActivityStartsToken(Binder entity) {
+    void removeAllowBackgroundStartPrivileges(Binder entity) {
+        requireNonNull(entity, "entity");
         synchronized (this) {
-            if (mBackgroundActivityStartTokens != null) {
-                mBackgroundActivityStartTokens.remove(entity);
+            if (mBackgroundStartPrivileges != null) {
+                mBackgroundStartPrivileges.remove(entity);
             }
         }
     }
@@ -240,27 +275,27 @@
             return false;
         }
         synchronized (this) {
-            if (mBackgroundActivityStartTokens == null
-                    || mBackgroundActivityStartTokens.isEmpty()) {
+            if (mBackgroundStartPrivileges == null
+                    || mBackgroundStartPrivileges.isEmpty()) {
                 return false;
             }
             return mBackgroundActivityStartCallback.canCloseSystemDialogs(
-                    mBackgroundActivityStartTokens.values(), uid);
+                    getOriginatingTokensThatAllowBal(), uid);
         }
     }
 
     void dump(PrintWriter pw, String prefix) {
         synchronized (this) {
-            if (mBackgroundActivityStartTokens != null
-                    && !mBackgroundActivityStartTokens.isEmpty()) {
+            if (mBackgroundStartPrivileges != null
+                    && !mBackgroundStartPrivileges.isEmpty()) {
                 pw.print(prefix);
                 pw.println("Background activity start tokens (token: originating token):");
-                for (int i = mBackgroundActivityStartTokens.size() - 1; i >= 0; i--) {
+                for (int i = mBackgroundStartPrivileges.size() - 1; i >= 0; i--) {
                     pw.print(prefix);
                     pw.print("  - ");
-                    pw.print(mBackgroundActivityStartTokens.keyAt(i));
+                    pw.print(mBackgroundStartPrivileges.keyAt(i));
                     pw.print(": ");
-                    pw.println(mBackgroundActivityStartTokens.valueAt(i));
+                    pw.println(mBackgroundStartPrivileges.valueAt(i));
                 }
             }
             if (mBoundClientUids != null && mBoundClientUids.size() > 0) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 3e279b2..8f6ac15 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -50,6 +50,7 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
+import android.app.BackgroundStartPrivileges;
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
 import android.app.servertransaction.ConfigurationChangeItem;
@@ -64,7 +65,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.FactoryTest;
-import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.Message;
 import android.os.Process;
@@ -547,17 +547,18 @@
     }
 
     /**
-     * @see BackgroundLaunchProcessController#addOrUpdateAllowBackgroundActivityStartsToken(Binder,
-     * IBinder)
+     * @see BackgroundLaunchProcessController#addOrUpdateAllowBackgroundStartPrivileges(Binder,
+     * BackgroundStartPrivileges)
      */
-    public void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
-            @Nullable IBinder originatingToken) {
-        mBgLaunchController.addOrUpdateAllowBackgroundActivityStartsToken(entity, originatingToken);
+    public void addOrUpdateBackgroundStartPrivileges(Binder entity,
+            BackgroundStartPrivileges backgroundStartPrivileges) {
+        mBgLaunchController.addOrUpdateAllowBackgroundStartPrivileges(entity,
+                backgroundStartPrivileges);
     }
 
-    /** @see BackgroundLaunchProcessController#removeAllowBackgroundActivityStartsToken(Binder) */
-    public void removeAllowBackgroundActivityStartsToken(Binder entity) {
-        mBgLaunchController.removeAllowBackgroundActivityStartsToken(entity);
+    /** @see BackgroundLaunchProcessController#removeAllowBackgroundStartPrivileges(Binder) */
+    public void removeBackgroundStartPrivileges(Binder entity) {
+        mBgLaunchController.removeAllowBackgroundStartPrivileges(entity);
     }
 
     /**
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 92570aa..e0f9be4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -50,6 +50,7 @@
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.appwidget.AppWidgetManager;
 import android.content.IIntentReceiver;
@@ -215,7 +216,7 @@
         return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null,
                 null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
                 Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
-                false, null, false, null);
+                BackgroundStartPrivileges.NONE, false, null);
     }
 
     private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
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 72d8556..5f4ff1a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -49,6 +49,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.ReceiverInfo;
@@ -635,8 +636,8 @@
         return new BroadcastRecord(mQueue, intent, callerApp, callerApp.info.packageName, null,
                 callerApp.getPid(), callerApp.info.uid, false, null, null, null, null,
                 AppOpsManager.OP_NONE, options, receivers, callerApp, resultTo,
-                Activity.RESULT_OK, null, resultExtras, ordered, false, false, userId, false, null,
-                false, null);
+                Activity.RESULT_OK, null, resultExtras, ordered, false, false, userId,
+                BackgroundStartPrivileges.NONE, false, null);
     }
 
     private static Map<String, Object> asMap(Bundle bundle) {
@@ -1707,20 +1708,21 @@
         final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
 
-        final Binder backgroundActivityStartsToken = new Binder();
+        final BackgroundStartPrivileges backgroundStartPrivileges =
+                BackgroundStartPrivileges.allowBackgroundActivityStarts(new Binder());
         final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord r = new BroadcastRecord(mQueue, intent, callerApp,
                 callerApp.info.packageName, null, callerApp.getPid(), callerApp.info.uid, false,
                 null, null, null, null, AppOpsManager.OP_NONE, BroadcastOptions.makeBasic(),
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), null, null,
-                Activity.RESULT_OK, null, null, false, false, false, UserHandle.USER_SYSTEM, true,
-                backgroundActivityStartsToken, false, null);
+                Activity.RESULT_OK, null, null, false, false, false, UserHandle.USER_SYSTEM,
+                backgroundStartPrivileges, false, null);
         enqueueBroadcast(r);
 
         waitForIdle();
-        verify(receiverApp).addOrUpdateAllowBackgroundActivityStartsToken(eq(r),
-                eq(backgroundActivityStartsToken));
-        verify(receiverApp).removeAllowBackgroundActivityStartsToken(eq(r));
+        verify(receiverApp).addOrUpdateBackgroundStartPrivileges(eq(r),
+                eq(backgroundStartPrivileges));
+        verify(receiverApp).removeBackgroundStartPrivileges(eq(r));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 3864c88..01e2768 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.doReturn;
 
 import android.app.ActivityManagerInternal;
+import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -695,8 +696,7 @@
                 false /* sticky */,
                 false /* initialSticky */,
                 userId,
-                false /* allowBackgroundActivityStarts */,
-                null /* activityStartsToken */,
+                BackgroundStartPrivileges.NONE,
                 false /* timeoutExempt */,
                 filterExtrasForReceiver);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 8f110a88..ca28558 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -81,6 +81,7 @@
 import static org.mockito.ArgumentMatchers.notNull;
 
 import android.app.ActivityOptions;
+import android.app.BackgroundStartPrivileges;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
@@ -886,7 +887,8 @@
         doReturn(callerIsRecents).when(recentTasks).isCallerRecents(callingUid);
         // caller is temp allowed
         if (callerIsTempAllowed) {
-            callerApp.addOrUpdateAllowBackgroundActivityStartsToken(new Binder(), null);
+            callerApp.addOrUpdateBackgroundStartPrivileges(new Binder(),
+                    BackgroundStartPrivileges.ALLOW_BAL);
         }
         // caller is instrumenting with background activity starts privileges
         callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges,