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

Commit a9e55bcc authored by Kweku Adams's avatar Kweku Adams
Browse files

Ability to grant apps in app idle mode network.

This introduces a new whitelist that JobScheduler can use to request
that an app be whitelisted from network restrictions while in app idle.
This is intended to be a temporary whitelist and the app will still be
subject to other power saving restrictions.

Bug: 117846754
Bug: 111423978
Test: atest CtsHostsideNetworkTests
and atest NetworkPolicyManagerServiceTest
Change-Id: I75a77d95c0a2052b456cd011dcbc953fff09f34c
parent f912513f
Loading
Loading
Loading
Loading
+25 −0
Original line number Original line Diff line number Diff line
@@ -64,6 +64,7 @@ public class NetworkPolicyLogger {
    private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
    private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
    private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
    private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
    private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
    private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
    private static final int EVENT_APP_IDLE_WL_CHANGED = 14;


    static final int NTWK_BLOCKED_POWER = 0;
    static final int NTWK_BLOCKED_POWER = 0;
    static final int NTWK_ALLOWED_NON_METERED = 1;
    static final int NTWK_ALLOWED_NON_METERED = 1;
@@ -145,6 +146,13 @@ public class NetworkPolicyLogger {
        }
        }
    }
    }


    void appIdleWlChanged(int uid, boolean isWhitelisted) {
        synchronized (mLock) {
            if (LOGD) Slog.d(TAG, getAppIdleWlChangedLog(uid, isWhitelisted));
            mEventsBuffer.appIdleWlChanged(uid, isWhitelisted);
        }
    }

    void paroleStateChanged(boolean paroleOn) {
    void paroleStateChanged(boolean paroleOn) {
        synchronized (mLock) {
        synchronized (mLock) {
            if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
            if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
@@ -259,6 +267,10 @@ public class NetworkPolicyLogger {
        return "App idle state of uid " + uid + ": " + idle;
        return "App idle state of uid " + uid + ": " + idle;
    }
    }


    private static String getAppIdleWlChangedLog(int uid, boolean isWhitelisted) {
        return "App idle whitelist state of uid " + uid + ": " + isWhitelisted;
    }

    private static String getParoleStateChanged(boolean paroleOn) {
    private static String getParoleStateChanged(boolean paroleOn) {
        return "Parole state: " + paroleOn;
        return "Parole state: " + paroleOn;
    }
    }
@@ -409,6 +421,17 @@ public class NetworkPolicyLogger {
            data.timeStamp = System.currentTimeMillis();
            data.timeStamp = System.currentTimeMillis();
        }
        }


        public void appIdleWlChanged(int uid, boolean isWhitelisted) {
            final Data data = getNextSlot();
            if (data == null) return;

            data.reset();
            data.type = EVENT_APP_IDLE_WL_CHANGED;
            data.ifield1 = uid;
            data.bfield1 = isWhitelisted;
            data.timeStamp = System.currentTimeMillis();
        }

        public void paroleStateChanged(boolean paroleOn) {
        public void paroleStateChanged(boolean paroleOn) {
            final Data data = getNextSlot();
            final Data data = getNextSlot();
            if (data == null) return;
            if (data == null) return;
@@ -487,6 +510,8 @@ public class NetworkPolicyLogger {
                    return getDeviceIdleModeEnabled(data.bfield1);
                    return getDeviceIdleModeEnabled(data.bfield1);
                case EVENT_APP_IDLE_STATE_CHANGED:
                case EVENT_APP_IDLE_STATE_CHANGED:
                    return getAppIdleChangedLog(data.ifield1, data.bfield1);
                    return getAppIdleChangedLog(data.ifield1, data.bfield1);
                case EVENT_APP_IDLE_WL_CHANGED:
                    return getAppIdleWlChangedLog(data.ifield1, data.bfield1);
                case EVENT_PAROLE_STATE_CHANGED:
                case EVENT_PAROLE_STATE_CHANGED:
                    return getParoleStateChanged(data.bfield1);
                    return getParoleStateChanged(data.bfield1);
                case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
                case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
+6 −0
Original line number Original line Diff line number Diff line
@@ -104,6 +104,12 @@ public abstract class NetworkPolicyManagerInternal {
     */
     */
    public abstract void onAdminDataAvailable();
    public abstract void onAdminDataAvailable();


    /**
     * Control if a UID should be whitelisted even if it's in app idle mode. Other restrictions may
     * still be in effect.
     */
    public abstract void setAppIdleWhitelist(int uid, boolean shouldWhitelist);

    /**
    /**
     * Sets a list of packages which are restricted by admin from accessing metered data.
     * Sets a list of packages which are restricted by admin from accessing metered data.
     *
     *
+93 −5
Original line number Original line Diff line number Diff line
@@ -464,6 +464,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    @GuardedBy("mUidRulesFirstLock")
    @GuardedBy("mUidRulesFirstLock")
    final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();
    final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();


    // "Power save mode" is the concept used in the DeviceIdleController that includes various
    // features including Doze and Battery Saver. It include Battery Saver, but "power save mode"
    // and "battery saver" are not equivalent.

    /**
    /**
     * UIDs that have been white-listed to always be able to have network access
     * UIDs that have been white-listed to always be able to have network access
     * in power save mode, except device idle (doze) still applies.
     * in power save mode, except device idle (doze) still applies.
@@ -483,6 +487,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    @GuardedBy("mUidRulesFirstLock")
    @GuardedBy("mUidRulesFirstLock")
    private final SparseBooleanArray mPowerSaveTempWhitelistAppIds = new SparseBooleanArray();
    private final SparseBooleanArray mPowerSaveTempWhitelistAppIds = new SparseBooleanArray();


    /**
     * UIDs that have been white-listed temporarily to be able to have network access despite being
     * idle. Other power saving restrictions still apply.
     */
    @GuardedBy("mUidRulesFirstLock")
    private final SparseBooleanArray mAppIdleTempWhitelistAppIds = new SparseBooleanArray();

    /**
    /**
     * UIDs that have been initially white-listed by system to avoid restricted background.
     * UIDs that have been initially white-listed by system to avoid restricted background.
     */
     */
@@ -3372,6 +3383,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
                    fout.decreaseIndent();
                    fout.decreaseIndent();
                }
                }


                size = mAppIdleTempWhitelistAppIds.size();
                if (size > 0) {
                    fout.println("App idle whitelist app ids:");
                    fout.increaseIndent();
                    for (int i = 0; i < size; i++) {
                        fout.print("UID=");
                        fout.print(mAppIdleTempWhitelistAppIds.keyAt(i));
                        fout.print(": ");
                        fout.print(mAppIdleTempWhitelistAppIds.valueAt(i));
                        fout.println();
                    }
                    fout.decreaseIndent();
                }

                size = mDefaultRestrictBackgroundWhitelistUids.size();
                size = mDefaultRestrictBackgroundWhitelistUids.size();
                if (size > 0) {
                if (size > 0) {
                    fout.println("Default restrict background whitelist uids:");
                    fout.println("Default restrict background whitelist uids:");
@@ -3640,12 +3665,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    }
    }


    /**
    /**
     * Returns whether a uid is whitelisted from power saving restrictions (eg: Battery Saver, Doze
     * mode, and app idle).
     *
     * @param deviceIdleMode if true then we don't consider
     * @param deviceIdleMode if true then we don't consider
     *        {@link #mPowerSaveWhitelistExceptIdleAppIds} for checking if the {@param uid} is
     *        {@link #mPowerSaveWhitelistExceptIdleAppIds} for checking if the {@param uid} is
     *        whitelisted.
     *        whitelisted.
     */
     */
    @GuardedBy("mUidRulesFirstLock")
    @GuardedBy("mUidRulesFirstLock")
    private boolean isWhitelistedBatterySaverUL(int uid, boolean deviceIdleMode) {
    private boolean isWhitelistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
        final int appId = UserHandle.getAppId(uid);
        final int appId = UserHandle.getAppId(uid);
        boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
        boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
                || mPowerSaveWhitelistAppIds.get(appId);
                || mPowerSaveWhitelistAppIds.get(appId);
@@ -3660,7 +3688,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    @GuardedBy("mUidRulesFirstLock")
    @GuardedBy("mUidRulesFirstLock")
    private void updateRulesForWhitelistedPowerSaveUL(int uid, boolean enabled, int chain) {
    private void updateRulesForWhitelistedPowerSaveUL(int uid, boolean enabled, int chain) {
        if (enabled) {
        if (enabled) {
            final boolean isWhitelisted = isWhitelistedBatterySaverUL(uid,
            final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid,
                    chain == FIREWALL_CHAIN_DOZABLE);
                    chain == FIREWALL_CHAIN_DOZABLE);
            if (isWhitelisted || isUidForegroundOnRestrictPowerUL(uid)) {
            if (isWhitelisted || isUidForegroundOnRestrictPowerUL(uid)) {
                setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
                setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
@@ -3712,8 +3740,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
            if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
                    && !isUidForegroundOnRestrictPowerUL(uid)) {
                    && !isUidForegroundOnRestrictPowerUL(uid)) {
                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
                if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL DENY " + uid);
            } else {
            } else {
                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
                if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL " + uid + " to DEFAULT");
            }
            }
        } finally {
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -3896,7 +3926,59 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        return UserHandle.isApp(uid) && hasInternetPermissions(uid);
        return UserHandle.isApp(uid) && hasInternetPermissions(uid);
    }
    }


    private boolean isUidIdle(int uid) {
    /**
     * Set whether or not an app should be whitelisted for network access while in app idle. Other
     * power saving restrictions may still apply.
     */
    @VisibleForTesting
    public void setAppIdleWhitelist(int uid, boolean shouldWhitelist) {
        synchronized (mUidRulesFirstLock) {
            if (mAppIdleTempWhitelistAppIds.get(uid) == shouldWhitelist) {
                // No change.
                return;
            }

            final long token = Binder.clearCallingIdentity();
            try {
                mLogger.appIdleWlChanged(uid, shouldWhitelist);
                if (shouldWhitelist) {
                    mAppIdleTempWhitelistAppIds.put(uid, true);
                } else {
                    mAppIdleTempWhitelistAppIds.delete(uid);
                }
                updateRuleForAppIdleUL(uid);
                updateRulesForPowerRestrictionsUL(uid);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

    /** Return the list of UIDs currently in the app idle whitelist. */
    @VisibleForTesting
    public int[] getAppIdleWhitelist() {
        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);

        synchronized (mUidRulesFirstLock) {
            final int len = mAppIdleTempWhitelistAppIds.size();
            int[] uids = new int[len];
            for (int i = 0; i < len; ++i) {
                uids[i] = mAppIdleTempWhitelistAppIds.keyAt(i);
            }
            return uids;
        }
    }

    /** Returns if the UID is currently considered idle. */
    @VisibleForTesting
    public boolean isUidIdle(int uid) {
        synchronized (mUidRulesFirstLock) {
            if (mAppIdleTempWhitelistAppIds.get(uid)) {
                // UID is temporarily whitelisted.
                return false;
            }
        }

        final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
        final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
        final int userId = UserHandle.getUserId(uid);
        final int userId = UserHandle.getUserId(uid);


@@ -3940,6 +4022,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
        mPowerSaveWhitelistExceptIdleAppIds.delete(uid);
        mPowerSaveWhitelistAppIds.delete(uid);
        mPowerSaveWhitelistAppIds.delete(uid);
        mPowerSaveTempWhitelistAppIds.delete(uid);
        mPowerSaveTempWhitelistAppIds.delete(uid);
        mAppIdleTempWhitelistAppIds.delete(uid);


        // ...then update iptables asynchronously.
        // ...then update iptables asynchronously.
        mHandler.obtainMessage(MSG_RESET_FIREWALL_RULES_BY_UID, uid, 0).sendToTarget();
        mHandler.obtainMessage(MSG_RESET_FIREWALL_RULES_BY_UID, uid, 0).sendToTarget();
@@ -3984,7 +4067,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     * <li>@{code bw_happy_box}: UIDs added to this chain have access (whitelist), unless they're
     * <li>@{code bw_happy_box}: UIDs added to this chain have access (whitelist), unless they're
     *     also blacklisted.
     *     also blacklisted.
     * <li>@{code bw_data_saver}: when enabled (through {@link #setRestrictBackground(boolean)}),
     * <li>@{code bw_data_saver}: when enabled (through {@link #setRestrictBackground(boolean)}),
     *     no UIDs other those whitelisted will have access.
     *     no UIDs other than those whitelisted will have access.
     * <ul>
     * <ul>
     *
     *
     * <p>The @{code bw_penalty_box} and @{code bw_happy_box} are primarily managed through the
     * <p>The @{code bw_penalty_box} and @{code bw_happy_box} are primarily managed through the
@@ -4194,7 +4277,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
        final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
        final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
        final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);


        final boolean isWhitelisted = isWhitelistedBatterySaverUL(uid, mDeviceIdleMode);
        final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
        final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
        final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
        int newRule = RULE_NONE;
        int newRule = RULE_NONE;


@@ -5022,6 +5105,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            mAdminDataAvailableLatch.countDown();
            mAdminDataAvailableLatch.countDown();
        }
        }


        @Override
        public void setAppIdleWhitelist(int uid, boolean shouldWhitelist) {
            NetworkPolicyManagerService.this.setAppIdleWhitelist(uid, shouldWhitelist);
        }

        @Override
        @Override
        public void setMeteredRestrictedPackages(Set<String> packageNames, int userId) {
        public void setMeteredRestrictedPackages(Set<String> packageNames, int userId) {
            setMeteredRestrictedPackagesInternal(packageNames, userId);
            setMeteredRestrictedPackagesInternal(packageNames, userId);
+38 −1
Original line number Original line Diff line number Diff line
@@ -78,6 +78,8 @@ class NetworkPolicyManagerShellCommand extends ShellCommand {
        pw.println("    Adds a UID to the whitelist for restrict background usage.");
        pw.println("    Adds a UID to the whitelist for restrict background usage.");
        pw.println("  add restrict-background-blacklist UID");
        pw.println("  add restrict-background-blacklist UID");
        pw.println("    Adds a UID to the blacklist for restrict background usage.");
        pw.println("    Adds a UID to the blacklist for restrict background usage.");
        pw.println("  add app-idle-whitelist UID");
        pw.println("    Adds a UID to the temporary app idle whitelist.");
        pw.println("  get restrict-background");
        pw.println("  get restrict-background");
        pw.println("    Gets the global restrict background usage status.");
        pw.println("    Gets the global restrict background usage status.");
        pw.println("  list wifi-networks [true|false]");
        pw.println("  list wifi-networks [true|false]");
@@ -92,6 +94,8 @@ class NetworkPolicyManagerShellCommand extends ShellCommand {
        pw.println("    Removes a UID from the whitelist for restrict background usage.");
        pw.println("    Removes a UID from the whitelist for restrict background usage.");
        pw.println("  remove restrict-background-blacklist UID");
        pw.println("  remove restrict-background-blacklist UID");
        pw.println("    Removes a UID from the blacklist for restrict background usage.");
        pw.println("    Removes a UID from the blacklist for restrict background usage.");
        pw.println("  remove app-idle-whitelist UID");
        pw.println("    Removes a UID from the temporary app idle whitelist.");
        pw.println("  set metered-network ID [undefined|true|false]");
        pw.println("  set metered-network ID [undefined|true|false]");
        pw.println("    Toggles whether the given wi-fi network is metered.");
        pw.println("    Toggles whether the given wi-fi network is metered.");
        pw.println("  set restrict-background BOOLEAN");
        pw.println("  set restrict-background BOOLEAN");
@@ -142,6 +146,8 @@ class NetworkPolicyManagerShellCommand extends ShellCommand {
            return -1;
            return -1;
        }
        }
        switch(type) {
        switch(type) {
            case "app-idle-whitelist":
                return listAppIdleWhitelist();
            case "wifi-networks":
            case "wifi-networks":
                return listWifiNetworks();
                return listWifiNetworks();
            case "restrict-background-whitelist":
            case "restrict-background-whitelist":
@@ -165,6 +171,8 @@ class NetworkPolicyManagerShellCommand extends ShellCommand {
                return addRestrictBackgroundWhitelist();
                return addRestrictBackgroundWhitelist();
            case "restrict-background-blacklist":
            case "restrict-background-blacklist":
                return addRestrictBackgroundBlacklist();
                return addRestrictBackgroundBlacklist();
            case "app-idle-whitelist":
                return addAppIdleWhitelist();
        }
        }
        pw.println("Error: unknown add type '" + type + "'");
        pw.println("Error: unknown add type '" + type + "'");
        return -1;
        return -1;
@@ -182,14 +190,20 @@ class NetworkPolicyManagerShellCommand extends ShellCommand {
                return removeRestrictBackgroundWhitelist();
                return removeRestrictBackgroundWhitelist();
            case "restrict-background-blacklist":
            case "restrict-background-blacklist":
                return removeRestrictBackgroundBlacklist();
                return removeRestrictBackgroundBlacklist();
            case "app-idle-whitelist":
                return removeAppIdleWhitelist();
        }
        }
        pw.println("Error: unknown remove type '" + type + "'");
        pw.println("Error: unknown remove type '" + type + "'");
        return -1;
        return -1;
    }
    }


    private int listUidPolicies(String msg, int policy) throws RemoteException {
    private int listUidPolicies(String msg, int policy) throws RemoteException {
        final PrintWriter pw = getOutPrintWriter();
        final int[] uids = mInterface.getUidsWithPolicy(policy);
        final int[] uids = mInterface.getUidsWithPolicy(policy);
        return listUidList(msg, uids);
    }

    private int listUidList(String msg, int[] uids) {
        final PrintWriter pw = getOutPrintWriter();
        pw.print(msg); pw.print(": ");
        pw.print(msg); pw.print(": ");
        if (uids.length == 0) {
        if (uids.length == 0) {
            pw.println("none");
            pw.println("none");
@@ -214,6 +228,12 @@ class NetworkPolicyManagerShellCommand extends ShellCommand {
                POLICY_REJECT_METERED_BACKGROUND);
                POLICY_REJECT_METERED_BACKGROUND);
    }
    }


    private int listAppIdleWhitelist() throws RemoteException {
        final PrintWriter pw = getOutPrintWriter();
        final int[] uids = mInterface.getAppIdleWhitelist();
        return listUidList("App Idle whitelisted UIDs", uids);
    }

    private int getRestrictBackground() throws RemoteException {
    private int getRestrictBackground() throws RemoteException {
        final PrintWriter pw = getOutPrintWriter();
        final PrintWriter pw = getOutPrintWriter();
        pw.print("Restrict background status: ");
        pw.print("Restrict background status: ");
@@ -277,6 +297,23 @@ class NetworkPolicyManagerShellCommand extends ShellCommand {
        return resetUidPolicy("not blacklisted", POLICY_REJECT_METERED_BACKGROUND);
        return resetUidPolicy("not blacklisted", POLICY_REJECT_METERED_BACKGROUND);
    }
    }


    private int setAppIdleWhitelist(boolean isWhitelisted) {
        final int uid = getUidFromNextArg();
        if (uid < 0) {
            return uid;
        }
        mInterface.setAppIdleWhitelist(uid, isWhitelisted);
        return 0;
    }

    private int addAppIdleWhitelist() throws RemoteException {
        return setAppIdleWhitelist(true);
    }

    private int removeAppIdleWhitelist() throws RemoteException {
        return setAppIdleWhitelist(false);
    }

    private int listWifiNetworks() {
    private int listWifiNetworks() {
        final PrintWriter pw = getOutPrintWriter();
        final PrintWriter pw = getOutPrintWriter();
        final String arg = getNextArg();
        final String arg = getNextArg();
+15 −3
Original line number Original line Diff line number Diff line
@@ -148,6 +148,9 @@ import com.android.server.net.NetworkStatsManagerInternal;


import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.AbstractFuture;


import libcore.io.IoUtils;
import libcore.io.Streams;

import org.junit.After;
import org.junit.After;
import org.junit.Before;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Rule;
@@ -162,9 +165,6 @@ import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.Answer;


import libcore.io.IoUtils;
import libcore.io.Streams;

import java.io.File;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStream;
@@ -843,6 +843,18 @@ public class NetworkPolicyManagerServiceTest {
        assertTrue(mService.isUidForeground(UID_B));
        assertTrue(mService.isUidForeground(UID_B));
    }
    }


    @Test
    public void testAppIdleTempWhitelisting() throws Exception {
        mService.setAppIdleWhitelist(UID_A, true);
        mService.setAppIdleWhitelist(UID_B, false);
        int[] whitelistedIds = mService.getAppIdleWhitelist();
        assertTrue(Arrays.binarySearch(whitelistedIds, UID_A) >= 0);
        assertTrue(Arrays.binarySearch(whitelistedIds, UID_B) < 0);
        assertFalse(mService.isUidIdle(UID_A));
        // Can't currently guarantee UID_B's app idle state.
        // TODO: expand with multiple app idle states.
    }

    private static long computeLastCycleBoundary(long currentTime, NetworkPolicy policy) {
    private static long computeLastCycleBoundary(long currentTime, NetworkPolicy policy) {
        RecurrenceRule.sClock = Clock.fixed(Instant.ofEpochMilli(currentTime),
        RecurrenceRule.sClock = Clock.fixed(Instant.ofEpochMilli(currentTime),
                ZoneId.systemDefault());
                ZoneId.systemDefault());