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

Commit b3f19ca3 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Enforce background data flag, rules through netd.

Connect up netd penalty box through NMS, and enforce the existing
background data flag by putting all UIDs in penalty box.  Allow
platform applications to have policy applied.  Only dispatch unique
rules to netd, avoiding any repeats.

Bug: 4598463, 4965677
Change-Id: Ibf9beff998ba7a1ea92f5e2f7eeba7b483d4b918
parent a91260bc
Loading
Loading
Loading
Loading
+20 −16
Original line number Diff line number Diff line
@@ -49,6 +49,8 @@ public class NetworkPolicyManager {
    /** Reject traffic on metered networks. */
    public static final int RULE_REJECT_METERED = 0x1;

    private static final boolean ALLOW_PLATFORM_APP_POLICY = true;

    /**
     * {@link Intent} action launched when user selects {@link NetworkPolicy}
     * warning notification.
@@ -223,6 +225,7 @@ public class NetworkPolicyManager {
            return false;
        }

        if (!ALLOW_PLATFORM_APP_POLICY) {
            final PackageManager pm = context.getPackageManager();
            final HashSet<Signature> systemSignature;
            try {
@@ -233,7 +236,7 @@ public class NetworkPolicyManager {
            }

            try {
            // reject apps signed with system cert
                // reject apps signed with platform cert
                for (String packageName : pm.getPackagesForUid(uid)) {
                    final HashSet<Signature> packageSignature = Sets.newHashSet(
                            pm.getPackageInfo(packageName, GET_SIGNATURES).signatures);
@@ -243,6 +246,7 @@ public class NetworkPolicyManager {
                }
            } catch (NameNotFoundException e) {
            }
        }

        // nothing found above; we can apply policy to UID
        return true;
+7 −2
Original line number Diff line number Diff line
@@ -210,9 +210,14 @@ interface INetworkManagementService
    NetworkStats getNetworkStatsUidDetail(int uid);

    /**
     * Set an overall quota for a group of interfaces.
     * Set quota for an interface.
     */
    void setInterfaceQuota(in String[] iface, long quota);
    void setInterfaceQuota(String iface, long quota);

    /**
     * Remove quota for an interface.
     */
    void removeInterfaceQuota(String iface);

    /**
     * Control network activity of a UID over interfaces with a quota limit.
+79 −25
Original line number Diff line number Diff line
@@ -37,9 +37,11 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;

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

import java.io.BufferedReader;
import java.io.DataInputStream;
@@ -52,6 +54,7 @@ import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
@@ -115,6 +118,11 @@ class NetworkManagementService extends INetworkManagementService.Stub {

    private ArrayList<INetworkManagementEventObserver> mObservers;

    /** Set of interfaces with active quotas. */
    private HashSet<String> mInterfaceQuota = Sets.newHashSet();
    /** Set of UIDs with active reject rules. */
    private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();

    /**
     * Constructs a new NetworkManagementService instance
     *
@@ -919,35 +927,81 @@ class NetworkManagementService extends INetworkManagementService.Stub {
    }

    @Override
    public void setInterfaceQuota(String[] iface, long quota)
            throws IllegalStateException {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.MANAGE_NETWORK_POLICY, "NetworkManagementService");
    public void setInterfaceQuota(String iface, long quota) {
        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);

        synchronized (mInterfaceQuota) {
            if (mInterfaceQuota.contains(iface)) {
                // TODO: eventually consider throwing
                return;
            }

            final StringBuilder command = new StringBuilder();
            command.append("bandwidth setiquota ").append(iface).append(" ").append(quota);

            try {
            // TODO: Add support for clubbing together multiple interfaces under
            // one quota. Will need support from the kernel and
            // BandwidthController to do this.
            mConnector.doCommand(
                    String.format("bandwidth setquota %s %d", iface[0], quota));
                // TODO: add support for quota shared across interfaces
                mConnector.doCommand(command.toString());
                mInterfaceQuota.add(iface);
            } catch (NativeDaemonConnectorException e) {
            throw new IllegalStateException(
                    "Error communicating to native daemon to set Interface quota",
                    e);
                throw new IllegalStateException("Error communicating to native daemon", e);
            }
        }
    }

    @Override
    public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces)
            throws IllegalStateException {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.MANAGE_NETWORK_POLICY, "NetworkManagementService");
    public void removeInterfaceQuota(String iface) {
        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);

        synchronized (mInterfaceQuota) {
            if (!mInterfaceQuota.contains(iface)) {
                // TODO: eventually consider throwing
                return;
            }

            final StringBuilder command = new StringBuilder();
            command.append("bandwidth removeiquota ").append(iface);

            try {
            // TODO: Connect with BandwidthController
            // mConnector.doCommand("");
                // TODO: add support for quota shared across interfaces
                mConnector.doCommand(command.toString());
                mInterfaceQuota.remove(iface);
            } catch (NativeDaemonConnectorException e) {
            throw new IllegalStateException(
                    "Error communicating to native daemon to set Interface quota",
                    e);
                throw new IllegalStateException("Error communicating to native daemon", e);
            }
        }
    }

    @Override
    public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);

        synchronized (mUidRejectOnQuota) {
            final boolean oldRejectOnQuota = mUidRejectOnQuota.get(uid, false);
            if (oldRejectOnQuota == rejectOnQuotaInterfaces) {
                // TODO: eventually consider throwing
                return;
            }

            final StringBuilder command = new StringBuilder();
            command.append("bandwidth");
            if (rejectOnQuotaInterfaces) {
                command.append(" addnaughtyapps");
            } else {
                command.append(" removenaughtyapps");
            }
            command.append(" ").append(uid);

            try {
                mConnector.doCommand(command.toString());
                if (rejectOnQuotaInterfaces) {
                    mUidRejectOnQuota.put(uid, true);
                } else {
                    mUidRejectOnQuota.delete(uid);
                }
            } catch (NativeDaemonConnectorException e) {
                throw new IllegalStateException("Error communicating to native daemon", e);
            }
        }
    }

+124 −39
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.Manifest.permission.READ_PHONE_STATE;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.*;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -57,6 +58,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
@@ -110,6 +113,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import libcore.io.IoUtils;

@@ -166,6 +170,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    private final Object mRulesLock = new Object();

    private boolean mScreenOn;
    private boolean mBackgroundData;

    /** Current policy for network templates. */
    private ArrayList<NetworkPolicy> mNetworkPolicy = Lists.newArrayList();
@@ -194,13 +199,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
    // TODO: keep whitelist of system-critical services that should never have
    // rules enforced, such as system, phone, and radio UIDs.

    // TODO: watch for package added broadcast to catch new UIDs.

    public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
            IPowerManager powerManager, INetworkStatsService networkStats,
            INetworkManagementService networkManagement) {
        // TODO: move to using cached NtpTrustedTime
        this(context, activityManager, powerManager, networkStats,
                networkManagement, new NtpTrustedTime(),
                getSystemDir());
        this(context, activityManager, powerManager, networkStats, networkManagement,
                new NtpTrustedTime(), getSystemDir());
    }

    private static File getSystemDir() {
@@ -215,7 +221,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        mActivityManager = checkNotNull(activityManager, "missing activityManager");
        mPowerManager = checkNotNull(powerManager, "missing powerManager");
        mNetworkStats = checkNotNull(networkStats, "missing networkStats");
        mNetworkManagement = checkNotNull(networkManagement, "missing networkManagementService");
        mNetworkManagement = checkNotNull(networkManagement, "missing networkManagement");
        mTime = checkNotNull(time, "missing TrustedTime");

        mHandlerThread = new HandlerThread(TAG);
@@ -241,6 +247,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }

        updateScreenOn();
        updateBackgroundData(true);

        try {
            mActivityManager.registerProcessObserver(mProcessObserver);
@@ -266,11 +273,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
        mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);

        // listen for warning polling events; currently dispatched by
        // listen for stats update events
        final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
        mContext.registerReceiver(
                mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);

        // listen for changes to background data flag
        final IntentFilter bgFilter = new IntentFilter(ACTION_BACKGROUND_DATA_SETTING_CHANGED);
        mContext.registerReceiver(mBgReceiver, bgFilter, CONNECTIVITY_INTERNAL, mHandler);

    }

    private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -352,6 +363,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
    };

    /**
     * Receiver that watches for
     * {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
     */
    private BroadcastReceiver mBgReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // on background handler thread, and verified CONNECTIVITY_INTERNAL
            // permission above.

            synchronized (mRulesLock) {
                updateBackgroundData(false);
            }
        }
    };

    /**
     * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
     * to show visible notifications as needed.
@@ -565,7 +592,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
                : System.currentTimeMillis();

        mMeteredIfaces.clear();
        final HashSet<String> newMeteredIfaces = Sets.newHashSet();

        // apply each policy that we found ifaces for; compute remaining data
        // based on current cycle and historical stats, and push to kernel.
@@ -595,48 +622,30 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            if (policy.limitBytes != NetworkPolicy.LIMIT_DISABLED) {
                // remaining "quota" is based on usage in current cycle
                final long quota = Math.max(0, policy.limitBytes - total);
                if (LOGD) {
                    Slog.d(TAG, "Applying quota rules for ifaces=" + Arrays.toString(ifaces)
                            + " LIMIT=" + policy.limitBytes + "  TOTAL="
                            + total + "  QUOTA=" + quota);
                }

                setQuotaOnIfaceList(ifaces, quota);
                if (ifaces.length > 1) {
                    // TODO: switch to shared quota once NMS supports
                    Slog.w(TAG, "shared quota unsupported; generating rule for each iface");
                }

                for (String iface : ifaces) {
                    mMeteredIfaces.add(iface);
                }
                    removeInterfaceQuota(iface);
                    setInterfaceQuota(iface, quota);
                    newMeteredIfaces.add(iface);
                }
            }

        final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
        mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
        }

    private void setQuotaOnIfaceList(String[] ifaces, long quota) {
        try {
            mNetworkManagement.setInterfaceQuota(ifaces, quota);
        } catch (IllegalStateException e) {
            Slog.e(TAG, "IllegalStateException in setQuotaOnIfaceList " + e);
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote Exception in setQuotaOnIfaceList " + e);
        // remove quota on any trailing interfaces
        for (String iface : mMeteredIfaces) {
            if (!newMeteredIfaces.contains(iface)) {
                removeInterfaceQuota(iface);
            }
        }
        mMeteredIfaces = newMeteredIfaces;

    private void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
        // TODO: connect over to NMS
        // ndc bandwidth app <uid> naughty
        try {
            if (LOGD) {
                Slog.d(TAG, "setUidNetworkRules() with uid=" + uid
                        + ", rejectOnQuotaInterfaces=" + rejectOnQuotaInterfaces);
            }
            mNetworkManagement.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
        } catch (IllegalStateException e) {
            Slog.e(TAG, "IllegalStateException in setUidNetworkRules " + e);
        } catch (RemoteException e) {
            Slog.e(TAG, "Remote Exception in setUidNetworkRules " + e);
        }
        final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
        mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
    }

    /**
@@ -962,6 +971,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
    }

    private void updateBackgroundData(boolean systemReady) {
        synchronized (mRulesLock) {
            try {
                mBackgroundData = mConnManager.getBackgroundDataSetting();
            } catch (RemoteException e) {
            }
            if (systemReady && mBackgroundData) {
                // typical behavior of background enabled during systemReady;
                // no need to clear rules for all UIDs.
            } else {
                updateRulesForBackgroundDataLocked();
            }
        }
    }

    /**
     * Update rules that might be changed by {@link #mScreenOn} value.
     */
@@ -976,6 +1000,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
    }

    /**
     * Update rules that might be changed by {@link #mBackgroundData} value.
     */
    private void updateRulesForBackgroundDataLocked() {
        // update rules for all installed applications
        final PackageManager pm = mContext.getPackageManager();
        final List<ApplicationInfo> apps = pm.getInstalledApplications(0);
        for (ApplicationInfo app : apps) {
            updateRulesForUidLocked(app.uid);
        }

        // and catch system UIDs
        // TODO: keep in sync with android_filesystem_config.h
        for (int uid = 1000; uid <= 1025; uid++) {
            updateRulesForUidLocked(uid);
        }
        for (int uid = 2000; uid <= 2002; uid++) {
            updateRulesForUidLocked(uid);
        }
        for (int uid = 3000; uid <= 3007; uid++) {
            updateRulesForUidLocked(uid);
        }
        for (int uid = 9998; uid <= 9999; uid++) {
            updateRulesForUidLocked(uid);
        }
    }

    private void updateRulesForUidLocked(int uid) {
        final int uidPolicy = getUidPolicy(uid);
        final boolean uidForeground = isUidForeground(uid);
@@ -986,6 +1037,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
            // uid in background, and policy says to block metered data
            uidRules = RULE_REJECT_METERED;
        }
        if (!uidForeground && !mBackgroundData) {
            // uid in background, and global background disabled
            uidRules = RULE_REJECT_METERED;
        }

        // TODO: only dispatch when rules actually change

@@ -1041,6 +1096,36 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
        }
    };

    private void setInterfaceQuota(String iface, long quota) {
        try {
            mNetworkManagement.setInterfaceQuota(iface, quota);
        } catch (IllegalStateException e) {
            Slog.e(TAG, "problem setting interface quota", e);
        } catch (RemoteException e) {
            Slog.e(TAG, "problem setting interface quota", e);
        }
    }

    private void removeInterfaceQuota(String iface) {
        try {
            mNetworkManagement.removeInterfaceQuota(iface);
        } catch (IllegalStateException e) {
            Slog.e(TAG, "problem removing interface quota", e);
        } catch (RemoteException e) {
            Slog.e(TAG, "problem removing interface quota", e);
        }
    }

    private void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
        try {
            mNetworkManagement.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
        } catch (IllegalStateException e) {
            Slog.e(TAG, "problem setting uid rules", e);
        } catch (RemoteException e) {
            Slog.e(TAG, "problem setting uid rules", e);
        }
    }

    private String getActiveSubscriberId() {
        final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
                Context.TELEPHONY_SERVICE);
+37 −2
Original line number Diff line number Diff line
@@ -179,6 +179,9 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
        expectTime(System.currentTimeMillis());

        // default behavior is background data enabled
        expect(mConnManager.getBackgroundDataSetting()).andReturn(true);

        replay();
        mService.systemReady();
        verifyAndReset();
@@ -257,6 +260,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
    public void testScreenChangesRules() throws Exception {
        Future<Void> future;

        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
@@ -264,6 +268,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // push strict policy for foreground uid, verify ALLOW rule
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -272,6 +277,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {

        // now turn screen off and verify REJECT rule
        expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce();
        expectSetUidNetworkRules(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
        mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
@@ -280,6 +286,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {

        // and turn screen back on, verify ALLOW rule restored
        expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
@@ -290,6 +297,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
    public void testPolicyNone() throws Exception {
        Future<Void> future;

        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
@@ -297,6 +305,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_NONE should RULE_ALLOW in foreground
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mService.setUidPolicy(UID_A, POLICY_NONE);
@@ -304,6 +313,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_NONE should RULE_ALLOW in background
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
@@ -315,6 +325,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        Future<Void> future;

        // POLICY_REJECT should RULE_ALLOW in background
        expectSetUidNetworkRules(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -322,6 +333,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_REJECT should RULE_ALLOW in foreground
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
@@ -329,6 +341,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_REJECT should RULE_REJECT in background
        expectSetUidNetworkRules(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
@@ -340,6 +353,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        Future<Void> future;

        // POLICY_NONE should have RULE_ALLOW in background
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
@@ -348,6 +362,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // adding POLICY_REJECT should cause RULE_REJECT
        expectSetUidNetworkRules(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -355,6 +370,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // removing POLICY_REJECT should return us to RULE_ALLOW
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        mService.setUidPolicy(UID_A, POLICY_NONE);
@@ -435,8 +451,9 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, TIME_MAR_10))
                .andReturn(stats).atLeastOnce();

        // expect that quota remaining should be 1536 bytes
        // TODO: write up NetworkManagementService mock
        // TODO: consider making strongly ordered mock
        expectRemoveInterfaceQuota(TEST_IFACE);
        expectSetInterfaceQuota(TEST_IFACE, 1536L);

        expectClearNotifications();
        future = expectMeteredIfacesChanged(TEST_IFACE);
@@ -451,6 +468,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        Future<Void> future;

        // POLICY_REJECT should RULE_REJECT in background
        expectSetUidNetworkRules(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
@@ -458,6 +476,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // uninstall should clear RULE_REJECT
        expectSetUidNetworkRules(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
        final Intent intent = new Intent(ACTION_UID_REMOVED);
@@ -498,6 +517,22 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        expectLastCall().anyTimes();
    }

    private void expectSetInterfaceQuota(String iface, long quota) throws Exception {
        mNetworkManagement.setInterfaceQuota(iface, quota);
        expectLastCall().atLeastOnce();
    }

    private void expectRemoveInterfaceQuota(String iface) throws Exception {
        mNetworkManagement.removeInterfaceQuota(iface);
        expectLastCall().atLeastOnce();
    }

    private void expectSetUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces)
            throws Exception {
        mNetworkManagement.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
        expectLastCall().atLeastOnce();
    }

    private Future<Void> expectRulesChanged(int uid, int policy) throws Exception {
        final FutureAnswer future = new FutureAnswer();
        mPolicyListener.onUidRulesChanged(eq(uid), eq(policy));