Policy and rules work for ConnectivityManager.

Teach ConnectivityManager about UID-specific rules derived from policy,
such as rejecting network traffic on "paid" interfaces.  Calls that
return NetworkInfo now filter based on any REJECT rules in effect for
the calling UID.  (Added uid parameter if callers that still want all
interfaces.)

Changed NetworkPolicyManager to derive rules based on current policy
combined with PowerManager and ActivityManager status, which it passes
to ConnectivityService for eventual enforcement through netd.  When
rules change the usability of a NetworkInfo for a specific UID, it also
dispatches CONNECTIVITY_ACTION broadcasts to that UID.  Combined paid
and background policy together to match current working definition.

Change-Id: I797ea49439fcc487cfe2cbc16703d4b91ceb9af6
diff --git a/Android.mk b/Android.mk
index c838600..270ee2e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -110,6 +110,7 @@
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/INetworkManagementEventObserver.aidl \
 	core/java/android/net/IThrottleManager.aidl \
+	core/java/android/net/INetworkPolicyListener.aidl \
 	core/java/android/net/INetworkPolicyManager.aidl \
 	core/java/android/nfc/ILlcpConnectionlessSocket.aidl \
 	core/java/android/nfc/ILlcpServiceSocket.aidl \
diff --git a/api/current.txt b/api/current.txt
index 9e87e9c..a1d4c4a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11041,6 +11041,7 @@
     method public static android.net.NetworkInfo.DetailedState valueOf(java.lang.String);
     method public static final android.net.NetworkInfo.DetailedState[] values();
     enum_constant public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
+    enum_constant public static final android.net.NetworkInfo.DetailedState BLOCKED;
     enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTED;
     enum_constant public static final android.net.NetworkInfo.DetailedState CONNECTING;
     enum_constant public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
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/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
new file mode 100644
index 0000000..9230151
--- /dev/null
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/** {@hide} */
+oneway interface INetworkPolicyListener {
+
+    void onRulesChanged(int uid, int uidRules);
+
+}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index d9351ee..24f2283 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.net.INetworkPolicyListener;
+
 /**
  * Interface that creates and modifies network policy rules.
  *
@@ -26,6 +28,9 @@
     void setUidPolicy(int uid, int policy);
     int getUidPolicy(int uid);
 
+    void registerListener(INetworkPolicyListener listener);
+    void unregisterListener(INetworkPolicyListener listener);
+
     // TODO: build API to surface stats details for settings UI
 
 }
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/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 1913aa7..0851b12 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -28,12 +28,13 @@
 
     /** No specific network policy, use system default. */
     public static final int POLICY_NONE = 0x0;
-    /** Reject network usage when application in background. */
-    public static final int POLICY_REJECT_BACKGROUND = 0x1;
-    /** Reject network usage on paid network connections. */
-    public static final int POLICY_REJECT_PAID = 0x2;
-    /** Application should conserve data. */
-    public static final int POLICY_CONSERVE_DATA = 0x4;
+    /** Reject network usage on paid networks when application in background. */
+    public static final int POLICY_REJECT_PAID_BACKGROUND = 0x1;
+
+    /** All network traffic should be allowed. */
+    public static final int RULE_ALLOW_ALL = 0x0;
+    /** Reject traffic on paid networks. */
+    public static final int RULE_REJECT_PAID = 0x1;
 
     private INetworkPolicyManager mService;
 
@@ -51,9 +52,8 @@
     /**
      * Set policy flags for specific UID.
      *
-     * @param policy {@link #POLICY_NONE} or combination of
-     *            {@link #POLICY_REJECT_BACKGROUND}, {@link #POLICY_REJECT_PAID},
-     *            or {@link #POLICY_CONSERVE_DATA}.
+     * @param policy {@link #POLICY_NONE} or combination of flags like
+     *            {@link #POLICY_REJECT_PAID_BACKGROUND}.
      */
     public void setUidPolicy(int uid, int policy) {
         try {
@@ -69,5 +69,4 @@
             return POLICY_NONE;
         }
     }
-
 }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index a564c2d..dd76eb8 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;
@@ -54,6 +61,7 @@
 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;
@@ -62,13 +70,12 @@
 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);
@@ -536,32 +553,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;
     }
@@ -574,15 +651,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;
     }
@@ -1027,6 +1108,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)
      */
@@ -1284,9 +1389,6 @@
     }
 
     void systemReady() {
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        mNetd = INetworkManagementService.Stub.asInterface(b);
-
         synchronized(this) {
             mSystemReady = true;
             if (mInitialBroadcast != null) {
@@ -2255,4 +2357,11 @@
         }
         return networkType;
     }
+
+    private static <T> T checkNotNull(T value, String message) {
+        if (value == null) {
+            throw new NullPointerException(message);
+        }
+        return value;
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5355d44..4cd601f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,19 +16,6 @@
 
 package com.android.server;
 
-import com.android.server.accessibility.AccessibilityManagerService;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.usb.UsbService;
-import com.android.server.wm.WindowManagerService;
-import com.android.internal.app.ShutdownThread;
-import com.android.internal.os.BinderInternal;
-import com.android.internal.os.SamplingProfilerIntegration;
-
-import dalvik.system.VMRuntime;
-import dalvik.system.Zygote;
-
 import android.accounts.AccountManagerService;
 import android.app.ActivityManagerNative;
 import android.bluetooth.BluetoothAdapter;
@@ -41,25 +28,34 @@
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.media.AudioService;
-import android.os.Build;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.provider.Contacts.People;
 import android.provider.Settings;
 import android.server.BluetoothA2dpService;
 import android.server.BluetoothService;
 import android.server.search.SearchManagerService;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
-import android.util.Log;
 import android.util.Slog;
-import android.view.Display;
 import android.view.WindowManager;
 
+import com.android.internal.app.ShutdownThread;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.net.NetworkPolicyManagerService;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.usb.UsbService;
+import com.android.server.wm.WindowManagerService;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.Zygote;
+
 import java.io.File;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -120,6 +116,7 @@
         LightsService lights = null;
         PowerManagerService power = null;
         BatteryService battery = null;
+        NetworkManagementService networkManagement = null;
         NetworkPolicyManagerService networkPolicy = null;
         ConnectivityService connectivity = null;
         IPackageManager pm = null;
@@ -294,16 +291,15 @@
 
             try {
                 Slog.i(TAG, "NetworkManagement Service");
-                ServiceManager.addService(
-                        Context.NETWORKMANAGEMENT_SERVICE,
-                        NetworkManagementService.create(context));
+                networkManagement = NetworkManagementService.create(context);
+                ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting NetworkManagement Service", e);
             }
 
             try {
                 Slog.i(TAG, "Connectivity Service");
-                connectivity = ConnectivityService.getInstance(context);
+                connectivity = new ConnectivityService(context, networkManagement, networkPolicy);
                 ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
             } catch (Throwable e) {
                 Slog.e(TAG, "Failure starting Connectivity Service", e);
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index d083d01..0f31f81 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -19,8 +19,9 @@
 import static android.Manifest.permission.MANAGE_APP_TOKENS;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_BACKGROUND;
-import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_PAID;
 
 import android.app.IActivityManager;
 import android.app.IProcessObserver;
@@ -28,8 +29,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.os.IPowerManager;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Slog;
@@ -40,6 +44,10 @@
 /**
  * Service that maintains low-level network policy rules and collects usage
  * statistics to drive those rules.
+ * <p>
+ * Derives active rules by combining a given policy with other system status,
+ * and delivers to listeners, such as {@link ConnectivityManager}, for
+ * enforcement.
  */
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
     private static final String TAG = "NetworkPolicy";
@@ -51,19 +59,27 @@
 
     private Object mRulesLock = new Object();
 
-    private boolean mScreenOn = false;
+    private boolean mScreenOn;
 
     /** Current network policy for each UID. */
     private SparseIntArray mUidPolicy = new SparseIntArray();
+    /** Current derived network rules for each UID. */
+    private SparseIntArray mUidRules = new SparseIntArray();
 
     /** Foreground at both UID and PID granularity. */
     private SparseBooleanArray mUidForeground = new SparseBooleanArray();
     private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
             SparseBooleanArray>();
 
+    private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
+            INetworkPolicyListener>();
+
     // TODO: periodically poll network stats and write to disk
     // TODO: save/restore policy information from disk
 
+    // TODO: keep whitelist of system-critical services that should never have
+    // rules enforced, such as system, phone, and radio UIDs.
+
     public NetworkPolicyManagerService(
             Context context, IActivityManager activityManager, IPowerManager powerManager) {
         mContext = checkNotNull(context, "missing context");
@@ -158,12 +174,17 @@
 
     @Override
     public void setUidPolicy(int uid, int policy) {
+        // TODO: create permission for modifying data policy
         mContext.enforceCallingOrSelfPermission(
                 UPDATE_DEVICE_STATS, "requires UPDATE_DEVICE_STATS permission");
 
+        final int oldPolicy;
         synchronized (mRulesLock) {
+            oldPolicy = getUidPolicy(uid);
             mUidPolicy.put(uid, policy);
         }
+
+        // TODO: consider dispatching BACKGROUND_DATA_SETTING broadcast
     }
 
     @Override
@@ -173,6 +194,36 @@
         }
     }
 
+    @Override
+    public void registerListener(INetworkPolicyListener listener) {
+        mListeners.register(listener);
+
+        synchronized (mRulesLock) {
+            // dispatch any existing rules to new listeners
+            final int size = mUidRules.size();
+            for (int i = 0; i < size; i++) {
+                final int uid = mUidRules.keyAt(i);
+                final int uidRules = mUidRules.valueAt(i);
+                if (uidRules != RULE_ALLOW_ALL) {
+                    try {
+                        listener.onRulesChanged(uid, uidRules);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void unregisterListener(INetworkPolicyListener listener) {
+        mListeners.unregister(listener);
+    }
+
+    private boolean isUidForegroundL(int uid) {
+        // only really in foreground when screen is also on
+        return mUidForeground.get(uid, false) && mScreenOn;
+    }
+
     /**
      * Foreground for PID changed; recompute foreground at UID level. If
      * changed, will trigger {@link #updateRulesForUidL(int)}.
@@ -223,22 +274,33 @@
     }
 
     private void updateRulesForUidL(int uid) {
-        // only really in foreground when screen on
-        final boolean uidForeground = mUidForeground.get(uid, false) && mScreenOn;
         final int uidPolicy = getUidPolicy(uid);
+        final boolean uidForeground = isUidForegroundL(uid);
 
-        if (LOGD) {
-            Log.d(TAG, "updateRulesForUid(uid=" + uid + ") found foreground=" + uidForeground
-                    + " and policy=" + uidPolicy);
+        // derive active rules based on policy and active state
+        int uidRules = RULE_ALLOW_ALL;
+        if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) {
+            // uid in background, and policy says to block paid data
+            uidRules = RULE_REJECT_PAID;
         }
 
-        if (!uidForeground && (uidPolicy & POLICY_REJECT_BACKGROUND) != 0) {
-            // TODO: build updated rules and push to NMS
-        } else if ((uidPolicy & POLICY_REJECT_PAID) != 0) {
-            // TODO: build updated rules and push to NMS
-        } else {
-            // TODO: build updated rules and push to NMS
+        // TODO: only dispatch when rules actually change
+
+        // record rule locally to dispatch to new listeners
+        mUidRules.put(uid, uidRules);
+
+        // dispatch changed rule to existing listeners
+        final int length = mListeners.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+            if (listener != null) {
+                try {
+                    listener.onRulesChanged(uid, uidRules);
+                } catch (RemoteException e) {
+                }
+            }
         }
+        mListeners.finishBroadcast();
     }
 
     private static <T> T checkNotNull(T value, String message) {