Merge "Do not register the callbacks in the constructor."
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 419288b..c72c4b0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -22,7 +22,6 @@
 import android.os.RemoteException;
 
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 
 /**
  * Class that answers queries about the state of network connectivity. It also
@@ -40,8 +39,9 @@
  * state of the available networks</li>
  * </ol>
  */
-public class ConnectivityManager
-{
+public class ConnectivityManager {
+    private static final String TAG = "ConnectivityManager";
+
     /**
      * A change in network connectivity has occurred. A connection has either
      * been established or lost. The NetworkInfo for the affected network is
@@ -109,7 +109,7 @@
      * The lookup key for an int that provides information about
      * our connection to the internet at large.  0 indicates no connection,
      * 100 indicates a great connection.  Retrieve it with
-     * {@link android.content.Intent@getIntExtra(String)}.
+     * {@link android.content.Intent#getIntExtra(String, int)}.
      * {@hide}
      */
     public static final String EXTRA_INET_CONDITION = "inetCondition";
@@ -120,13 +120,12 @@
      * <p>
      * If an application uses the network in the background, it should listen
      * for this broadcast and stop using the background data if the value is
-     * false.
+     * {@code false}.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED =
             "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
 
-
     /**
      * Broadcast Action: The network connection may not be good
      * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and
@@ -255,7 +254,7 @@
 
     public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
 
-    private IConnectivityManager mService;
+    private final IConnectivityManager mService;
 
     static public boolean isNetworkTypeValid(int networkType) {
         return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
@@ -284,6 +283,15 @@
         }
     }
 
+    /** {@hide} */
+    public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+        try {
+            return mService.getActiveNetworkInfoForUid(uid);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
     public NetworkInfo getNetworkInfo(int networkType) {
         try {
             return mService.getNetworkInfo(networkType);
@@ -300,7 +308,7 @@
         }
     }
 
-    /** @hide */
+    /** {@hide} */
     public LinkProperties getActiveLinkProperties() {
         try {
             return mService.getActiveLinkProperties();
@@ -309,7 +317,7 @@
         }
     }
 
-    /** @hide */
+    /** {@hide} */
     public LinkProperties getLinkProperties(int networkType) {
         try {
             return mService.getLinkProperties(networkType);
@@ -479,19 +487,11 @@
     }
 
     /**
-     * Don't allow use of default constructor.
-     */
-    @SuppressWarnings({"UnusedDeclaration"})
-    private ConnectivityManager() {
-    }
-
-    /**
      * {@hide}
      */
     public ConnectivityManager(IConnectivityManager service) {
         if (service == null) {
-            throw new IllegalArgumentException(
-                "ConnectivityManager() cannot be constructed with null service");
+            throw new IllegalArgumentException("missing IConnectivityManager");
         }
         mService = service;
     }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 8be492c..647a60a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -33,13 +33,11 @@
     int getNetworkPreference();
 
     NetworkInfo getActiveNetworkInfo();
-
+    NetworkInfo getActiveNetworkInfoForUid(int uid);
     NetworkInfo getNetworkInfo(int networkType);
-
     NetworkInfo[] getAllNetworkInfo();
 
     LinkProperties getActiveLinkProperties();
-
     LinkProperties getLinkProperties(int networkType);
 
     boolean setRadios(boolean onOff);
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 5f5e11c..537750a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -74,7 +74,9 @@
         /** IP traffic not available. */
         DISCONNECTED,
         /** Attempt to connect failed. */
-        FAILED
+        FAILED,
+        /** Access to this network is blocked. */
+        BLOCKED
     }
 
     /**
@@ -96,6 +98,7 @@
         stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
         stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
         stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
+        stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
     }
 
     private int mNetworkType;
@@ -138,6 +141,23 @@
         mIsRoaming = false;
     }
 
+    /** {@hide} */
+    public NetworkInfo(NetworkInfo source) {
+        if (source != null) {
+            mNetworkType = source.mNetworkType;
+            mSubtype = source.mSubtype;
+            mTypeName = source.mTypeName;
+            mSubtypeName = source.mSubtypeName;
+            mState = source.mState;
+            mDetailedState = source.mDetailedState;
+            mReason = source.mReason;
+            mExtraInfo = source.mExtraInfo;
+            mIsFailover = source.mIsFailover;
+            mIsRoaming = source.mIsRoaming;
+            mIsAvailable = source.mIsAvailable;
+        }
+    }
+
     /**
      * Reports the type of network (currently mobile or Wi-Fi) to which the
      * info in this object pertains.
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
new file mode 100644
index 0000000..eb63c0d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011 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.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkStatsHistory.UID_ALL;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+
+@SmallTest
+public class NetworkStatsHistoryTest extends TestCase {
+    private static final String TAG = "NetworkStatsHistoryTest";
+
+    private static final long TEST_START = 1194220800000L;
+
+    private NetworkStatsHistory stats;
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (stats != null) {
+            assertConsistent(stats);
+        }
+    }
+
+    public void testRecordSingleBucket() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record data into narrow window to get single bucket
+        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
+
+        assertEquals(1, stats.bucketCount);
+        assertBucket(stats, 0, 1024L, 2048L);
+    }
+
+    public void testRecordEqualBuckets() throws Exception {
+        final long bucketDuration = HOUR_IN_MILLIS;
+        stats = buildStats(bucketDuration);
+
+        // split equally across two buckets
+        final long recordStart = TEST_START + (bucketDuration / 2);
+        stats.recordData(recordStart, recordStart + bucketDuration, 1024L, 128L);
+
+        assertEquals(2, stats.bucketCount);
+        assertBucket(stats, 0, 512L, 64L);
+        assertBucket(stats, 1, 512L, 64L);
+    }
+
+    public void testRecordTouchingBuckets() throws Exception {
+        final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // split almost completely into middle bucket, but with a few minutes
+        // overlap into neighboring buckets. total record is 20 minutes.
+        final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
+        final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
+        stats.recordData(recordStart, recordEnd, 1000L, 5000L);
+
+        assertEquals(3, stats.bucketCount);
+        // first bucket should have (1/20 of value)
+        assertBucket(stats, 0, 50L, 250L);
+        // second bucket should have (15/20 of value)
+        assertBucket(stats, 1, 750L, 3750L);
+        // final bucket should have (4/20 of value)
+        assertBucket(stats, 2, 200L, 1000L);
+    }
+
+    public void testRecordGapBuckets() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record some data today and next week with large gap
+        final long firstStart = TEST_START;
+        final long lastStart = TEST_START + WEEK_IN_MILLIS;
+        stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS, 128L, 256L);
+        stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS, 64L, 512L);
+
+        // we should have two buckets, far apart from each other
+        assertEquals(2, stats.bucketCount);
+        assertBucket(stats, 0, 128L, 256L);
+        assertBucket(stats, 1, 64L, 512L);
+
+        // now record something in middle, spread across two buckets
+        final long middleStart = TEST_START + DAY_IN_MILLIS;
+        final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
+        stats.recordData(middleStart, middleEnd, 2048L, 2048L);
+
+        // now should have four buckets, with new record in middle two buckets
+        assertEquals(4, stats.bucketCount);
+        assertBucket(stats, 0, 128L, 256L);
+        assertBucket(stats, 1, 1024L, 1024L);
+        assertBucket(stats, 2, 1024L, 1024L);
+        assertBucket(stats, 3, 64L, 512L);
+    }
+
+    public void testRecordOverlapBuckets() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record some data in one bucket, and another overlapping buckets
+        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 256L, 256L);
+        final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
+        stats.recordData(midStart, midStart + HOUR_IN_MILLIS, 1024L, 1024L);
+
+        // should have two buckets, with some data mixed together
+        assertEquals(2, stats.bucketCount);
+        assertBucket(stats, 0, 768L, 768L);
+        assertBucket(stats, 1, 512L, 512L);
+    }
+
+    public void testRemove() throws Exception {
+        final long BUCKET_SIZE = HOUR_IN_MILLIS;
+        stats = buildStats(BUCKET_SIZE);
+
+        // record some data across 24 buckets
+        stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
+        assertEquals(24, stats.bucketCount);
+
+        // try removing far before buckets; should be no change
+        stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+        assertEquals(24, stats.bucketCount);
+
+        // try removing just moments into first bucket; should be no change
+        // since that bucket contains data beyond the cutoff
+        stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+        assertEquals(24, stats.bucketCount);
+
+        // try removing single bucket
+        stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+        assertEquals(23, stats.bucketCount);
+
+        // try removing multiple buckets
+        stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+        assertEquals(20, stats.bucketCount);
+
+        // try removing all buckets
+        stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+        assertEquals(0, stats.bucketCount);
+    }
+
+    @Suppress
+    public void testFuzzing() throws Exception {
+        try {
+            // fuzzing with random events, looking for crashes
+            final Random r = new Random();
+            for (int i = 0; i < 500; i++) {
+                stats = buildStats(r.nextLong());
+                for (int j = 0; j < 10000; j++) {
+                    if (r.nextBoolean()) {
+                        // add range
+                        final long start = r.nextLong();
+                        final long end = start + r.nextInt();
+                        stats.recordData(start, end, r.nextLong(), r.nextLong());
+                    } else {
+                        // trim something
+                        stats.removeBucketsBefore(r.nextLong());
+                    }
+                }
+                assertConsistent(stats);
+            }
+        } catch (Throwable e) {
+            Log.e(TAG, String.valueOf(stats));
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static NetworkStatsHistory buildStats(long bucketSize) {
+        return new NetworkStatsHistory(TYPE_MOBILE, null, UID_ALL, bucketSize);
+    }
+
+    private static void assertConsistent(NetworkStatsHistory stats) {
+        // verify timestamps are monotonic
+        for (int i = 1; i < stats.bucketCount; i++) {
+            assertTrue(stats.bucketStart[i - 1] < stats.bucketStart[i]);
+        }
+    }
+
+    private static void assertBucket(NetworkStatsHistory stats, int index, long rx, long tx) {
+        assertEquals("unexpected rx", rx, stats.rx[index]);
+        assertEquals("unexpected tx", tx, stats.tx[index]);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
new file mode 100644
index 0000000..23eb9cf
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011 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 android.os.SystemClock;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class NetworkStatsTest extends TestCase {
+
+    private static final String TEST_IFACE = "test0";
+
+    public void testFindIndex() throws Exception {
+        final NetworkStats stats = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 3)
+                .addEntry(TEST_IFACE, 100, 1024, 0)
+                .addEntry(TEST_IFACE, 101, 0, 1024)
+                .addEntry(TEST_IFACE, 102, 1024, 1024).build();
+
+        assertEquals(2, stats.findIndex(TEST_IFACE, 102));
+        assertEquals(2, stats.findIndex(TEST_IFACE, 102));
+        assertEquals(0, stats.findIndex(TEST_IFACE, 100));
+        assertEquals(-1, stats.findIndex(TEST_IFACE, 6));
+    }
+
+    public void testSubtractIdenticalData() throws Exception {
+        final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+                .addEntry(TEST_IFACE, 100, 1024, 0)
+                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+
+        final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+                .addEntry(TEST_IFACE, 100, 1024, 0)
+                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+
+        final NetworkStats result = after.subtract(before, true);
+
+        // identical data should result in zero delta
+        assertEquals(0, result.rx[0]);
+        assertEquals(0, result.tx[0]);
+        assertEquals(0, result.rx[1]);
+        assertEquals(0, result.tx[1]);
+    }
+
+    public void testSubtractIdenticalRows() throws Exception {
+        final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+                .addEntry(TEST_IFACE, 100, 1024, 0)
+                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+
+        final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+                .addEntry(TEST_IFACE, 100, 1025, 2)
+                .addEntry(TEST_IFACE, 101, 3, 1028).build();
+
+        final NetworkStats result = after.subtract(before, true);
+
+        // expect delta between measurements
+        assertEquals(1, result.rx[0]);
+        assertEquals(2, result.tx[0]);
+        assertEquals(3, result.rx[1]);
+        assertEquals(4, result.tx[1]);
+    }
+
+    public void testSubtractNewRows() throws Exception {
+        final NetworkStats before = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 2)
+                .addEntry(TEST_IFACE, 100, 1024, 0)
+                .addEntry(TEST_IFACE, 101, 0, 1024).build();
+
+        final NetworkStats after = new NetworkStats.Builder(SystemClock.elapsedRealtime(), 3)
+                .addEntry(TEST_IFACE, 100, 1024, 0)
+                .addEntry(TEST_IFACE, 101, 0, 1024)
+                .addEntry(TEST_IFACE, 102, 1024, 1024).build();
+
+        final NetworkStats result = after.subtract(before, true);
+
+        // its okay to have new rows
+        assertEquals(0, result.rx[0]);
+        assertEquals(0, result.tx[0]);
+        assertEquals(0, result.rx[1]);
+        assertEquals(0, result.tx[1]);
+        assertEquals(1024, result.rx[2]);
+        assertEquals(1024, result.tx[2]);
+    }
+
+}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 9158d9e..b559fb9 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,6 +16,11 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
+import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
+
 import android.bluetooth.BluetoothTetheringDataTracker;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -26,11 +31,13 @@
 import android.net.DummyDataStateTracker;
 import android.net.EthernetDataTracker;
 import android.net.IConnectivityManager;
-import android.net.LinkAddress;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
 import android.net.LinkProperties;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
 import android.net.Proxy;
@@ -39,6 +46,7 @@
 import android.net.vpn.VpnManager;
 import android.net.wifi.WifiStateTracker;
 import android.os.Binder;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -53,22 +61,21 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.SparseIntArray;
 
 import com.android.internal.telephony.Phone;
 import com.android.server.connectivity.Tethering;
 
 import java.io.FileDescriptor;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.InetAddress;
-import java.net.Inet4Address;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.GregorianCalendar;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @hide
@@ -78,6 +85,8 @@
     private static final boolean DBG = true;
     private static final String TAG = "ConnectivityService";
 
+    private static final boolean LOGD_RULES = false;
+
     // how long to wait before switching back to a radio's default network
     private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
     // system property that can override the above value
@@ -91,6 +100,9 @@
     private Tethering mTethering;
     private boolean mTetheringConfigValid = false;
 
+    /** Currently active network rules by UID. */
+    private SparseIntArray mUidRules = new SparseIntArray();
+
     /**
      * Sometimes we want to refer to the individual network state
      * trackers separately, and sometimes we just want to treat them
@@ -128,6 +140,7 @@
     private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true);
 
     private INetworkManagementService mNetd;
+    private INetworkPolicyManager mPolicyManager;
 
     private static final int ENABLED  = 1;
     private static final int DISABLED = 0;
@@ -250,14 +263,8 @@
     }
     RadioAttributes[] mRadioAttributes;
 
-    public static synchronized ConnectivityService getInstance(Context context) {
-        if (sServiceInstance == null) {
-            sServiceInstance = new ConnectivityService(context);
-        }
-        return sServiceInstance;
-    }
-
-    private ConnectivityService(Context context) {
+    public ConnectivityService(
+            Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
         if (DBG) log("ConnectivityService starting up");
 
         HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
@@ -290,9 +297,19 @@
             loge("Error setting defaultDns using " + dns);
         }
 
-        mContext = context;
+        mContext = checkNotNull(context, "missing Context");
+        mNetd = checkNotNull(netd, "missing INetworkManagementService");
+        mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
 
-        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        try {
+            mPolicyManager.registerListener(mPolicyListener);
+        } catch (RemoteException e) {
+            // ouch, no rules updates means some processes may never get network
+            Slog.e(TAG, "unable to register INetworkPolicyListener", e);
+        }
+
+        final PowerManager powerManager = (PowerManager) context.getSystemService(
+                Context.POWER_SERVICE);
         mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_networkTransitionTimeout);
@@ -542,32 +559,92 @@
     }
 
     /**
+     * Check if UID is blocked from using the given {@link NetworkInfo}.
+     */
+    private boolean isNetworkBlocked(NetworkInfo info, int uid) {
+        synchronized (mUidRules) {
+            return isNetworkBlockedLocked(info, uid);
+        }
+    }
+
+    /**
+     * Check if UID is blocked from using the given {@link NetworkInfo}.
+     */
+    private boolean isNetworkBlockedLocked(NetworkInfo info, int uid) {
+        // TODO: expand definition of "paid" network to cover tethered or paid
+        // hotspot use cases.
+        final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI;
+        final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+
+        if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) {
+            return true;
+        }
+
+        // no restrictive rules; network is visible
+        return false;
+    }
+
+    /**
      * Return NetworkInfo for the active (i.e., connected) network interface.
      * It is assumed that at most one network is active at a time. If more
      * than one is active, it is indeterminate which will be returned.
      * @return the info for the active network, or {@code null} if none is
      * active
      */
+    @Override
     public NetworkInfo getActiveNetworkInfo() {
-        return getNetworkInfo(mActiveDefaultNetwork);
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        return getNetworkInfo(mActiveDefaultNetwork, uid);
     }
 
+    @Override
+    public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+        enforceConnectivityInternalPermission();
+        return getNetworkInfo(mActiveDefaultNetwork, uid);
+    }
+
+    @Override
     public NetworkInfo getNetworkInfo(int networkType) {
         enforceAccessPermission();
-        if (ConnectivityManager.isNetworkTypeValid(networkType)) {
-            NetworkStateTracker t = mNetTrackers[networkType];
-            if (t != null)
-                return t.getNetworkInfo();
-        }
-        return null;
+        final int uid = Binder.getCallingUid();
+        return getNetworkInfo(networkType, uid);
     }
 
+    private NetworkInfo getNetworkInfo(int networkType, int uid) {
+        NetworkInfo info = null;
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                info = tracker.getNetworkInfo();
+                if (isNetworkBlocked(info, uid)) {
+                    // network is blocked; clone and override state
+                    info = new NetworkInfo(info);
+                    info.setDetailedState(DetailedState.BLOCKED, null, null);
+                }
+            }
+        }
+        return info;
+    }
+
+    @Override
     public NetworkInfo[] getAllNetworkInfo() {
         enforceAccessPermission();
-        NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
+        final int uid = Binder.getCallingUid();
+        final NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
         int i = 0;
-        for (NetworkStateTracker t : mNetTrackers) {
-            if(t != null) result[i++] = t.getNetworkInfo();
+        synchronized (mUidRules) {
+            for (NetworkStateTracker tracker : mNetTrackers) {
+                if (tracker != null) {
+                    NetworkInfo info = tracker.getNetworkInfo();
+                    if (isNetworkBlockedLocked(info, uid)) {
+                        // network is blocked; clone and override state
+                        info = new NetworkInfo(info);
+                        info.setDetailedState(DetailedState.BLOCKED, null, null);
+                    }
+                    result[i++] = info;
+                }
+            }
         }
         return result;
     }
@@ -580,15 +657,19 @@
      * @return the ip properties for the active network, or {@code null} if
      * none is active
      */
+    @Override
     public LinkProperties getActiveLinkProperties() {
         return getLinkProperties(mActiveDefaultNetwork);
     }
 
+    @Override
     public LinkProperties getLinkProperties(int networkType) {
         enforceAccessPermission();
-        if (ConnectivityManager.isNetworkTypeValid(networkType)) {
-            NetworkStateTracker t = mNetTrackers[networkType];
-            if (t != null) return t.getLinkProperties();
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                return tracker.getLinkProperties();
+            }
         }
         return null;
     }
@@ -1033,6 +1114,30 @@
         }
     }
 
+    private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+        @Override
+        public void onRulesChanged(int uid, int uidRules) {
+            // only someone like NPMS should only be calling us
+            // TODO: create permission for modifying data policy
+            mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+
+            if (LOGD_RULES) {
+                Slog.d(TAG, "onRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+            }
+
+            synchronized (mUidRules) {
+                // skip update when we've already applied rules
+                final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+                if (oldRules == uidRules) return;
+
+                mUidRules.put(uid, uidRules);
+            }
+
+            // TODO: dispatch into NMS to push rules towards kernel module
+            // TODO: notify UID when it has requested targeted updates
+        }
+    };
+
     /**
      * @see ConnectivityManager#setMobileDataEnabled(boolean)
      */
@@ -1290,9 +1395,6 @@
     }
 
     void systemReady() {
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        mNetd = INetworkManagementService.Stub.asInterface(b);
-
         synchronized(this) {
             mSystemReady = true;
             if (mInitialBroadcast != null) {
@@ -1558,12 +1660,12 @@
 
             if (values.length == 6) {
               final String prefix = "/sys/kernel/ipv4/tcp_";
-                stringToFile(prefix + "rmem_min", values[0]);
-                stringToFile(prefix + "rmem_def", values[1]);
-                stringToFile(prefix + "rmem_max", values[2]);
-                stringToFile(prefix + "wmem_min", values[3]);
-                stringToFile(prefix + "wmem_def", values[4]);
-                stringToFile(prefix + "wmem_max", values[5]);
+                FileUtils.stringToFile(prefix + "rmem_min", values[0]);
+                FileUtils.stringToFile(prefix + "rmem_def", values[1]);
+                FileUtils.stringToFile(prefix + "rmem_max", values[2]);
+                FileUtils.stringToFile(prefix + "wmem_min", values[3]);
+                FileUtils.stringToFile(prefix + "wmem_def", values[4]);
+                FileUtils.stringToFile(prefix + "wmem_max", values[5]);
             } else {
                 loge("Invalid buffersize string: " + bufferSizes);
             }
@@ -1572,23 +1674,6 @@
         }
     }
 
-   /**
-     * Writes string to file. Basically same as "echo -n $string > $filename"
-     *
-     * @param filename
-     * @param string
-     * @throws IOException
-     */
-    private void stringToFile(String filename, String string) throws IOException {
-        FileWriter out = new FileWriter(filename);
-        try {
-            out.write(string);
-        } finally {
-            out.close();
-        }
-    }
-
-
     /**
      * Adjust the per-process dns entries (net.dns<x>.<pid>) based
      * on the highest priority active net which this process requested.
@@ -2278,4 +2363,11 @@
         }
         return networkType;
     }
+
+    private static <T> T checkNotNull(T value, String message) {
+        if (value == null) {
+            throw new NullPointerException(message);
+        }
+        return value;
+    }
 }