Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0da21564 authored by Felipe Leme's avatar Felipe Leme Committed by android-build-merger
Browse files

Merge "Refactored NetworkPolicyManagerService mUidRules." into nyc-dev

am: de66540f

* commit 'de66540f':
  Refactored NetworkPolicyManagerService mUidRules.

Change-Id: I89af2823e8d791a487917daafe16be6764c231b2
parents 110e2e87 de66540f
Loading
Loading
Loading
Loading
+46 −11
Original line number Original line Diff line number Diff line
@@ -28,10 +28,10 @@ import android.content.pm.Signature;
import android.os.RemoteException;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserHandle;
import android.text.format.Time;
import android.text.format.Time;
import android.util.DebugUtils;


import com.google.android.collect.Sets;
import com.google.android.collect.Sets;


import java.io.PrintWriter;
import java.util.HashSet;
import java.util.HashSet;


/**
/**
@@ -49,18 +49,39 @@ public class NetworkPolicyManager {
    /** Allow network use (metered or not) in the background in battery save mode. */
    /** Allow network use (metered or not) in the background in battery save mode. */
    public static final int POLICY_ALLOW_BACKGROUND_BATTERY_SAVE = 0x2;
    public static final int POLICY_ALLOW_BACKGROUND_BATTERY_SAVE = 0x2;


    /* RULE_* are not masks and they must be exclusive */
    /*
    public static final int RULE_UNKNOWN = -1;
     * Rules defining whether an uid has access to a network given its type (metered / non-metered).
    /** All network traffic should be allowed. */
     *
    public static final int RULE_ALLOW_ALL = 0;
     * These rules are bits and can be used in bitmask operations; in particular:
    /** Reject traffic on metered networks. */
     * - rule & RULE_MASK_METERED: returns the metered-networks status.
    public static final int RULE_REJECT_METERED = 1;
     * - rule & RULE_MASK_ALL: returns the all-networks status.
    /** Reject traffic on all networks. */
     *
    public static final int RULE_REJECT_ALL = 2;
     * The RULE_xxx_ALL rules applies to all networks (metered or non-metered), but on
     * metered networks, the RULE_xxx_METERED rules should be checked first. For example,
     * if the device is on Battery Saver Mode and Data Saver Mode simulatenously, and a uid
     * is whitelisted for the former but not the latter, its status would be
     * RULE_REJECT_METERED | RULE_ALLOW_ALL, meaning it could have access to non-metered
     * networks but not to metered networks.
     *
     * See network-policy-restrictions.md for more info.
     */
    /** No specific rule was set */
    public static final int RULE_NONE = 0;
    /** Allow traffic on metered networks. */
    /** Allow traffic on metered networks. */
    public static final int RULE_ALLOW_METERED = 3;
    public static final int RULE_ALLOW_METERED = 1 << 0;
    /** Temporarily allow traffic on metered networks because app is on foreground. */
    /** Temporarily allow traffic on metered networks because app is on foreground. */
    public static final int RULE_TEMPORARY_ALLOW_METERED = 4;
    public static final int RULE_TEMPORARY_ALLOW_METERED = 1 << 1;
    /** Reject traffic on metered networks. */
    public static final int RULE_REJECT_METERED = 1 << 2;
    /** Network traffic should be allowed on all networks (metered or non-metered), although
     * metered-network restrictions could still apply. */
    public static final int RULE_ALLOW_ALL = 1 << 5;
    /** Reject traffic on all networks. */
    public static final int RULE_REJECT_ALL = 1 << 6;
    /** Mask used to get the {@code RULE_xxx_METERED} rules */
    public static final int MASK_METERED_NETWORKS = 0b00001111;
    /** Mask used to get the {@code RULE_xxx_ALL} rules */
    public static final int MASK_ALL_NETWORKS     = 0b11110000;


    public static final int FIREWALL_RULE_DEFAULT = 0;
    public static final int FIREWALL_RULE_DEFAULT = 0;
    public static final int FIREWALL_RULE_ALLOW = 1;
    public static final int FIREWALL_RULE_ALLOW = 1;
@@ -341,4 +362,18 @@ public class NetworkPolicyManager {
        // nothing found above; we can apply policy to UID
        // nothing found above; we can apply policy to UID
        return true;
        return true;
    }
    }

    /*
     * @hide
     */
    public static String uidRulesToString(int uidRules) {
        final StringBuilder string = new StringBuilder().append(uidRules).append(" (");
        if (uidRules == RULE_NONE) {
            string.append("NONE");
        } else {
            string.append(DebugUtils.flagsToString(NetworkPolicyManager.class, "RULE_", uidRules));
        }
        string.append(")");
        return string.toString();
    }
}
}
+47 −0
Original line number Original line Diff line number Diff line
# Data Saver vs Battery Saver

The tables below show whether an app has network access while on background depending on the status of Data Saver mode, Battery Saver mode, and the app's whitelist on those restricted modes.

### How to read the tables

The 2 topmost rows define the Battery Saver mode and whether the app is whitelisted or not for it.
The 2  leftmost columns define the Data Saver mode and whether the app is whitelisted, not whitelisted, or blacklisted for it.
The cells define the network status when the app is on background.

More specifically:

* **DS ON**: Data Saver Mode is on
* **DS OFF**: Data Saver Mode is off
* **BS ON**: Battery Saver Mode is on
* **BS OFF**: Battery Saver Mode is off
* **WL**: app is whitelisted
* **!WL**: app is not whitelisted
* **BL**: app is blacklisted
* **ok**: network access granted while app on background (NetworkInfo's state/detailed state should be `CONNECTED` / `CONNECTED`)
* **blk**: network access blocked while app on background (NetworkInfo's state/detailed state should be `DISCONNECTED` / `BLOCKED`)


## On metered networks

|         |       | BS   | ON    | BS   | OFF   |
|:-------:|-------|------|-------|------|-------|
|         |       | *WL* | *!WL* | *WL* | *!WL* |
| **DS**  |  *WL* |  ok  | blk   |  ok  |  ok   |
| **ON**  | *!WL* | blk  | blk   | blk  | blk   |
|         |  *BL* | blk  | blk   | blk  | blk   |
| **DS**  |  *WL* | blk  |  ok   |  ok  |  ok   |
| **OFF** | *!WL* | blk  |  ok   |  ok  |  ok   |
|         |  *BL* | blk  | blk   | blk  | blk   |


## On non-metered networks

|         |       | BS   | ON    | BS   | OFF   |
|:-------:|-------|------|-------|------|-------|
|         |       | *WL* | *!WL* | *WL* | *!WL* |
| **DS**  |  *WL* |  ok  | blk   |  ok  |  ok   |
| **ON**  | *!WL* |  ok  | blk   |  ok  |  ok   |
|         |  *BL* |  ok  | blk   |  ok  |  ok   |
| **DS**  |  *WL* |  ok  | blk   |  ok  |  ok   |
| **OFF** | *!WL* |  ok  | blk   |  ok  |  ok   |
|         |  *BL* |  ok  | blk   |  ok  |  ok   |
+107 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2016 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.net;

import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
import static android.net.NetworkPolicyManager.uidRulesToString;

import junit.framework.TestCase;

public class NetworkPolicyManagerTest extends TestCase {

    public void testUidRulesToString() {
        uidRulesToStringTest(RULE_NONE, "0 (NONE)");
        uidRulesToStringTest(RULE_ALLOW_METERED, "1 (ALLOW_METERED)");
        uidRulesToStringTest(RULE_TEMPORARY_ALLOW_METERED, "2 (TEMPORARY_ALLOW_METERED)");
        uidRulesToStringTest(RULE_REJECT_METERED, "4 (REJECT_METERED)");
        uidRulesToStringTest(RULE_ALLOW_ALL, "32 (ALLOW_ALL)");
        uidRulesToStringTest(RULE_REJECT_ALL, "64 (REJECT_ALL)");

        uidRulesToStringTest(RULE_ALLOW_METERED | RULE_ALLOW_ALL,
                "33 (ALLOW_METERED|ALLOW_ALL)");
        uidRulesToStringTest(RULE_ALLOW_METERED | RULE_REJECT_ALL,
                "65 (ALLOW_METERED|REJECT_ALL)");
        uidRulesToStringTest(RULE_TEMPORARY_ALLOW_METERED | RULE_ALLOW_ALL,
                "34 (TEMPORARY_ALLOW_METERED|ALLOW_ALL)");
        uidRulesToStringTest(RULE_TEMPORARY_ALLOW_METERED | RULE_REJECT_ALL,
                "66 (TEMPORARY_ALLOW_METERED|REJECT_ALL)");
        uidRulesToStringTest(RULE_REJECT_METERED | RULE_ALLOW_ALL,
                "36 (REJECT_METERED|ALLOW_ALL)");
        uidRulesToStringTest(RULE_REJECT_METERED | RULE_REJECT_ALL,
                "68 (REJECT_METERED|REJECT_ALL)");
    }

    private void uidRulesToStringTest(int uidRules, String expected) {
        final String actual = uidRulesToString(uidRules);
        assertEquals("Wrong string for uidRules " + uidRules, expected, actual);
    }

    public void testMeteredNetworksMask() {
        assertEquals(RULE_NONE, MASK_METERED_NETWORKS
                & RULE_NONE);

        assertEquals(RULE_ALLOW_METERED, MASK_METERED_NETWORKS
                & RULE_ALLOW_METERED);
        assertEquals(RULE_ALLOW_METERED, MASK_METERED_NETWORKS
                & (RULE_ALLOW_METERED | RULE_ALLOW_ALL));
        assertEquals(RULE_ALLOW_METERED, MASK_METERED_NETWORKS
                & (RULE_ALLOW_METERED | RULE_REJECT_ALL));

        assertEquals(RULE_TEMPORARY_ALLOW_METERED, MASK_METERED_NETWORKS
                & RULE_TEMPORARY_ALLOW_METERED);
        assertEquals(RULE_TEMPORARY_ALLOW_METERED, MASK_METERED_NETWORKS
                & (RULE_TEMPORARY_ALLOW_METERED | RULE_ALLOW_ALL));
        assertEquals(RULE_TEMPORARY_ALLOW_METERED, MASK_METERED_NETWORKS
                & (RULE_TEMPORARY_ALLOW_METERED | RULE_REJECT_ALL));

        assertEquals(RULE_REJECT_METERED, MASK_METERED_NETWORKS
                & RULE_REJECT_METERED);
        assertEquals(RULE_REJECT_METERED, MASK_METERED_NETWORKS
                & (RULE_REJECT_METERED | RULE_ALLOW_ALL));
        assertEquals(RULE_REJECT_METERED, MASK_METERED_NETWORKS
                & (RULE_REJECT_METERED | RULE_REJECT_ALL));
    }

    public void testAllNetworksMask() {
        assertEquals(RULE_NONE, MASK_ALL_NETWORKS
                & RULE_NONE);

        assertEquals(RULE_ALLOW_ALL, MASK_ALL_NETWORKS
                & RULE_ALLOW_ALL);
        assertEquals(RULE_ALLOW_ALL, MASK_ALL_NETWORKS
                & (RULE_ALLOW_ALL | RULE_ALLOW_METERED));
        assertEquals(RULE_ALLOW_ALL, MASK_ALL_NETWORKS
                & (RULE_ALLOW_ALL | RULE_TEMPORARY_ALLOW_METERED));
        assertEquals(RULE_ALLOW_ALL, MASK_ALL_NETWORKS
                & (RULE_ALLOW_ALL | RULE_REJECT_METERED));

        assertEquals(RULE_REJECT_ALL, MASK_ALL_NETWORKS
                & RULE_REJECT_ALL);
        assertEquals(RULE_REJECT_ALL, MASK_ALL_NETWORKS
                & (RULE_REJECT_ALL | RULE_ALLOW_METERED));
        assertEquals(RULE_REJECT_ALL, MASK_ALL_NETWORKS
                & (RULE_REJECT_ALL | RULE_TEMPORARY_ALLOW_METERED));
        assertEquals(RULE_REJECT_ALL, MASK_ALL_NETWORKS
                & (RULE_REJECT_ALL | RULE_REJECT_METERED));
    }
}
+19 −3
Original line number Original line Diff line number Diff line
@@ -30,10 +30,11 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_UNKNOWN;
import static android.net.NetworkPolicyManager.uidRulesToString;


import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.BroadcastOptions;
import android.app.BroadcastOptions;
@@ -915,7 +916,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
        final String iface = (lp == null ? "" : lp.getInterfaceName());
        final String iface = (lp == null ? "" : lp.getInterfaceName());
        synchronized (mRulesLock) {
        synchronized (mRulesLock) {
            networkMetered = mMeteredIfaces.contains(iface);
            networkMetered = mMeteredIfaces.contains(iface);
            uidRules = mUidRules.get(uid, RULE_UNKNOWN);
            uidRules = mUidRules.get(uid, RULE_NONE);
        }
        }


        switch (uidRules) {
        switch (uidRules) {
@@ -927,7 +928,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
                return networkMetered;
                return networkMetered;
            case RULE_REJECT_ALL:
            case RULE_REJECT_ALL:
                return true;
                return true;
            case RULE_UNKNOWN:
            case RULE_NONE:
            default:
            default:
                // When background data is restricted device-wide, the default
                // When background data is restricted device-wide, the default
                // behavior for apps should be like RULE_REJECT_METERED
                // behavior for apps should be like RULE_REJECT_METERED
@@ -1861,6 +1862,21 @@ public class ConnectivityService extends IConnectivityManager.Stub
        pw.println(mRestrictBackground);
        pw.println(mRestrictBackground);
        pw.println();
        pw.println();


        pw.println("Status for known UIDs:");
        pw.increaseIndent();
        final int size = mUidRules.size();
        for (int i = 0; i < size; i++) {
            final int uid = mUidRules.keyAt(i);
            pw.print("UID=");
            pw.print(uid);
            final int uidRules = mUidRules.get(uid, RULE_NONE);
            pw.print(" rules=");
            pw.print(uidRulesToString(uidRules));
            pw.println();
        }
        pw.println();
        pw.decreaseIndent();

        pw.println("Network Requests:");
        pw.println("Network Requests:");
        pw.increaseIndent();
        pw.increaseIndent();
        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+74 −47
Original line number Original line Diff line number Diff line
@@ -49,10 +49,13 @@ import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED;
import static android.net.NetworkPolicyManager.RULE_UNKNOWN;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
import static android.net.NetworkPolicyManager.uidRulesToString;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -2048,13 +2051,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    private boolean removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean uidDeleted,
    private boolean removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean uidDeleted,
            boolean updateNow) {
            boolean updateNow) {
        final boolean oldStatus = mRestrictBackgroundWhitelistUids.get(uid);
        final boolean oldStatus = mRestrictBackgroundWhitelistUids.get(uid);
        if (!oldStatus) {
        if (!oldStatus && !uidDeleted) {
            if (LOGD) Slog.d(TAG, "uid " + uid + " was not whitelisted before");
            if (LOGD) Slog.d(TAG, "uid " + uid + " was not whitelisted before");
            return false;
            return false;
        }
        }
        final boolean needFirewallRules = uidDeleted || isUidValidForWhitelistRules(uid);
        final boolean needFirewallRules = uidDeleted || isUidValidForWhitelistRules(uid);
        if (oldStatus) {
            Slog.i(TAG, "removing uid " + uid + " from restrict background whitelist");
            Slog.i(TAG, "removing uid " + uid + " from restrict background whitelist");
            mRestrictBackgroundWhitelistUids.delete(uid);
            mRestrictBackgroundWhitelistUids.delete(uid);
        }
        if (mDefaultRestrictBackgroundWhitelistUids.get(uid)
        if (mDefaultRestrictBackgroundWhitelistUids.get(uid)
                && !mRestrictBackgroundWhitelistRevokedUids.get(uid)) {
                && !mRestrictBackgroundWhitelistRevokedUids.get(uid)) {
            if (LOGD) Slog.d(TAG, "Adding uid " + uid
            if (LOGD) Slog.d(TAG, "Adding uid " + uid
@@ -2352,7 +2357,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            collectKeys(mUidState, knownUids);
            collectKeys(mUidState, knownUids);
            collectKeys(mUidRules, knownUids);
            collectKeys(mUidRules, knownUids);


            fout.println("Status for known UIDs:");
            fout.println("Status for all known UIDs:");
            fout.increaseIndent();
            fout.increaseIndent();
            size = knownUids.size();
            size = knownUids.size();
            for (int i = 0; i < size; i++) {
            for (int i = 0; i < size; i++) {
@@ -2370,18 +2375,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                            ? " (fg svc)" : " (bg)");
                            ? " (fg svc)" : " (bg)");
                }
                }


                final int rule = mUidRules.get(uid, RULE_UNKNOWN);
                final int uidRules = mUidRules.get(uid, RULE_NONE);
                fout.print(" rule=");
                fout.print(" rules=");
                fout.print(ruleToString(rule));
                fout.print(uidRulesToString(uidRules));

                fout.println();
                fout.println();
            }
            }
            fout.decreaseIndent();
            fout.decreaseIndent();

            fout.println("Status for just UIDs with rules:");
            fout.increaseIndent();
            size = mUidRules.size();
            for (int i = 0; i < size; i++) {
                final int uid = mUidRules.keyAt(i);
                fout.print("UID=");
                fout.print(uid);
                final int uidRules = mUidRules.get(uid, RULE_NONE);
                fout.print(" rules=");
                fout.print(uidRulesToString(uidRules));
                fout.println();
            }
            }
            fout.decreaseIndent();
        }
        }

    private String ruleToString(int rule) {
        return DebugUtils.valueToString(NetworkPolicyManager.class, "RULE_", rule);
    }
    }


    @Override
    @Override
@@ -2564,12 +2578,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        enableFirewallChainLocked(chain, enabled);
        enableFirewallChainLocked(chain, enabled);
    }
    }


    private boolean isWhitelistedBatterySaverLocked(int uid) {
        final int appId = UserHandle.getAppId(uid);
        return mPowerSaveTempWhitelistAppIds.get(appId) || mPowerSaveWhitelistAppIds.get(appId);
    }

    // NOTE: since both fw_dozable and fw_powersave uses the same map
    // NOTE: since both fw_dozable and fw_powersave uses the same map
    // (mPowerSaveTempWhitelistAppIds) for whitelisting, we can reuse their logic in this method.
    // (mPowerSaveTempWhitelistAppIds) for whitelisting, we can reuse their logic in this method.
    private void updateRulesForWhitelistedPowerSaveLocked(int uid, boolean enabled, int chain) {
    private void updateRulesForWhitelistedPowerSaveLocked(int uid, boolean enabled, int chain) {
        if (enabled) {
        if (enabled) {
            int appId = UserHandle.getAppId(uid);
            if (isWhitelistedBatterySaverLocked(uid)
            if (mPowerSaveTempWhitelistAppIds.get(appId) || mPowerSaveWhitelistAppIds.get(appId)
                    || isProcStateAllowedWhileIdleOrPowerSaveMode(mUidState.get(uid))) {
                    || isProcStateAllowedWhileIdleOrPowerSaveMode(mUidState.get(uid))) {
                setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
                setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
            } else {
            } else {
@@ -2732,7 +2750,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     * <p>There are currently 2 types of restriction rules:
     * <p>There are currently 2 types of restriction rules:
     * <ul>
     * <ul>
     * <li>Battery Saver Mode (also referred as power save).
     * <li>Battery Saver Mode (also referred as power save).
     * <li>Data Saver Mode (formerly known as restrict background data).
     * <li>Data Saver Mode (The Feature Formerly Known As 'Restrict Background Data').
     * </ul>
     * </ul>
     */
     */
    private void updateRestrictionRulesForUidLocked(int uid) {
    private void updateRestrictionRulesForUidLocked(int uid) {
@@ -2793,42 +2811,47 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
        }


        final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
        final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
        final int oldUidRules = mUidRules.get(uid, RULE_NONE);
        final boolean isForeground = isUidForegroundOnRestrictBackgroundLocked(uid);
        final boolean isForeground = isUidForegroundOnRestrictBackgroundLocked(uid);
        final boolean isBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
        final boolean isWhitelisted = mRestrictBackgroundWhitelistUids.get(uid);


        int newRule = RULE_UNKNOWN;
        // Data Saver status.
        final int oldRule = mUidRules.get(uid, RULE_UNKNOWN);
        final boolean isDsBlacklisted = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
        final boolean isDsWhitelisted = mRestrictBackgroundWhitelistUids.get(uid);
        int newDsRule = RULE_NONE;
        final int oldDsRule = oldUidRules & MASK_METERED_NETWORKS;


        // First step: define the new rule based on user restrictions and foreground state.
        // First step: define the new rule based on user restrictions and foreground state.
        if (isForeground) {
        if (isForeground) {
            if (isBlacklisted || (mRestrictBackground && !isWhitelisted)) {
            if (isDsBlacklisted || (mRestrictBackground && !isDsWhitelisted)) {
                newRule = RULE_TEMPORARY_ALLOW_METERED;
                newDsRule = RULE_TEMPORARY_ALLOW_METERED;
            }
            }
        } else {
        } else {
            if (isBlacklisted) {
            if (isDsBlacklisted) {
                newRule = RULE_REJECT_METERED;
                newDsRule = RULE_REJECT_METERED;
            } else if (isWhitelisted) {
            } else if (isDsWhitelisted) {
                newRule = RULE_ALLOW_METERED;
                newDsRule = RULE_ALLOW_METERED;
            }
            }
        }
        }


        final int newUidRules = newDsRule;

        if (LOGV) {
        if (LOGV) {
            Log.v(TAG, "updateRuleForRestrictBackgroundLocked(" + uid + "):"
            Log.v(TAG, "updateRuleForRestrictBackgroundLocked(" + uid + "):"
                    + " isForeground=" +isForeground + ", isBlacklisted: " + isBlacklisted
                    + " isForeground=" +isForeground + ", isBlacklisted: " + isDsBlacklisted
                    + ", isWhitelisted: " + isWhitelisted + ", newRule: " + ruleToString(newRule)
                    + ", isDsWhitelisted: " + isDsWhitelisted
                    + ", oldRule: " + ruleToString(oldRule));
                    + ", newUidRule: " + uidRulesToString(newUidRules)
                    + ", oldUidRule: " + uidRulesToString(oldUidRules));
        }
        }


        if (newRule == RULE_UNKNOWN) {
        if (newUidRules == RULE_NONE) {
            mUidRules.delete(uid);
            mUidRules.delete(uid);
        } else {
        } else {
            mUidRules.put(uid, newRule);
            mUidRules.put(uid, newUidRules);
        }
        }


        // Second step: apply bw changes based on change of state.
        // Second step: apply bw changes based on change of state.
        if (newRule != oldRule) {
        if (newDsRule != oldDsRule) {
            if (newRule == RULE_TEMPORARY_ALLOW_METERED) {
            if ((newDsRule & RULE_TEMPORARY_ALLOW_METERED) != 0) {
                // Temporarily whitelist foreground app, removing from blacklist if necessary
                // Temporarily whitelist foreground app, removing from blacklist if necessary
                // (since bw_penalty_box prevails over bw_happy_box).
                // (since bw_penalty_box prevails over bw_happy_box).


@@ -2836,44 +2859,48 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
                // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
                // but ideally it should be just:
                // but ideally it should be just:
                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
                if (isBlacklisted) {
                if (isDsBlacklisted) {
                    setMeteredNetworkBlacklist(uid, false);
                    setMeteredNetworkBlacklist(uid, false);
                }
                }
            } else if (oldRule == RULE_TEMPORARY_ALLOW_METERED) {
            } else if ((oldDsRule & RULE_TEMPORARY_ALLOW_METERED) != 0) {
                // Remove temporary whitelist from app that is not on foreground anymore.
                // Remove temporary whitelist from app that is not on foreground anymore.


                // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
                // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
                // but ideally they should be just:
                // but ideally they should be just:
                //    setMeteredNetworkWhitelist(uid, isWhitelisted);
                //    setMeteredNetworkWhitelist(uid, isWhitelisted);
                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
                //    setMeteredNetworkBlacklist(uid, isBlacklisted);
                if (!isWhitelisted) {
                if (!isDsWhitelisted) {
                    setMeteredNetworkWhitelist(uid, false);
                    setMeteredNetworkWhitelist(uid, false);
                }
                }
                if (isBlacklisted) {
                if (isDsBlacklisted) {
                    setMeteredNetworkBlacklist(uid, true);
                    setMeteredNetworkBlacklist(uid, true);
                }
                }
            } else if (newRule == RULE_REJECT_METERED || oldRule == RULE_REJECT_METERED) {
            } else if ((newDsRule & RULE_REJECT_METERED) != 0
                    || (oldDsRule & RULE_REJECT_METERED) != 0) {
                // Flip state because app was explicitly added or removed to blacklist.
                // Flip state because app was explicitly added or removed to blacklist.
                setMeteredNetworkBlacklist(uid, isBlacklisted);
                setMeteredNetworkBlacklist(uid, isDsBlacklisted);
                if (oldRule == RULE_REJECT_METERED && isWhitelisted) {
                if ((oldDsRule & RULE_REJECT_METERED) != 0 && isDsWhitelisted) {
                    // Since blacklist prevails over whitelist, we need to handle the special case
                    // Since blacklist prevails over whitelist, we need to handle the special case
                    // where app is whitelisted and blacklisted at the same time (although such
                    // where app is whitelisted and blacklisted at the same time (although such
                    // scenario should be blocked by the UI), then blacklist is removed.
                    // scenario should be blocked by the UI), then blacklist is removed.
                    setMeteredNetworkWhitelist(uid, isWhitelisted);
                    setMeteredNetworkWhitelist(uid, isDsWhitelisted);
                }
                }
            } else if (newRule == RULE_ALLOW_METERED || oldRule == RULE_ALLOW_METERED) {
            } else if ((newDsRule & RULE_ALLOW_METERED) != 0
                    || (oldDsRule & RULE_ALLOW_METERED) != 0) {
                // Flip state because app was explicitly added or removed to whitelist.
                // Flip state because app was explicitly added or removed to whitelist.
                setMeteredNetworkWhitelist(uid, isWhitelisted);
                setMeteredNetworkWhitelist(uid, isDsWhitelisted);
            } else {
            } else {
                // All scenarios should have been covered above
                // All scenarios should have been covered above
                Log.wtf(TAG, "Unexpected change of state for " + uid
                Log.wtf(TAG, "Unexpected change of metered UID state for " + uid
                        + ": foreground=" + isForeground + ", whitelisted=" + isWhitelisted
                        + ": foreground=" + isForeground
                        + ", blacklisted=" + isBlacklisted + ", newRule="
                        + ", whitelisted=" + isDsWhitelisted
                        + ruleToString(newRule) + ", oldRule=" + ruleToString(oldRule));
                        + ", blacklisted=" + isDsBlacklisted
                        + ", newRules=" + uidRulesToString(newUidRules)
                        + ", oldRules=" + uidRulesToString(oldUidRules));
            }
            }


            // dispatch changed rule to existing listeners
            // dispatch changed rule to existing listeners
            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newRule).sendToTarget();
            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
        }
        }
    }
    }