Merge "Restricting network for low-priority proc-states" into main
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 365f913..594ec18 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManager.procStateToString;
 import static android.content.pm.PackageManager.GET_SIGNATURES;
@@ -170,6 +171,8 @@
     public static final String FIREWALL_CHAIN_NAME_RESTRICTED = "restricted";
     /** @hide */
     public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby";
+    /** @hide */
+    public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background";
 
     private static final boolean ALLOW_PLATFORM_APP_POLICY = true;
 
@@ -180,6 +183,9 @@
     /** @hide */
     public static final int TOP_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_TOP;
 
+    /** @hide */
+    public static final int BACKGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+
     /**
      * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it
      * applies to.
@@ -264,6 +270,16 @@
      * @hide
      */
     public static final int ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST = 1 << 6;
+
+    /**
+     * Flag to indicate that the app is exempt from always-on background network restrictions.
+     * Note that this is explicitly different to the flag NOT_FOREGROUND which is used to grant
+     * shared exception to apps from power restrictions like doze, battery saver and app-standby.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_REASON_NOT_IN_BACKGROUND = 1 << 7;
+
     /**
      * Flag to indicate that app is exempt from certain metered network restrictions because user
      * explicitly exempted it.
@@ -822,6 +838,21 @@
     }
 
     /**
+     * This is currently only used as an implementation detail for
+     * {@link com.android.server.net.NetworkPolicyManagerService}.
+     * Only put here to be together with other isProcStateAllowed* methods.
+     *
+     * @hide
+     */
+    public static boolean isProcStateAllowedNetworkWhileBackground(@Nullable UidState uidState) {
+        if (uidState == null) {
+            return false;
+        }
+        return uidState.procState < BACKGROUND_THRESHOLD_STATE
+                || (uidState.capability & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0;
+    }
+
+    /**
      * Returns true if {@param procState} is considered foreground and as such will be allowed
      * to access network when the device is in data saver mode. Otherwise, false.
      * @hide
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 21cfd242..2dda76e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -190,6 +190,7 @@
         "com.android.sysprop.watchdog",
         "ImmutabilityAnnotation",
         "securebox",
+        "net_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp
new file mode 100644
index 0000000..71d8e6b
--- /dev/null
+++ b/services/core/java/com/android/server/net/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+    name: "net_flags",
+    package: "com.android.server.net",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "net_flags_lib",
+    aconfig_declarations: "net_flags",
+}
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 681d1a0..d25f529 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -17,6 +17,7 @@
 package com.android.server.net;
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -27,6 +28,7 @@
 import static android.net.INetd.FIREWALL_DENYLIST;
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -187,6 +189,13 @@
      */
     @GuardedBy("mRulesLock")
     private final SparseIntArray mUidFirewallLowPowerStandbyRules = new SparseIntArray();
+
+    /**
+     * Contains the per-UID firewall rules that are used when Background chain is enabled.
+     */
+    @GuardedBy("mRulesLock")
+    private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+
     /** Set of states for the child firewall chains. True if the chain is active. */
     @GuardedBy("mRulesLock")
     final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
@@ -449,13 +458,15 @@
             syncFirewallChainLocked(FIREWALL_CHAIN_POWERSAVE, "powersave ");
             syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted ");
             syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby ");
+            syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND);
 
             final int[] chains = {
                     FIREWALL_CHAIN_STANDBY,
                     FIREWALL_CHAIN_DOZABLE,
                     FIREWALL_CHAIN_POWERSAVE,
                     FIREWALL_CHAIN_RESTRICTED,
-                    FIREWALL_CHAIN_LOW_POWER_STANDBY
+                    FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                    FIREWALL_CHAIN_BACKGROUND,
             };
 
             for (int chain : chains) {
@@ -1206,6 +1217,8 @@
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_CHAIN_NAME_BACKGROUND;
             default:
                 throw new IllegalArgumentException("Bad child chain: " + chain);
         }
@@ -1223,6 +1236,8 @@
                 return FIREWALL_ALLOWLIST;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_ALLOWLIST;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_ALLOWLIST;
             default:
                 return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST;
         }
@@ -1343,6 +1358,8 @@
                 return mUidFirewallRestrictedRules;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return mUidFirewallLowPowerStandbyRules;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return mUidFirewallBackgroundRules;
             case FIREWALL_CHAIN_NONE:
                 return mUidFirewallRules;
             default:
@@ -1395,6 +1412,10 @@
             pw.println(getFirewallChainState(FIREWALL_CHAIN_LOW_POWER_STANDBY));
             dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY,
                     mUidFirewallLowPowerStandbyRules);
+
+            pw.print("UID firewall background chain enabled: ");
+            pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND));
+            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules);
         }
 
         pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
@@ -1494,6 +1515,11 @@
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of low power standby");
                 return true;
             }
+            if (getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)
+                    && mUidFirewallBackgroundRules.get(uid) != FIREWALL_RULE_ALLOW) {
+                if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background");
+                return true;
+            }
             if (mUidRejectOnMetered.get(uid)) {
                 if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data"
                         + " in the background");
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index d7188c7..8e2d778 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -16,6 +16,7 @@
 package com.android.server.net;
 
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -24,6 +25,7 @@
 import static android.net.INetd.FIREWALL_RULE_ALLOW;
 import static android.net.INetd.FIREWALL_RULE_DENY;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
@@ -389,6 +391,8 @@
                 return FIREWALL_CHAIN_NAME_RESTRICTED;
             case FIREWALL_CHAIN_LOW_POWER_STANDBY:
                 return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY;
+            case FIREWALL_CHAIN_BACKGROUND:
+                return FIREWALL_CHAIN_NAME_BACKGROUND;
             default:
                 return String.valueOf(chain);
         }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 52734a4..b47458b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -47,6 +47,7 @@
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
@@ -54,6 +55,7 @@
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -77,6 +79,7 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
@@ -96,6 +99,7 @@
 import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
 import static android.net.NetworkPolicyManager.allowedReasonsToString;
 import static android.net.NetworkPolicyManager.blockedReasonsToString;
+import static android.net.NetworkPolicyManager.isProcStateAllowedNetworkWhileBackground;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileInLowPowerStandby;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
@@ -201,12 +205,12 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
-import android.os.PowerWhitelistManager;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -242,6 +246,7 @@
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 import android.util.SparseSetArray;
+import android.util.TimeUtils;
 import android.util.Xml;
 
 import com.android.internal.R;
@@ -457,6 +462,12 @@
      */
     private static final int MSG_UIDS_BLOCKED_REASONS_CHANGED = 23;
 
+    /**
+     * Message to update background restriction rules for uids that should lose network access
+     * due to being in the background.
+     */
+    private static final int MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS = 24;
+
     private static final int UID_MSG_STATE_CHANGED = 100;
     private static final int UID_MSG_GONE = 101;
 
@@ -475,7 +486,7 @@
 
     private ConnectivityManager mConnManager;
     private PowerManagerInternal mPowerManagerInternal;
-    private PowerWhitelistManager mPowerWhitelistManager;
+    private PowerExemptionManager mPowerExemptionManager;
     @NonNull
     private final Dependencies mDeps;
 
@@ -490,6 +501,12 @@
     // Denotes the status of restrict background read from disk.
     private boolean mLoadedRestrictBackground;
 
+    /**
+     * Whether or not network for apps in proc-states greater than
+     * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked.
+     */
+    private boolean mBackgroundNetworkRestricted;
+
     // See main javadoc for instructions on how to use these locks.
     final Object mUidRulesFirstLock = new Object();
     final Object mNetworkPoliciesSecondLock = new Object();
@@ -514,6 +531,15 @@
 
     private volatile boolean mNetworkManagerReady;
 
+    /**
+     * Delay after which a uid going into a process state greater than or equal to
+     * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} will lose network access.
+     * The delay is meant to prevent churn due to quick process-state changes.
+     * Note that there is no delay while granting network access.
+     */
+    @VisibleForTesting
+    long mBackgroundRestrictionDelayMs = TimeUnit.SECONDS.toMillis(5);
+
     /** Defined network policies. */
     @GuardedBy("mNetworkPoliciesSecondLock")
     final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
@@ -545,6 +571,8 @@
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallPowerSaveRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
+    final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray();
+    @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallRestrictedModeRules = new SparseIntArray();
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallLowPowerStandbyModeRules = new SparseIntArray();
@@ -624,6 +652,14 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
 
+    /**
+     * Stores a map of uids to the time their transition to background is considered complete. They
+     * will lose network access after this time. This is used to prevent churn in rules due to quick
+     * process-state transitions.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseLongArray mBackgroundTransitioningUids = new SparseLongArray();
+
     /** Map from network ID to last observed meteredness state */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -823,7 +859,7 @@
         mContext = Objects.requireNonNull(context, "missing context");
         mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager");
         mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement");
-        mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
+        mPowerExemptionManager = mContext.getSystemService(PowerExemptionManager.class);
         mClock = Objects.requireNonNull(clock, "missing Clock");
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
@@ -859,15 +895,15 @@
 
     @GuardedBy("mUidRulesFirstLock")
     private void updatePowerSaveAllowlistUL() {
-        int[] whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ false);
+        int[] allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ false);
         mPowerSaveWhitelistExceptIdleAppIds.clear();
-        for (int uid : whitelist) {
+        for (int uid : allowlist) {
             mPowerSaveWhitelistExceptIdleAppIds.put(uid, true);
         }
 
-        whitelist = mPowerWhitelistManager.getWhitelistedAppIds(/* includingIdle */ true);
+        allowlist = mPowerExemptionManager.getAllowListedAppIds(/* includingIdle */ true);
         mPowerSaveWhitelistAppIds.clear();
-        for (int uid : whitelist) {
+        for (int uid : allowlist) {
             mPowerSaveWhitelistAppIds.put(uid, true);
         }
     }
@@ -1017,6 +1053,14 @@
                         writePolicyAL();
                     }
 
+                    // The flag is boot-stable.
+                    mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
+                    if (mBackgroundNetworkRestricted) {
+                        // Firewall rules and UidBlockedState will get updated in
+                        // updateRulesForGlobalChangeAL below.
+                        enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
+                    }
+
                     setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
                     updateRulesForGlobalChangeAL(false);
                     updateNotificationsNL();
@@ -1027,17 +1071,22 @@
                 final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
                         | ActivityManager.UID_OBSERVER_GONE
                         | ActivityManager.UID_OBSERVER_CAPABILITY;
+
+                final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN
+                        : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE;
+                // TODO (b/319728914): Filter out the unnecessary changes when using no cutpoint.
+
                 mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
-                        NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android");
+                        cutpoint, "android");
                 mNetworkManager.registerObserver(mAlertObserver);
             } catch (RemoteException e) {
                 // ignored; both services live in system_server
             }
 
             // listen for changes to power save allowlist
-            final IntentFilter whitelistFilter = new IntentFilter(
+            final IntentFilter allowlistFilter = new IntentFilter(
                     PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-            mContext.registerReceiver(mPowerSaveWhitelistReceiver, whitelistFilter, null, mHandler);
+            mContext.registerReceiver(mPowerSaveAllowlistReceiver, allowlistFilter, null, mHandler);
 
             // watch for network interfaces to be claimed
             final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
@@ -1188,12 +1237,15 @@
         }
     }
 
-    final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mPowerSaveAllowlistReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected
             synchronized (mUidRulesFirstLock) {
                 updatePowerSaveAllowlistUL();
+                if (mBackgroundNetworkRestricted) {
+                    updateRulesForBackgroundChainUL();
+                }
                 updateRulesForRestrictPowerUL();
                 updateRulesForAppIdleUL();
             }
@@ -3901,6 +3953,11 @@
                 }
 
                 fout.println();
+                fout.println("Flags:");
+                fout.println("Network blocked for TOP_SLEEPING and above: "
+                        + mBackgroundNetworkRestricted);
+
+                fout.println();
                 fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
                 fout.println("mRestrictBackgroundBeforeBsm: " + mRestrictBackgroundBeforeBsm);
                 fout.println("mLoadedRestrictBackground: " + mLoadedRestrictBackground);
@@ -4042,6 +4099,22 @@
                     fout.decreaseIndent();
                 }
 
+                size = mBackgroundTransitioningUids.size();
+                if (size > 0) {
+                    final long nowUptime = SystemClock.uptimeMillis();
+                    fout.println("Uids transitioning to background:");
+                    fout.increaseIndent();
+                    for (int i = 0; i < size; i++) {
+                        fout.print("UID=");
+                        fout.print(mBackgroundTransitioningUids.keyAt(i));
+                        fout.print(", ");
+                        TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i), nowUptime,
+                                fout);
+                        fout.println();
+                    }
+                    fout.decreaseIndent();
+                }
+
                 final SparseBooleanArray knownUids = new SparseBooleanArray();
                 collectKeys(mUidState, knownUids);
                 synchronized (mUidBlockedState) {
@@ -4163,6 +4236,12 @@
         return isProcStateAllowedWhileInLowPowerStandby(uidState);
     }
 
+    @GuardedBy("mUidRulesFirstLock")
+    private boolean isUidExemptFromBackgroundRestrictions(int uid) {
+        return mBackgroundTransitioningUids.indexOfKey(uid) >= 0
+                || isProcStateAllowedNetworkWhileBackground(mUidState.get(uid));
+    }
+
     /**
      * Process state of UID changed; if needed, will trigger
      * {@link #updateRulesForDataUsageRestrictionsUL(int)} and
@@ -4188,6 +4267,8 @@
                 // state changed, push updated rules
                 mUidState.put(uid, newUidState);
                 updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState);
+
+                boolean updatePowerRestrictionRules = false;
                 boolean allowedWhileIdleOrPowerSaveModeChanged =
                         isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState)
                                 != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState);
@@ -4199,19 +4280,44 @@
                     if (mRestrictPower) {
                         updateRuleForRestrictPowerUL(uid);
                     }
-                    updateRulesForPowerRestrictionsUL(uid, procState);
+                    updatePowerRestrictionRules = true;
+                }
+                if (mBackgroundNetworkRestricted) {
+                    final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
+                            oldUidState);
+                    final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
+                    if (!wasAllowed && isAllowed) {
+                        mBackgroundTransitioningUids.delete(uid);
+                        updateRuleForBackgroundUL(uid);
+                        updatePowerRestrictionRules = true;
+                    } else if (wasAllowed && !isAllowed) {
+                        final long completionTimeMs = SystemClock.uptimeMillis()
+                                + mBackgroundRestrictionDelayMs;
+                        if (mBackgroundTransitioningUids.indexOfKey(uid) < 0) {
+                            // This is just a defensive check in case the upstream code ever makes
+                            // multiple calls for the same process state change.
+                            mBackgroundTransitioningUids.put(uid, completionTimeMs);
+                        }
+                        if (!mHandler.hasMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS)) {
+                            // Many uids may be in this "transitioning" state at the same time, so
+                            // using one message at a time to avoid congestion in the MessageQueue.
+                            mHandler.sendEmptyMessageAtTime(
+                                    MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
+                        }
+                    }
                 }
                 if (mLowPowerStandbyActive) {
                     boolean allowedInLpsChanged =
                             isProcStateAllowedWhileInLowPowerStandby(oldUidState)
                                     != isProcStateAllowedWhileInLowPowerStandby(newUidState);
                     if (allowedInLpsChanged) {
-                        if (!allowedWhileIdleOrPowerSaveModeChanged) {
-                            updateRulesForPowerRestrictionsUL(uid, procState);
-                        }
                         updateRuleForLowPowerStandbyUL(uid);
+                        updatePowerRestrictionRules = true;
                     }
                 }
+                if (updatePowerRestrictionRules) {
+                    updateRulesForPowerRestrictionsUL(uid, procState);
+                }
                 return true;
             }
         } finally {
@@ -4234,6 +4340,12 @@
                 if (mRestrictPower) {
                     updateRuleForRestrictPowerUL(uid);
                 }
+                if (mBackgroundNetworkRestricted) {
+                    // Uid is no longer running, there is no point in any grace period of network
+                    // access during transitions to lower importance proc-states.
+                    mBackgroundTransitioningUids.delete(uid);
+                    updateRuleForBackgroundUL(uid);
+                }
                 updateRulesForPowerRestrictionsUL(uid);
                 if (mLowPowerStandbyActive) {
                     updateRuleForLowPowerStandbyUL(uid);
@@ -4441,11 +4553,41 @@
         }
     }
 
+    /**
+     * Updates the rules for apps allowlisted to use network while in the background.
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    private void updateRulesForBackgroundChainUL() {
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForBackgroundChainUL");
+        try {
+            final SparseIntArray uidRules = mUidFirewallBackgroundRules;
+            uidRules.clear();
+
+            final List<UserInfo> users = mUserManager.getUsers();
+            for (int ui = users.size() - 1; ui >= 0; ui--) {
+                final UserInfo user = users.get(ui);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
+                updateRulesForAllowlistedAppIds(uidRules, mPowerSaveWhitelistExceptIdleAppIds,
+                        user.id);
+            }
+            for (int i = mUidState.size() - 1; i >= 0; i--) {
+                if (mBackgroundTransitioningUids.indexOfKey(mUidState.keyAt(i)) >= 0
+                        || isProcStateAllowedNetworkWhileBackground(mUidState.valueAt(i))) {
+                    uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
+                }
+            }
+            setUidFirewallRulesUL(FIREWALL_CHAIN_BACKGROUND, uidRules);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_NETWORK);
+        }
+    }
+
     private void updateRulesForAllowlistedAppIds(final SparseIntArray uidRules,
-            final SparseBooleanArray whitelistedAppIds, int userId) {
-        for (int i = whitelistedAppIds.size() - 1; i >= 0; --i) {
-            if (whitelistedAppIds.valueAt(i)) {
-                final int appId = whitelistedAppIds.keyAt(i);
+            final SparseBooleanArray allowlistedAppIds, int userId) {
+        for (int i = allowlistedAppIds.size() - 1; i >= 0; --i) {
+            if (allowlistedAppIds.valueAt(i)) {
+                final int appId = allowlistedAppIds.keyAt(i);
                 final int uid = UserHandle.getUid(userId, appId);
                 uidRules.put(uid, FIREWALL_RULE_ALLOW);
             }
@@ -4504,12 +4646,12 @@
     @GuardedBy("mUidRulesFirstLock")
     private boolean isAllowlistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
         final int appId = UserHandle.getAppId(uid);
-        boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
+        boolean allowlisted = mPowerSaveTempWhitelistAppIds.get(appId)
                 || mPowerSaveWhitelistAppIds.get(appId);
         if (!deviceIdleMode) {
-            isWhitelisted = isWhitelisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
+            allowlisted = allowlisted || isAllowlistedFromPowerSaveExceptIdleUL(uid);
         }
-        return isWhitelisted;
+        return allowlisted;
     }
 
     /**
@@ -4598,6 +4740,38 @@
     }
 
     /**
+     * Update firewall rule for a single uid whenever there are any interesting changes in the uid.
+     * Currently, it is called when:
+     * - The uid is added to or removed from power allowlists
+     * - The uid undergoes a process-state change
+     * - A package belonging to this uid is added
+     * - The uid is evicted from memory
+     */
+    @GuardedBy("mUidRulesFirstLock")
+    void updateRuleForBackgroundUL(int uid) {
+        if (!isUidValidForAllowlistRulesUL(uid)) {
+            return;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForBackgroundUL: " + uid);
+        try {
+            // The uid should be absent from mUidState and mBackgroundTransitioningUids if it is
+            // not running when this method is called. Then, the firewall state will depend on the
+            // allowlist alone. This is the desired behavior.
+            if (isAllowlistedFromPowerSaveUL(uid, false)
+                    || isUidExemptFromBackgroundRestrictions(uid)) {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW);
+                if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL ALLOW " + uid);
+            } else {
+                setUidFirewallRuleUL(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT);
+                if (LOGD) Log.d(TAG, "updateRuleForBackgroundUL " + uid + " to DEFAULT");
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+        }
+    }
+
+    /**
      * Toggle the firewall standby chain and inform listeners if the uid rules have effectively
      * changed.
      */
@@ -4644,6 +4818,9 @@
                     "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
         }
         try {
+            if (mBackgroundNetworkRestricted) {
+                updateRulesForBackgroundChainUL();
+            }
             updateRulesForAppIdleUL();
             updateRulesForRestrictPowerUL();
             updateRulesForRestrictBackgroundUL();
@@ -4803,6 +4980,9 @@
             updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
             updateRuleForDeviceIdleUL(uid);
             updateRuleForRestrictPowerUL(uid);
+            if (mBackgroundNetworkRestricted) {
+                updateRuleForBackgroundUL(uid);
+            }
             // Update internal rules.
             updateRulesForPowerRestrictionsUL(uid);
         }
@@ -4940,6 +5120,8 @@
         mUidFirewallStandbyRules.delete(uid);
         mUidFirewallDozableRules.delete(uid);
         mUidFirewallPowerSaveRules.delete(uid);
+        mUidFirewallBackgroundRules.delete(uid);
+        mBackgroundTransitioningUids.delete(uid);
         mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
         mPowerSaveWhitelistAppIds.delete(uid);
         mPowerSaveTempWhitelistAppIds.delete(uid);
@@ -4973,6 +5155,9 @@
         updateRuleForDeviceIdleUL(uid);
         updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
         updateRuleForRestrictPowerUL(uid);
+        if (mBackgroundNetworkRestricted) {
+            updateRuleForBackgroundUL(uid);
+        }
 
         // If the uid has the necessary permissions, then it should be added to the restricted mode
         // firewall allowlist.
@@ -5157,7 +5342,6 @@
      * Similar to above but ignores idle state if app standby is currently disabled by parole.
      *
      * @param uid the uid of the app to update rules for
-     * @param oldUidRules the current rules for the uid, in order to determine if there's a change
      * @param isUidIdle whether uid is idle or not
      */
     @GuardedBy("mUidRulesFirstLock")
@@ -5203,6 +5387,7 @@
             newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
             newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
             newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
+            newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0;
 
             newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
             newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
@@ -5215,6 +5400,9 @@
                     & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
             newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
                     ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
+            newAllowedReasons |= (mBackgroundNetworkRestricted
+                    && isUidExemptFromBackgroundRestrictions(uid))
+                    ? ALLOWED_REASON_NOT_IN_BACKGROUND : 0;
 
             uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
                     & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
@@ -5236,7 +5424,7 @@
 
             oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
             newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
-            uidRules = oldEffectiveBlockedReasons == newEffectiveBlockedReasons
+            uidRules = (oldEffectiveBlockedReasons == newEffectiveBlockedReasons)
                     ? RULE_NONE
                     : uidBlockedState.deriveUidRules();
         }
@@ -5429,6 +5617,28 @@
                     mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
                     return true;
                 }
+                case MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS: {
+                    final long now = SystemClock.uptimeMillis();
+                    long nextCheckTime = Long.MAX_VALUE;
+                    synchronized (mUidRulesFirstLock) {
+                        for (int i = mBackgroundTransitioningUids.size() - 1; i >= 0; i--) {
+                            final long completionTimeMs = mBackgroundTransitioningUids.valueAt(i);
+                            if (completionTimeMs > now) {
+                                nextCheckTime = Math.min(nextCheckTime, completionTimeMs);
+                                continue;
+                            }
+                            final int uid = mBackgroundTransitioningUids.keyAt(i);
+                            mBackgroundTransitioningUids.removeAt(i);
+                            updateRuleForBackgroundUL(uid);
+                            updateRulesForPowerRestrictionsUL(uid, false);
+                        }
+                    }
+                    if (nextCheckTime < Long.MAX_VALUE) {
+                        mHandler.sendEmptyMessageAtTime(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS,
+                                nextCheckTime);
+                    }
+                    return true;
+                }
                 case MSG_POLICIES_CHANGED: {
                     final int uid = msg.arg1;
                     final int policy = msg.arg2;
@@ -5840,6 +6050,8 @@
                 mUidFirewallRestrictedModeRules.put(uid, rule);
             } else if (chain == FIREWALL_CHAIN_LOW_POWER_STANDBY) {
                 mUidFirewallLowPowerStandbyModeRules.put(uid, rule);
+            } else if (chain == FIREWALL_CHAIN_BACKGROUND) {
+                mUidFirewallBackgroundRules.put(uid, rule);
             }
 
             try {
@@ -5896,6 +6108,8 @@
                     FIREWALL_RULE_DEFAULT);
             mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid,
                     FIREWALL_RULE_DEFAULT);
+            mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid,
+                    FIREWALL_RULE_DEFAULT);
             mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false);
             mLogger.meteredAllowlistChanged(uid, false);
             mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false);
@@ -6420,10 +6634,12 @@
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_DOZE;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
             }
             if ((allowedReasons & ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_BATTERY_SAVER;
                 effectiveBlockedReasons &= ~BLOCKED_REASON_APP_STANDBY;
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
             }
             if ((allowedReasons & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_RESTRICTED_MODE;
@@ -6434,19 +6650,24 @@
             if ((allowedReasons & ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST) != 0) {
                 effectiveBlockedReasons &= ~BLOCKED_REASON_LOW_POWER_STANDBY;
             }
+            if ((allowedReasons & ALLOWED_REASON_NOT_IN_BACKGROUND) != 0) {
+                effectiveBlockedReasons &= ~BLOCKED_REASON_APP_BACKGROUND;
+            }
 
             return effectiveBlockedReasons;
         }
 
         static int getAllowedReasonsForProcState(int procState) {
-            if (procState > NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
-                return ALLOWED_REASON_NONE;
-            } else if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
+            if (procState <= NetworkPolicyManager.TOP_THRESHOLD_STATE) {
                 return ALLOWED_REASON_TOP | ALLOWED_REASON_FOREGROUND
-                        | ALLOWED_METERED_REASON_FOREGROUND;
-            } else {
-                return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND;
+                        | ALLOWED_METERED_REASON_FOREGROUND | ALLOWED_REASON_NOT_IN_BACKGROUND;
+            } else if (procState <= NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE) {
+                return ALLOWED_REASON_FOREGROUND | ALLOWED_METERED_REASON_FOREGROUND
+                        | ALLOWED_REASON_NOT_IN_BACKGROUND;
+            } else if (procState < NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE) {
+                return ALLOWED_REASON_NOT_IN_BACKGROUND;
             }
+            return ALLOWED_REASON_NONE;
         }
 
         @Override
@@ -6471,6 +6692,7 @@
                 BLOCKED_REASON_APP_STANDBY,
                 BLOCKED_REASON_RESTRICTED_MODE,
                 BLOCKED_REASON_LOW_POWER_STANDBY,
+                BLOCKED_REASON_APP_BACKGROUND,
                 BLOCKED_METERED_REASON_DATA_SAVER,
                 BLOCKED_METERED_REASON_USER_RESTRICTED,
                 BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -6484,6 +6706,7 @@
                 ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
                 ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
                 ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST,
+                ALLOWED_REASON_NOT_IN_BACKGROUND,
                 ALLOWED_METERED_REASON_USER_EXEMPTED,
                 ALLOWED_METERED_REASON_SYSTEM,
                 ALLOWED_METERED_REASON_FOREGROUND,
@@ -6503,6 +6726,8 @@
                     return "RESTRICTED_MODE";
                 case BLOCKED_REASON_LOW_POWER_STANDBY:
                     return "LOW_POWER_STANDBY";
+                case BLOCKED_REASON_APP_BACKGROUND:
+                    return "APP_BACKGROUND";
                 case BLOCKED_METERED_REASON_DATA_SAVER:
                     return "DATA_SAVER";
                 case BLOCKED_METERED_REASON_USER_RESTRICTED:
@@ -6533,6 +6758,8 @@
                     return "RESTRICTED_MODE_PERMISSIONS";
                 case ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST:
                     return "LOW_POWER_STANDBY_ALLOWLIST";
+                case ALLOWED_REASON_NOT_IN_BACKGROUND:
+                    return "NOT_IN_BACKGROUND";
                 case ALLOWED_METERED_REASON_USER_EXEMPTED:
                     return "METERED_USER_EXEMPTED";
                 case ALLOWED_METERED_REASON_SYSTEM:
@@ -6600,7 +6827,8 @@
             int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
                     | BLOCKED_REASON_DOZE
                     | BLOCKED_REASON_BATTERY_SAVER
-                    | BLOCKED_REASON_LOW_POWER_STANDBY;
+                    | BLOCKED_REASON_LOW_POWER_STANDBY
+                    | BLOCKED_REASON_APP_BACKGROUND;
             if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
                 uidRule |= RULE_REJECT_ALL;
             } else if ((blockedReasons & powerBlockedReasons) != 0) {
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
new file mode 100644
index 0000000..419665a
--- /dev/null
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.net"
+
+flag {
+    name: "network_blocked_for_top_sleeping_and_above"
+    namespace: "backstage_power"
+    description: "Block network access for apps in a low importance background state"
+    bug: "304347838"
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 27fd8a6..5cce1c2 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -68,6 +68,7 @@
         "coretests-aidl",
         "securebox",
         "flag-junit",
+        "net_flags_lib",
     ],
 
     libs: [
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 13dc120..d6d2b6d 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -327,12 +328,20 @@
         isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
         expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
 
+        // Background chain
+        final ArrayMap<Integer, Boolean> isRestrictedInBackground = new ArrayMap<>();
+        isRestrictedInBackground.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedInBackground.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedInBackground.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_BACKGROUND, isRestrictedInBackground);
+
         final int[] chains = {
                 FIREWALL_CHAIN_STANDBY,
                 FIREWALL_CHAIN_POWERSAVE,
                 FIREWALL_CHAIN_DOZABLE,
                 FIREWALL_CHAIN_RESTRICTED,
-                FIREWALL_CHAIN_LOW_POWER_STANDBY
+                FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                FIREWALL_CHAIN_BACKGROUND
         };
         final int[] states = {
                 INetd.FIREWALL_RULE_ALLOW,
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 2a76452..4451cae 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -26,12 +26,14 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
 import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
 import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -48,8 +50,13 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_FOREGROUND;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_NOT_IN_BACKGROUND;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
+import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_TOP;
+import static android.net.NetworkPolicyManager.BACKGROUND_THRESHOLD_STATE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
@@ -64,6 +71,7 @@
 import static android.net.NetworkTemplate.MATCH_CARRIER;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
 import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.os.PowerExemptionManager.REASON_OTHER;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED;
 import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT;
@@ -146,6 +154,8 @@
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.PersistableBundle;
+import android.os.PowerExemptionManager;
+import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.RemoteException;
@@ -153,6 +163,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -169,6 +182,7 @@
 import android.util.Range;
 import android.util.RecurrenceRule;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
@@ -243,6 +257,9 @@
 public class NetworkPolicyManagerServiceTest {
     private static final String TAG = "NetworkPolicyManagerServiceTest";
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final long TEST_START = 1194220800000L;
     private static final String TEST_IFACE = "test0";
     private static final String TEST_WIFI_NETWORK_KEY = "TestWifiNetworkKey";
@@ -285,6 +302,7 @@
     private @Mock TelephonyManager mTelephonyManager;
     private @Mock UserManager mUserManager;
     private @Mock NetworkStatsManager mStatsManager;
+    private @Mock PowerExemptionManager mPowerExemptionManager;
     private TestDependencies mDeps;
 
     private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor =
@@ -302,6 +320,7 @@
     private NetworkPolicyManagerService mService;
 
     private final ArraySet<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+    private BroadcastReceiver mPowerAllowlistReceiver;
 
     /**
      * In some of the tests while initializing NetworkPolicyManagerService,
@@ -446,6 +465,7 @@
     @Before
     public void callSystemReady() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean())).thenReturn(new int[0]);
 
         final Context context = InstrumentationRegistry.getContext();
 
@@ -482,6 +502,8 @@
                         return mUserManager;
                     case Context.NETWORK_STATS_SERVICE:
                         return mStatsManager;
+                    case Context.POWER_EXEMPTION_SERVICE:
+                        return mPowerExemptionManager;
                     default:
                         return super.getSystemService(name);
                 }
@@ -495,6 +517,9 @@
             @Override
             public Intent registerReceiver(BroadcastReceiver receiver,
                     IntentFilter filter, String broadcastPermission, Handler scheduler) {
+                if (filter.hasAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED)) {
+                    mPowerAllowlistReceiver = receiver;
+                }
                 mRegisteredReceivers.add(receiver);
                 return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
             }
@@ -2066,6 +2091,12 @@
         expectHasUseRestrictedNetworksPermission(UID_A, true);
         expectHasUseRestrictedNetworksPermission(UID_B, false);
 
+        // Set low enough proc-states to ensure these uids are allowed in the background chain.
+        // To maintain clean separation between separate firewall chains, the tests could
+        // check for the specific blockedReasons in the uidBlockedState.
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, 21);
+        callAndWaitOnUidStateChanged(UID_B, BACKGROUND_THRESHOLD_STATE - 1, 21);
+
         Map<Integer, Integer> firewallUidRules = new ArrayMap<>();
         doAnswer(arg -> {
             int[] uids = arg.getArgument(1);
@@ -2113,7 +2144,111 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainEnabled() throws Exception {
+        verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
+    }
+
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnProcStateChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        mService.mBackgroundRestrictionDelayMs = 500; // To avoid waiting too long in tests.
+
+        // The app will be blocked when there is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        int procStateSeq = 23;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE - 1, procStateSeq++);
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_ALLOW);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq++);
+
+        // The app should be blocked after a delay. Posting a message just after the delay and
+        // waiting for it to complete to ensure that the blocking code has executed.
+        waitForDelayedMessageOnHandler(mService.mBackgroundRestrictionDelayMs + 1);
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_DEFAULT);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnAllowlistChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        // The apps will be blocked when there is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+        assertTrue(mService.isUidNetworkingBlocked(UID_B, false));
+
+        final int procStateSeq = 29;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        when(mPowerExemptionManager.getAllowListedAppIds(anyBoolean()))
+                .thenReturn(new int[]{APP_ID_A, APP_ID_B});
+        final SparseIntArray firewallUidRules = new SparseIntArray();
+        doAnswer(arg -> {
+            final int[] uids = arg.getArgument(1);
+            final int[] rules = arg.getArgument(2);
+            assertTrue(uids.length == rules.length);
+
+            for (int i = 0; i < uids.length; ++i) {
+                firewallUidRules.put(uids[i], rules[i]);
+            }
+            return null;
+        }).when(mNetworkManager).setFirewallUidRules(eq(FIREWALL_CHAIN_BACKGROUND),
+                any(int[].class), any(int[].class));
+
+        mPowerAllowlistReceiver.onReceive(mServiceContext, null);
+
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_A, -1));
+        assertEquals(FIREWALL_RULE_ALLOW, firewallUidRules.get(UID_B, -1));
+
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+        assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
+    public void testBackgroundChainOnTempAllowlistChange() throws Exception {
+        // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
+        clearInvocations(mNetworkManager);
+
+        // The app will be blocked as is no prior proc-state.
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        final int procStateSeq = 19;
+        callAndWaitOnUidStateChanged(UID_A, BACKGROUND_THRESHOLD_STATE + 1, procStateSeq);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+
+        final NetworkPolicyManagerInternal internal = LocalServices.getService(
+                NetworkPolicyManagerInternal.class);
+
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, true, REASON_OTHER, "testing");
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_ALLOW);
+        assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
+
+        internal.onTempPowerSaveWhitelistChange(APP_ID_A, false, REASON_OTHER, "testing");
+
+        verify(mNetworkManager).setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, UID_A,
+                FIREWALL_RULE_DEFAULT);
+        assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
+    }
+
+    @Test
     public void testLowPowerStandbyAllowlist() throws Exception {
+        // Chain background is also enabled but these procstates are important enough to be exempt.
         callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
         callAndWaitOnUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
         callAndWaitOnUidStateChanged(UID_C, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
@@ -2200,7 +2335,21 @@
                 ALLOWED_REASON_TOP), BLOCKED_REASON_NONE);
         effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_LOW_POWER_STANDBY,
                 ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST), BLOCKED_REASON_NONE);
-        // TODO: test more combinations of blocked reasons.
+
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_NOT_IN_BACKGROUND), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+                        | BLOCKED_REASON_BATTERY_SAVER, ALLOWED_REASON_NOT_IN_BACKGROUND),
+                BLOCKED_REASON_BATTERY_SAVER);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND
+                        | BLOCKED_REASON_DOZE, ALLOWED_REASON_NOT_IN_BACKGROUND),
+                BLOCKED_REASON_DOZE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                        ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS), BLOCKED_REASON_APP_BACKGROUND);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_POWER_SAVE_ALLOWLIST), BLOCKED_REASON_NONE);
+        effectiveBlockedReasons.put(Pair.create(BLOCKED_REASON_APP_BACKGROUND,
+                ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST), BLOCKED_REASON_NONE);
 
         for (Map.Entry<Pair<Integer, Integer>, Integer> test : effectiveBlockedReasons.entrySet()) {
             final int expectedEffectiveBlockedReasons = test.getValue();
@@ -2529,7 +2678,6 @@
     private FutureIntent mRestrictBackgroundChanged;
 
     private void postMsgAndWaitForCompletion() throws InterruptedException {
-        final Handler handler = mService.getHandlerForTesting();
         final CountDownLatch latch = new CountDownLatch(1);
         mService.getHandlerForTesting().post(latch::countDown);
         if (!latch.await(5, TimeUnit.SECONDS)) {
@@ -2537,6 +2685,14 @@
         }
     }
 
+    private void waitForDelayedMessageOnHandler(long delayMs) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mService.getHandlerForTesting().postDelayed(latch::countDown, delayMs);
+        if (!latch.await(delayMs + 5_000, TimeUnit.MILLISECONDS)) {
+            fail("Timed out waiting for delayed msg to be handled");
+        }
+    }
+
     private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
             throws InterruptedException {
         mService.setSubscriptionPlans(subId, plans, 0, callingPackage);