Merge "[RESTRICT AUTOMERGE] Fix bypass BG-FGS and BAL via package manager APIs" into sc-v2-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index b2d441f..5812595 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -56,6 +56,7 @@
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -317,6 +318,8 @@
     private final SparseBooleanArray mPendingSendNextAlarmClockChangedForUser =
             new SparseBooleanArray();
     private boolean mNextAlarmClockMayChange;
+    ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic();
+    BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic();
 
     @GuardedBy("mLock")
     private final Runnable mAlarmClockUpdater = () -> mNextAlarmClockMayChange = true;
@@ -1611,6 +1614,11 @@
     @Override
     public void onStart() {
         mInjector.init();
+        mOptsWithFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
+        mOptsWithoutFgs.setPendingIntentBackgroundActivityLaunchAllowed(false);
+        mOptsTimeBroadcast.setPendingIntentBackgroundActivityLaunchAllowed(false);
+        mActivityOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
+        mBroadcastOptsRestrictBal.setPendingIntentBackgroundActivityLaunchAllowed(false);
         mMetricsHelper = new MetricsHelper(getContext(), mLock);
 
         mListenerDeathRecipient = new IBinder.DeathRecipient() {
@@ -4306,6 +4314,14 @@
         return alarm.creatorUid;
     }
 
+    private Bundle getAlarmOperationBundle(Alarm alarm) {
+        if (alarm.mIdleOptions != null) {
+            return alarm.mIdleOptions;
+        } else if (alarm.operation.isActivity()) {
+            return mActivityOptsRestrictBal.toBundle();
+        }
+        return mBroadcastOptsRestrictBal.toBundle();
+    }
 
     @VisibleForTesting
     class AlarmHandler extends Handler {
@@ -4344,7 +4360,11 @@
                     for (int i = 0; i < triggerList.size(); i++) {
                         Alarm alarm = triggerList.get(i);
                         try {
-                            alarm.operation.send();
+                            // Disallow AlarmManager to start random background activity.
+                            final Bundle bundle = getAlarmOperationBundle(alarm);
+                            alarm.operation.send(/* context */ null, /* code */0, /* intent */
+                                    null, /* onFinished */null, /* handler */
+                                    null, /* requiredPermission */ null, bundle);
                         } catch (PendingIntent.CanceledException e) {
                             if (alarm.repeatInterval > 0) {
                                 // This IntentSender is no longer valid, but this
@@ -4908,9 +4928,10 @@
                     mSendCount++;
 
                     try {
+                        final Bundle bundle = getAlarmOperationBundle(alarm);
                         alarm.operation.send(getContext(), 0,
                                 mBackgroundIntent.putExtra(Intent.EXTRA_ALARM_COUNT, alarm.count),
-                                mDeliveryTracker, mHandler, null, alarm.mIdleOptions);
+                                mDeliveryTracker, mHandler, null, bundle);
                     } catch (PendingIntent.CanceledException e) {
                         if (alarm.repeatInterval > 0) {
                             // This IntentSender is no longer valid, but this
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9d59225..a9772e0 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3666,6 +3666,9 @@
      * processes to reclaim memory; the system will take care of restarting
      * these processes in the future as needed.
      *
+     * <p class="note">Third party applications can only use this API to kill their own processes.
+     * </p>
+     *
      * @param packageName The name of the package whose processes are to
      * be killed.
      */
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e76f89c..3a81322 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -69,7 +69,7 @@
  * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
  * Context.startActivity(Intent, Bundle)} and related methods.
  */
-public class ActivityOptions {
+public class ActivityOptions extends ComponentOptions {
     private static final String TAG = "ActivityOptions";
 
     /**
@@ -1081,13 +1081,12 @@
     }
 
     private ActivityOptions() {
+        super();
     }
 
     /** @hide */
     public ActivityOptions(Bundle opts) {
-        // If the remote side sent us bad parcelables, they won't get the
-        // results they want, which is their loss.
-        opts.setDefusable(true);
+        super(opts);
 
         mPackageName = opts.getString(KEY_PACKAGE_NAME);
         try {
@@ -1832,8 +1831,9 @@
      * object; you must not modify it, but can supply it to the startActivity
      * methods that take an options Bundle.
      */
+    @Override
     public Bundle toBundle() {
-        Bundle b = new Bundle();
+        Bundle b = super.toBundle();
         if (mPackageName != null) {
             b.putString(KEY_PACKAGE_NAME, mPackageName);
         }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index bd7162c..38c94ec 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -34,7 +34,7 @@
  * {@hide}
  */
 @SystemApi
-public class BroadcastOptions {
+public class BroadcastOptions extends ComponentOptions {
     private long mTemporaryAppAllowlistDuration;
     private @TempAllowListType int mTemporaryAppAllowlistType;
     private @ReasonCode int mTemporaryAppAllowlistReasonCode;
@@ -108,12 +108,14 @@
     }
 
     private BroadcastOptions() {
+        super();
         resetTemporaryAppAllowlist();
     }
 
     /** @hide */
     @TestApi
     public BroadcastOptions(@NonNull Bundle opts) {
+        super(opts);
         // Match the logic in toBundle().
         if (opts.containsKey(KEY_TEMPORARY_APP_ALLOWLIST_DURATION)) {
             mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION);
@@ -191,6 +193,24 @@
     }
 
     /**
+     * Set PendingIntent activity is allowed to be started in the background if the caller
+     * can start background activities.
+     * @hide
+     */
+    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
+     */
+    public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+        return super.isPendingIntentBackgroundActivityLaunchAllowed();
+    }
+
+    /**
      * Return {@link #setTemporaryAppAllowlist}.
      * @hide
      */
@@ -308,8 +328,9 @@
      * object; you must not modify it, but can supply it to the sendBroadcast
      * methods that take an options Bundle.
      */
+    @Override
     public Bundle toBundle() {
-        Bundle b = new Bundle();
+        Bundle b = super.toBundle();
         if (isTemporaryAppAllowlistSet()) {
             b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration);
             b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType);
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
new file mode 100644
index 0000000..86efd62
--- /dev/null
+++ b/core/java/android/app/ComponentOptions.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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.os.Bundle;
+
+/**
+ * @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.
+     * @hide
+     */
+    public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED =
+            "android.pendingIntent.backgroundActivityAllowed";
+
+    private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
+
+    ComponentOptions() {
+    }
+
+    ComponentOptions(Bundle opts) {
+        // 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));
+    }
+
+    /**
+     * Set PendingIntent activity is allowed to be started in the background if the caller
+     * can start background activities.
+     *
+     * @hide
+     */
+    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.
+     *
+     * @hide
+     */
+    public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+        return mPendingIntentBalAllowed;
+    }
+
+    /**
+     * @hide
+     */
+    public Bundle toBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+        return bundle;
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dd541f2..7b3fb56 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2815,7 +2815,11 @@
         android:protectionLevel="normal" />
 
     <!-- Allows an application to call
-        {@link android.app.ActivityManager#killBackgroundProcesses}.
+         {@link android.app.ActivityManager#killBackgroundProcesses}.
+
+         <p class="note">Third party applications can only use this API to kill their own
+         processes.</p>
+
          <p>Protection level: normal
     -->
     <permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 336433b..5de1b3e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3618,8 +3618,20 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final int callingAppId = UserHandle.getAppId(callingUid);
 
-        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+        ProcessRecord proc;
+        synchronized (mPidsSelfLocked) {
+            proc = mPidsSelfLocked.get(callingPid);
+        }
+        final boolean hasKillAllPermission = PERMISSION_GRANTED == checkPermission(
+                android.Manifest.permission.FORCE_STOP_PACKAGES, callingPid, callingUid)
+                || UserHandle.isCore(callingUid)
+                || (proc != null && proc.info.isSystemApp());
+
+        userId = mUserController.handleIncomingUser(callingPid, callingUid,
                 userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
         final int[] userIds = mUserController.expandUserId(userId);
 
@@ -3634,7 +3646,7 @@
                                     targetUserId));
                 } catch (RemoteException e) {
                 }
-                if (appId == -1) {
+                if (appId == -1 || (!hasKillAllPermission && appId != callingAppId)) {
                     Slog.w(TAG, "Invalid packageName: " + packageName);
                     return;
                 }
@@ -3702,6 +3714,22 @@
             throw new SecurityException(msg);
         }
 
+        final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+
+        ProcessRecord proc;
+        synchronized (mPidsSelfLocked) {
+            proc = mPidsSelfLocked.get(callingPid);
+        }
+        if (callingUid >= FIRST_APPLICATION_UID
+                && (proc == null || !proc.info.isSystemApp())) {
+            final String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
+                    + callingPid + ", uid=" + callingUid + " is not allowed";
+            Slog.w(TAG, msg);
+            // Silently return to avoid existing apps from crashing.
+            return;
+        }
+
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 33f745e..7e8dcd8 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -310,6 +310,25 @@
                 requiredPermission, null, null, 0, 0, 0, options);
     }
 
+    /**
+     * Return true if the activity options allows PendingIntent to use caller's BAL permission.
+     */
+    public static boolean isPendingIntentBalAllowedByCaller(
+            @Nullable ActivityOptions activityOptions) {
+        if (activityOptions == null) {
+            return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
+        }
+        return isPendingIntentBalAllowedByCaller(activityOptions.toBundle());
+    }
+
+    private static boolean isPendingIntentBalAllowedByCaller(@Nullable Bundle options) {
+        if (options == null) {
+            return ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT;
+        }
+        return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+                ActivityOptions.PENDING_INTENT_BAL_ALLOWED_DEFAULT);
+    }
+
     public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken,
             IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo,
             String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) {
@@ -350,16 +369,11 @@
                 resolvedType = key.requestResolvedType;
             }
 
-            // Apply any launch flags from the ActivityOptions. This is used only by SystemUI
-            // to ensure that we can launch the pending intent with a consistent launch mode even
-            // if the provided PendingIntent is immutable (ie. to force an activity to launch into
-            // a new task, or to launch multiple instances if supported by the app)
+            // Apply any launch flags from the ActivityOptions. This is to ensure that the caller
+            // can specify a consistent launch mode even if the PendingIntent is immutable
             final ActivityOptions opts = ActivityOptions.fromBundle(options);
             if (opts != null) {
-                // TODO(b/254490217): Move this check into SafeActivityOptions
-                if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) {
-                    finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
-                }
+                finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
             }
 
             // Extract options before clearing calling identity
@@ -436,7 +450,8 @@
             // 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);
+                    && 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
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 9ed63b5..eda5a94 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -281,6 +281,7 @@
         public void deliverOnFlushComplete(int requestCode) throws PendingIntent.CanceledException {
             BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setDontSendToRestrictedApps(true);
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
 
             mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_FLUSH_COMPLETE, requestCode),
                     null, null, null, options.toBundle());
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 72b75f3..1a0fa60 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -98,6 +98,8 @@
 
     @VisibleForTesting
     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
+    @VisibleForTesting
+    static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000;
 
     private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
     private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
@@ -239,6 +241,7 @@
                                 }
                             }
                             boolean skipWarningLogged = false;
+                            boolean skipGroupWarningLogged = false;
                             boolean hasSAWPermission = false;
                             if (upgradeForBubbles && uid != UNKNOWN_UID) {
                                 hasSAWPermission = mAppOps.noteOpNoThrow(
@@ -289,6 +292,14 @@
                                 String tagName = parser.getName();
                                 // Channel groups
                                 if (TAG_GROUP.equals(tagName)) {
+                                    if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) {
+                                        if (!skipGroupWarningLogged) {
+                                            Slog.w(TAG, "Skipping further groups for " + r.pkg
+                                                    + "; app has too many");
+                                            skipGroupWarningLogged = true;
+                                        }
+                                        continue;
+                                    }
                                     String id = parser.getAttributeValue(null, ATT_ID);
                                     CharSequence groupName = parser.getAttributeValue(null,
                                             ATT_NAME);
@@ -798,6 +809,9 @@
             }
             if (fromTargetApp) {
                 group.setBlocked(false);
+                if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) {
+                    throw new IllegalStateException("Limit exceed; cannot create more groups");
+                }
             }
             final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
             if (oldGroup != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ac806e1..b640ef8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -26850,10 +26850,10 @@
             // will be null whereas dataOwnerPkg will contain information about the package
             // which was uninstalled while keeping its data.
             AndroidPackage dataOwnerPkg = mPackages.get(packageName);
+            PackageSetting dataOwnerPs = mSettings.getPackageLPr(packageName);
             if (dataOwnerPkg  == null) {
-                PackageSetting ps = mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    dataOwnerPkg = ps.pkg;
+                if (dataOwnerPs != null) {
+                    dataOwnerPkg = dataOwnerPs.getPkg();
                 }
             }
 
@@ -26877,12 +26877,34 @@
             if (dataOwnerPkg != null) {
                 if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                         dataOwnerPkg.isDebuggable())) {
+                    // Downgrade is not permitted; a lower version of the app will not be allowed
                     try {
                         checkDowngrade(dataOwnerPkg, pkgLite);
                     } catch (PackageManagerException e) {
                         Slog.w(TAG, "Downgrade detected: " + e.getMessage());
                         return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
                     }
+                } else if (dataOwnerPs.isSystem()) {
+                    // Downgrade is permitted, but system apps can't be downgraded below
+                    // the version preloaded onto the system image
+                    final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(
+                            dataOwnerPs);
+                    if (disabledPs != null) {
+                        dataOwnerPkg = disabledPs.getPkg();
+                    }
+                    if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) {
+                        // Only restrict non-debuggable builds and non-debuggable version of the app
+                        try {
+                            checkDowngrade(dataOwnerPkg, pkgLite);
+                        } catch (PackageManagerException e) {
+                            String errorMsg = "System app: " + packageName
+                                    + " cannot be downgraded to"
+                                    + " older than its preloaded version on the system image. "
+                                    + e.getMessage();
+                            Slog.w(TAG, errorMsg);
+                            return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
+                        }
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 436a325..6ba77cd 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -989,6 +989,10 @@
         abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
                 callingPackage);
 
+        // Merge the two options bundles, while realCallerOptions takes precedence.
+        ActivityOptions checkedOptions = options != null
+                ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
+
         boolean restrictedBgActivity = false;
         if (!abort) {
             try {
@@ -997,15 +1001,12 @@
                 restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
                         callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
                         request.originatingPendingIntent, request.allowBackgroundActivityStart,
-                        intent);
+                        intent, checkedOptions);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             }
         }
 
-        // Merge the two options bundles, while realCallerOptions takes precedence.
-        ActivityOptions checkedOptions = options != null
-                ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null;
         if (request.allowPendingRemoteAnimationRegistryLookup) {
             checkedOptions = mService.getActivityStartController()
                     .getPendingRemoteAnimationRegistry()
@@ -1247,7 +1248,7 @@
     boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
             final String callingPackage, int realCallingUid, int realCallingPid,
             WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart, Intent intent) {
+            boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) {
         // don't abort for the most important UIDs
         final int callingAppId = UserHandle.getAppId(callingUid);
         if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
@@ -1318,9 +1319,12 @@
                 ? isCallingUidPersistentSystemProcess
                 : (realCallingAppId == Process.SYSTEM_UID)
                         || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
-        if (realCallingUid != callingUid) {
-            // don't abort if the realCallingUid has a visible window
-            // TODO(b/171459802): We should check appSwitchAllowed also
+
+        // Legacy behavior allows to use caller foreground state to bypass BAL restriction.
+        final boolean balAllowedByPiSender =
+                PendingIntentRecord.isPendingIntentBalAllowedByCaller(checkedOptions);
+
+        if (balAllowedByPiSender && realCallingUid != callingUid) {
             if (realCallingUidHasAnyVisibleWindow) {
                 if (DEBUG_ACTIVITY_STARTS) {
                     Slog.d(TAG, "Activity start allowed: realCallingUid (" + realCallingUid
@@ -1393,9 +1397,9 @@
         // If we don't have callerApp at this point, no caller was provided to startActivity().
         // That's the case for PendingIntent-based starts, since the creator's process might not be
         // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
-        // caller, so that we can make the decision based on its state.
+        // caller if caller allows, so that we can make the decision based on its state.
         int callerAppUid = callingUid;
-        if (callerApp == null) {
+        if (callerApp == null && balAllowedByPiSender) {
             callerApp = mService.getProcessController(realCallingPid, realCallingUid);
             callerAppUid = realCallingUid;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f17c9f9..0965709 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2105,7 +2105,7 @@
         final ActivityStarter starter = getActivityStartController().obtainStarter(
                 null /* intent */, "moveTaskToFront");
         if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1,
-                -1, callerApp, null, false, null)) {
+                -1, callerApp, null, false, null, null)) {
             if (!isBackgroundActivityStartsEnabled()) {
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 3c9adfb..ccd525b 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -108,7 +108,7 @@
                 final ActivityStarter starter = mService.getActivityStartController().obtainStarter(
                         null /* intent */, "moveToFront");
                 if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid,
-                        callingPackage, -1, -1, callerApp, null, false, null)) {
+                        callingPackage, -1, -1, callerApp, null, false, null, null)) {
                     if (!mService.isBackgroundActivityStartsEnabled()) {
                         return;
                     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 32d9247..e2ac889 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -109,6 +109,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -552,13 +553,23 @@
 
 
     private PendingIntent getNewMockPendingIntent() {
-        return getNewMockPendingIntent(TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+        return getNewMockPendingIntent(false);
+    }
+
+    private PendingIntent getNewMockPendingIntent(boolean isActivity) {
+        return getNewMockPendingIntent(TEST_CALLING_UID, TEST_CALLING_PACKAGE, isActivity);
     }
 
     private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage) {
+        return getNewMockPendingIntent(creatorUid, creatorPackage, false);
+    }
+
+    private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage,
+            boolean isActivity) {
         final PendingIntent mockPi = mock(PendingIntent.class, Answers.RETURNS_DEEP_STUBS);
         when(mockPi.getCreatorUid()).thenReturn(creatorUid);
         when(mockPi.getCreatorPackage()).thenReturn(creatorPackage);
+        when(mockPi.isActivity()).thenReturn(isActivity);
         return mockPi;
     }
 
@@ -2801,21 +2812,53 @@
                 anyString()));
     }
 
-    @Test
-    public void idleOptionsSentOnExpiration() throws Exception {
+    private void optionsSentOnExpiration(boolean isActivity, Bundle idleOptions)
+            throws Exception {
         final long triggerTime = mNowElapsedTest + 5000;
-        final PendingIntent alarmPi = getNewMockPendingIntent();
-        final Bundle idleOptions = new Bundle();
-        idleOptions.putChar("TEST_CHAR_KEY", 'x');
-        idleOptions.putInt("TEST_INT_KEY", 53);
+        final PendingIntent alarmPi = getNewMockPendingIntent(isActivity);
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, 0, alarmPi, 0, 0, TEST_CALLING_UID,
                 idleOptions);
 
         mNowElapsedTest = mTestTimer.getElapsed();
         mTestTimer.expire();
 
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
-                any(), any(Handler.class), isNull(), eq(idleOptions));
+                any(), any(Handler.class), isNull(), bundleCaptor.capture());
+        if (idleOptions != null) {
+            assertEquals(idleOptions, bundleCaptor.getValue());
+        } else {
+            assertFalse("BAL flag needs to be false in alarm manager",
+                    bundleCaptor.getValue().getBoolean(
+                            ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+                            true));
+        }
+    }
+
+    @Test
+    public void activityIdleOptionsSentOnExpiration() throws Exception {
+        final Bundle idleOptions = new Bundle();
+        idleOptions.putChar("TEST_CHAR_KEY", 'x');
+        idleOptions.putInt("TEST_INT_KEY", 53);
+        optionsSentOnExpiration(true, idleOptions);
+    }
+
+    @Test
+    public void broadcastIdleOptionsSentOnExpiration() throws Exception {
+        final Bundle idleOptions = new Bundle();
+        idleOptions.putChar("TEST_CHAR_KEY", 'x');
+        idleOptions.putInt("TEST_INT_KEY", 53);
+        optionsSentOnExpiration(false, idleOptions);
+    }
+
+    @Test
+    public void emptyActivityOptionsSentOnExpiration() throws Exception {
+        optionsSentOnExpiration(true, null);
+    }
+
+    @Test
+    public void emptyBroadcastOptionsSentOnExpiration() throws Exception {
+        optionsSentOnExpiration(false, null);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 77612b9..ceacdb3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -42,6 +42,7 @@
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER;
 import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
+import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT;
 import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -3166,6 +3167,52 @@
     }
 
     @Test
+    public void testTooManyGroups() {
+        for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) {
+            NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i),
+                    String.valueOf(i));
+            mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+        }
+        try {
+            NotificationChannelGroup group = new NotificationChannelGroup(
+                    String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT),
+                    String.valueOf(NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT));
+            mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+            fail("Allowed to create too many notification channel groups");
+        } catch (IllegalStateException e) {
+            // great
+        }
+    }
+
+    @Test
+    public void testTooManyGroups_xml() throws Exception {
+        String extraGroup = "EXTRA";
+        String extraGroup1 = "EXTRA1";
+
+        // create first... many... directly so we don't need a big xml blob in this test
+        for (int i = 0; i < NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; i++) {
+            NotificationChannelGroup group = new NotificationChannelGroup(String.valueOf(i),
+                    String.valueOf(i));
+            mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+        }
+
+        final String xml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
+                + "<channelGroup id=\"" + extraGroup + "\" name=\"hi\"/>"
+                + "<channelGroup id=\"" + extraGroup1 + "\" name=\"hi2\"/>"
+                + "</package>"
+                + "</ranking>";
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+        assertNull(mHelper.getNotificationChannelGroup(extraGroup, PKG_O, UID_O));
+        assertNull(mHelper.getNotificationChannelGroup(extraGroup1, PKG_O, UID_O));
+    }
+
+    @Test
     public void testRestoreMultiUser() throws Exception {
         String pkg = "restore_pkg";
         String channelId = "channelId";