Merge "Proactively disable data when over policy limit."
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index a7b0037..83d1bda 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.DhcpInfoInternal;
-import android.net.LinkAddress;
 import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
@@ -30,7 +29,6 @@
 import android.os.Message;
 import android.util.Log;
 
-import java.net.InetAddress;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -184,11 +182,14 @@
         return -1;
     }
 
-    /**
-     * @param enabled
-     */
-    public void setDataEnable(boolean enabled) {
-        android.util.Log.d(TAG, "setDataEnabled: IGNORING enabled=" + enabled);
+    @Override
+    public void setUserDataEnable(boolean enabled) {
+        Log.w(TAG, "ignoring setUserDataEnable(" + enabled + ")");
+    }
+
+    @Override
+    public void setPolicyDataEnable(boolean enabled) {
+        Log.w(TAG, "ignoring setPolicyDataEnable(" + enabled + ")");
     }
 
     /**
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index e39725a..9f0f9cd 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -19,9 +19,6 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo;
-import android.net.LinkProperties;
 import android.util.Slog;
 
 /**
@@ -168,7 +165,14 @@
         return true;
     }
 
-    public void setDataEnable(boolean enabled) {
+    @Override
+    public void setUserDataEnable(boolean enabled) {
+        // ignored
+    }
+
+    @Override
+    public void setPolicyDataEnable(boolean enabled) {
+        // ignored
     }
 
     @Override
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index b035c51..21ecc22 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -17,15 +17,7 @@
 package android.net;
 
 import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.DhcpInfoInternal;
-import android.net.LinkAddress;
-import android.net.LinkCapabilities;
-import android.net.LinkProperties;
-import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkStateTracker;
-import android.net.NetworkUtils;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -34,7 +26,6 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
-import java.net.InetAddress;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -302,11 +293,14 @@
         return -1;
     }
 
-    /**
-     * @param enabled
-     */
-    public void setDataEnable(boolean enabled) {
-        Log.d(TAG, "setDataEnabled: IGNORING enabled=" + enabled);
+    @Override
+    public void setUserDataEnable(boolean enabled) {
+        Log.w(TAG, "ignoring setUserDataEnable(" + enabled + ")");
+    }
+
+    @Override
+    public void setPolicyDataEnable(boolean enabled) {
+        Log.w(TAG, "ignoring setPolicyDataEnable(" + enabled + ")");
     }
 
     /**
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 1b95b60..c9553c0 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -64,9 +64,11 @@
     boolean requestRouteToHostAddress(int networkType, in byte[] hostAddress);
 
     boolean getMobileDataEnabled();
-
     void setMobileDataEnabled(boolean enabled);
 
+    /** Policy control over specific {@link NetworkStateTracker}. */
+    void setPolicyDataEnable(int networkType, boolean enabled);
+
     int tether(String iface);
 
     int untether(String iface);
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 5501f38..5929cfb 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -16,19 +16,26 @@
 
 package android.net;
 
+import static com.android.internal.telephony.DataConnectionTracker.CMD_SET_POLICY_DATA_ENABLE;
+import static com.android.internal.telephony.DataConnectionTracker.CMD_SET_USER_DATA_ENABLE;
+import static com.android.internal.telephony.DataConnectionTracker.DISABLED;
+import static com.android.internal.telephony.DataConnectionTracker.ENABLED;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.NetworkInfo.DetailedState;
 import android.os.Bundle;
-import android.os.HandlerThread;
+import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
 import android.os.ServiceManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
 
 import com.android.internal.telephony.DataConnectionTracker;
 import com.android.internal.telephony.ITelephony;
@@ -36,13 +43,6 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.AsyncChannel;
 
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo;
-import android.net.LinkProperties;
-import android.telephony.TelephonyManager;
-import android.util.Slog;
-import android.text.TextUtils;
-
 /**
  * Track the state of mobile data connectivity. This is done by
  * receiving broadcast intents from the Phone process whenever
@@ -452,17 +452,22 @@
         return false;
     }
 
-    /**
-     * @param enabled
-     */
-    public void setDataEnable(boolean enabled) {
-        try {
-            if (DBG) log("setDataEnable: E enabled=" + enabled);
-            mDataConnectionTrackerAc.sendMessage(DataConnectionTracker.CMD_SET_DATA_ENABLE,
-                    enabled ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
-            if (VDBG) log("setDataEnable: X enabled=" + enabled);
-        } catch (Exception e) {
-            loge("setDataEnable: X mAc was null" + e);
+    @Override
+    public void setUserDataEnable(boolean enabled) {
+        if (DBG) log("setUserDataEnable: E enabled=" + enabled);
+        final AsyncChannel channel = mDataConnectionTrackerAc;
+        if (channel != null) {
+            channel.sendMessage(CMD_SET_USER_DATA_ENABLE, enabled ? ENABLED : DISABLED);
+        }
+        if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
+    }
+
+    @Override
+    public void setPolicyDataEnable(boolean enabled) {
+        if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")");
+        final AsyncChannel channel = mDataConnectionTrackerAc;
+        if (channel != null) {
+            channel.sendMessage(CMD_SET_POLICY_DATA_ENABLE, enabled ? ENABLED : DISABLED);
         }
     }
 
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index f53063d..1735592 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -136,9 +136,17 @@
     public boolean isAvailable();
 
     /**
-     * @param enabled
+     * User control of data connection through this network, typically persisted
+     * internally.
      */
-    public void setDataEnable(boolean enabled);
+    public void setUserDataEnable(boolean enabled);
+
+    /**
+     * Policy control of data connection through this network, typically not
+     * persisted internally. Usually used when {@link NetworkPolicy#limitBytes}
+     * is passed.
+     */
+    public void setPolicyDataEnable(boolean enabled);
 
     /**
      * -------------------------------------------------------------
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c80923d..8eaac8c 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3163,6 +3163,8 @@
     <string name="data_usage_4g_limit_title">4G data disabled</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
     <string name="data_usage_mobile_limit_title">Mobile data disabled</string>
+    <!-- Notification title when Wi-Fi data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
+    <string name="data_usage_wifi_limit_title">Wi-Fi data disabled</string>
     <!-- Notification body when data usage has exceeded limit threshold, and has been disabled. [CHAR LIMIT=32] -->
     <string name="data_usage_limit_body">Touch to enable</string>
 
@@ -3172,6 +3174,8 @@
     <string name="data_usage_4g_limit_snoozed_title">4G data limit exceeded</string>
     <!-- Notification title when mobile data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
     <string name="data_usage_mobile_limit_snoozed_title">Mobile data limit exceeded</string>
+    <!-- Notification title when Wi-Fi data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
+    <string name="data_usage_wifi_limit_snoozed_title">Wi-Fi data limit exceeded</string>
     <!-- Notification body when data usage has exceeded limit threshold. [CHAR LIMIT=32] -->
     <string name="data_usage_limit_snoozed_body"><xliff:g id="size" example="3.8GB">%s</xliff:g> over specified limit</string>
 
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 1341dd4..bfca851 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -72,7 +72,6 @@
 import com.android.internal.telephony.Phone;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
-
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
 
@@ -89,7 +88,6 @@
 import java.util.GregorianCalendar;
 import java.util.HashSet;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @hide
@@ -251,6 +249,12 @@
     private static final int EVENT_SEND_STICKY_BROADCAST_INTENT =
             MAX_NETWORK_STATE_TRACKER_EVENT + 12;
 
+    /**
+     * Used internally to
+     * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
+     */
+    private static final int EVENT_SET_POLICY_DATA_ENABLE = MAX_NETWORK_STATE_TRACKER_EVENT + 13;
+
     private Handler mHandler;
 
     // list of DeathRecipients used to make sure features are turned off when
@@ -1282,7 +1286,25 @@
             if (VDBG) {
                 log(mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
             }
-            mNetTrackers[ConnectivityManager.TYPE_MOBILE].setDataEnable(enabled);
+            mNetTrackers[ConnectivityManager.TYPE_MOBILE].setUserDataEnable(enabled);
+        }
+    }
+
+    @Override
+    public void setPolicyDataEnable(int networkType, boolean enabled) {
+        // only someone like NPMS should only be calling us
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        mHandler.sendMessage(mHandler.obtainMessage(
+                EVENT_SET_POLICY_DATA_ENABLE, networkType, (enabled ? ENABLED : DISABLED)));
+    }
+
+    private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                tracker.setPolicyDataEnable(enabled);
+            }
         }
     }
 
@@ -2263,6 +2285,11 @@
                     sendStickyBroadcast(intent);
                     break;
                 }
+                case EVENT_SET_POLICY_DATA_ENABLE: {
+                    final int networkType = msg.arg1;
+                    final boolean enabled = msg.arg2 == ENABLED;
+                    handleSetPolicyDataEnable(networkType, enabled);
+                }
             }
         }
     }
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 14d9665..84880f9 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -27,7 +27,10 @@
 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.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.SNOOZE_NEVER;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -40,8 +43,11 @@
 import static android.net.NetworkPolicyManager.dumpPolicy;
 import static android.net.NetworkPolicyManager.dumpRules;
 import static android.net.NetworkPolicyManager.isUidValidForPolicy;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
 import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
@@ -104,6 +110,7 @@
 import com.android.internal.R;
 import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 import com.google.android.collect.Sets;
@@ -129,8 +136,8 @@
 import libcore.io.IoUtils;
 
 /**
- * Service that maintains low-level network policy rules and collects usage
- * statistics to drive those rules.
+ * Service that maintains low-level network policy rules, using
+ * {@link NetworkStatsService} 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
@@ -195,6 +202,8 @@
     private volatile boolean mScreenOn;
     private volatile boolean mRestrictBackground;
 
+    private final boolean mSuppressDefaultPolicy;
+
     /** Defined network policies. */
     private HashMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = Maps.newHashMap();
     /** Currently active network rules for ifaces. */
@@ -210,6 +219,11 @@
     /** Set of over-limit templates that have been notified. */
     private HashSet<NetworkTemplate> mOverLimitNotified = Sets.newHashSet();
 
+    /** Set of currently active {@link Notification} tags. */
+    private HashSet<String> mActiveNotifs = Sets.newHashSet();
+    /** Current values from {@link #setPolicyDataEnable(int, boolean)}. */
+    private SparseBooleanArray mActiveNetworkEnabled = new SparseBooleanArray();
+
     /** Foreground at both UID and PID granularity. */
     private SparseBooleanArray mUidForeground = new SparseBooleanArray();
     private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
@@ -232,7 +246,7 @@
             IPowerManager powerManager, INetworkStatsService networkStats,
             INetworkManagementService networkManagement) {
         this(context, activityManager, powerManager, networkStats, networkManagement,
-                NtpTrustedTime.getInstance(context), getSystemDir());
+                NtpTrustedTime.getInstance(context), getSystemDir(), false);
     }
 
     private static File getSystemDir() {
@@ -241,8 +255,8 @@
 
     public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
             IPowerManager powerManager, INetworkStatsService networkStats,
-            INetworkManagementService networkManagement,
-            TrustedTime time, File systemDir) {
+            INetworkManagementService networkManagement, TrustedTime time, File systemDir,
+            boolean suppressDefaultPolicy) {
         mContext = checkNotNull(context, "missing context");
         mActivityManager = checkNotNull(activityManager, "missing activityManager");
         mPowerManager = checkNotNull(powerManager, "missing powerManager");
@@ -254,6 +268,8 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
 
+        mSuppressDefaultPolicy = suppressDefaultPolicy;
+
         mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
     }
 
@@ -408,6 +424,7 @@
             // READ_NETWORK_USAGE_HISTORY permission above.
 
             synchronized (mRulesLock) {
+                updateNetworkEnabledLocked();
                 updateNotificationsLocked();
             }
         }
@@ -446,6 +463,7 @@
                         Slog.w(TAG, "problem updating network stats");
                     }
 
+                    updateNetworkEnabledLocked();
                     updateNotificationsLocked();
                 }
             }
@@ -459,74 +477,70 @@
     private void updateNotificationsLocked() {
         if (LOGV) Slog.v(TAG, "updateNotificationsLocked()");
 
-        // try refreshing time source when stale
-        if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
-            mTime.forceRefresh();
-        }
-
-        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
-                : System.currentTimeMillis();
+        // keep track of previously active notifications
+        final HashSet<String> beforeNotifs = Sets.newHashSet();
+        beforeNotifs.addAll(mActiveNotifs);
+        mActiveNotifs.clear();
 
         // TODO: when switching to kernel notifications, compute next future
         // cycle boundary to recompute notifications.
 
         // examine stats for each active policy
-        for (NetworkPolicy policy : mNetworkRules.keySet()) {
+        final long currentTime = currentTimeMillis(true);
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+            // ignore policies that aren't relevant to user
+            if (!isTemplateRelevant(policy.template)) continue;
+
             final long start = computeLastCycleBoundary(currentTime, policy);
             final long end = currentTime;
 
-            final long totalBytes;
-            try {
-                final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
-                        policy.template, start, end);
-                final NetworkStats.Entry entry = stats.getValues(0, null);
-                totalBytes = entry.rxBytes + entry.txBytes;
-            } catch (RemoteException e) {
-                Slog.w(TAG, "problem reading summary for template " + policy.template);
-                continue;
-            }
+            final long totalBytes = getTotalBytes(policy.template, start, end);
+            if (totalBytes == UNKNOWN_BYTES) continue;
 
             if (policy.limitBytes != LIMIT_DISABLED && totalBytes >= policy.limitBytes) {
-                cancelNotification(policy, TYPE_WARNING);
-
                 if (policy.lastSnooze >= start) {
-                    cancelNotification(policy, TYPE_LIMIT);
                     enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes);
                 } else {
-                    cancelNotification(policy, TYPE_LIMIT_SNOOZED);
                     enqueueNotification(policy, TYPE_LIMIT, totalBytes);
                     notifyOverLimitLocked(policy.template);
                 }
 
             } else {
-                cancelNotification(policy, TYPE_LIMIT);
-                cancelNotification(policy, TYPE_LIMIT_SNOOZED);
                 notifyUnderLimitLocked(policy.template);
 
                 if (policy.warningBytes != WARNING_DISABLED && totalBytes >= policy.warningBytes) {
                     enqueueNotification(policy, TYPE_WARNING, totalBytes);
-                } else {
-                    cancelNotification(policy, TYPE_WARNING);
                 }
             }
         }
 
-        // clear notifications for non-active policies
-        for (NetworkPolicy policy : mNetworkPolicy.values()) {
-            if (!mNetworkRules.containsKey(policy)) {
-                cancelNotification(policy, TYPE_WARNING);
-                cancelNotification(policy, TYPE_LIMIT);
-                cancelNotification(policy, TYPE_LIMIT_SNOOZED);
-                notifyUnderLimitLocked(policy.template);
-            }
-        }
-
         // ongoing notification when restricting background data
         if (mRestrictBackground) {
             enqueueRestrictedNotification(TAG_ALLOW_BACKGROUND);
-        } else {
-            cancelNotification(TAG_ALLOW_BACKGROUND);
         }
+
+        // cancel stale notifications that we didn't renew above
+        for (String tag : beforeNotifs) {
+            if (!mActiveNotifs.contains(tag)) {
+                cancelNotification(tag);
+            }
+        }
+    }
+
+    /**
+     * Test if given {@link NetworkTemplate} is relevant to user based on
+     * current device state, such as when {@link #getActiveSubscriberId()}
+     * matches. This is regardless of data connection status.
+     */
+    private boolean isTemplateRelevant(NetworkTemplate template) {
+        switch (template.getMatchRule()) {
+            case MATCH_MOBILE_3G_LOWER:
+            case MATCH_MOBILE_4G:
+            case MATCH_MOBILE_ALL:
+                // mobile templates are relevant when subscriberid is active
+                return Objects.equal(getActiveSubscriberId(), template.getSubscriberId());
+        }
+        return true;
     }
 
     /**
@@ -590,9 +604,15 @@
                     case MATCH_MOBILE_4G:
                         title = res.getText(R.string.data_usage_4g_limit_title);
                         break;
-                    default:
+                    case MATCH_MOBILE_ALL:
                         title = res.getText(R.string.data_usage_mobile_limit_title);
                         break;
+                    case MATCH_WIFI:
+                        title = res.getText(R.string.data_usage_wifi_limit_title);
+                        break;
+                    default:
+                        title = null;
+                        break;
                 }
 
                 builder.setSmallIcon(com.android.internal.R.drawable.ic_menu_block);
@@ -618,9 +638,15 @@
                     case MATCH_MOBILE_4G:
                         title = res.getText(R.string.data_usage_4g_limit_snoozed_title);
                         break;
-                    default:
+                    case MATCH_MOBILE_ALL:
                         title = res.getText(R.string.data_usage_mobile_limit_snoozed_title);
                         break;
+                    case MATCH_WIFI:
+                        title = res.getText(R.string.data_usage_wifi_limit_snoozed_title);
+                        break;
+                    default:
+                        title = null;
+                        break;
                 }
 
                 builder.setSmallIcon(R.drawable.ic_menu_info_details);
@@ -641,6 +667,7 @@
             final int[] idReceived = new int[1];
             mNotifManager.enqueueNotificationWithTag(
                     packageName, tag, 0x0, builder.getNotification(), idReceived);
+            mActiveNotifs.add(tag);
         } catch (RemoteException e) {
             Slog.w(TAG, "problem during enqueueNotification: " + e);
         }
@@ -674,19 +701,12 @@
             final int[] idReceived = new int[1];
             mNotifManager.enqueueNotificationWithTag(packageName, tag,
                     0x0, builder.getNotification(), idReceived);
+            mActiveNotifs.add(tag);
         } catch (RemoteException e) {
             Slog.w(TAG, "problem during enqueueNotification: " + e);
         }
     }
 
-    /**
-     * Cancel any notification for combined {@link NetworkPolicy} and specific
-     * type, like {@link #TYPE_LIMIT}.
-     */
-    private void cancelNotification(NetworkPolicy policy, int type) {
-        cancelNotification(buildNotificationTag(policy, type));
-    }
-
     private void cancelNotification(String tag) {
         // TODO: move to NotificationManager once we can mock it
         try {
@@ -709,6 +729,7 @@
             // permission above.
             synchronized (mRulesLock) {
                 ensureActiveMobilePolicyLocked();
+                updateNetworkEnabledLocked();
                 updateNetworkRulesLocked();
                 updateNotificationsLocked();
             }
@@ -716,6 +737,65 @@
     };
 
     /**
+     * Proactively control network data connections when they exceed
+     * {@link NetworkPolicy#limitBytes}.
+     */
+    private void updateNetworkEnabledLocked() {
+        if (LOGV) Slog.v(TAG, "updateNetworkEnabledLocked()");
+
+        // TODO: reset any policy-disabled networks when any policy is removed
+        // completely, which is currently rare case.
+
+        final long currentTime = currentTimeMillis(true);
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+            // shortcut when policy has no limit
+            if (policy.limitBytes == LIMIT_DISABLED) {
+                setNetworkTemplateEnabled(policy.template, true);
+                continue;
+            }
+
+            final long start = computeLastCycleBoundary(currentTime, policy);
+            final long end = currentTime;
+
+            final long totalBytes = getTotalBytes(policy.template, start, end);
+            if (totalBytes == UNKNOWN_BYTES) continue;
+
+            // disable data connection when over limit and not snoozed
+            final boolean overLimit = policy.limitBytes != LIMIT_DISABLED
+                    && totalBytes > policy.limitBytes && policy.lastSnooze < start;
+            setNetworkTemplateEnabled(policy.template, !overLimit);
+        }
+    }
+
+    /**
+     * Control {@link IConnectivityManager#setPolicyDataEnable(int, boolean)}
+     * for the given {@link NetworkTemplate}.
+     */
+    private void setNetworkTemplateEnabled(NetworkTemplate template, boolean enabled) {
+        if (LOGD) Slog.d(TAG, "setting template=" + template + " enabled=" + enabled);
+        switch (template.getMatchRule()) {
+            case MATCH_MOBILE_3G_LOWER:
+            case MATCH_MOBILE_4G:
+            case MATCH_MOBILE_ALL:
+                // TODO: offer more granular control over radio states once
+                // 4965893 is available.
+                if (Objects.equal(getActiveSubscriberId(), template.getSubscriberId())) {
+                    setPolicyDataEnable(TYPE_MOBILE, enabled);
+                    setPolicyDataEnable(TYPE_WIMAX, enabled);
+                }
+                break;
+            case MATCH_WIFI:
+                setPolicyDataEnable(TYPE_WIFI, enabled);
+                break;
+            case MATCH_ETHERNET:
+                setPolicyDataEnable(TYPE_ETHERNET, enabled);
+                break;
+            default:
+                throw new IllegalArgumentException("unexpected template");
+        }
+    }
+
+    /**
      * Examine all connected {@link NetworkState}, looking for
      * {@link NetworkPolicy} that need to be enforced. When matches found, set
      * remaining quota based on usage cycle and historical stats.
@@ -763,34 +843,19 @@
             }
         }
 
-        // try refreshing time source when stale
-        if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
-            mTime.forceRefresh();
-        }
-
-        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
-                : System.currentTimeMillis();
-
         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.
+        final long currentTime = currentTimeMillis(true);
         for (NetworkPolicy policy : mNetworkRules.keySet()) {
             final String[] ifaces = mNetworkRules.get(policy);
 
             final long start = computeLastCycleBoundary(currentTime, policy);
             final long end = currentTime;
 
-            final NetworkStats stats;
-            final long total;
-            try {
-                stats = mNetworkStats.getSummaryForNetwork(policy.template, start, end);
-                final NetworkStats.Entry entry = stats.getValues(0, null);
-                total = entry.rxBytes + entry.txBytes;
-            } catch (RemoteException e) {
-                Slog.w(TAG, "problem reading summary for template " + policy.template);
-                continue;
-            }
+            final long totalBytes = getTotalBytes(policy.template, start, end);
+            if (totalBytes == UNKNOWN_BYTES) continue;
 
             if (LOGD) {
                 Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces "
@@ -798,11 +863,18 @@
             }
 
             final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
-            final boolean hasWarning = policy.warningBytes != WARNING_DISABLED;
-
             if (hasLimit) {
-                // remaining "quota" is based on usage in current cycle
-                final long quotaBytes = Math.max(0, policy.limitBytes - total);
+                final long quotaBytes;
+                if (policy.lastSnooze >= start) {
+                    // snoozing past quota, but we still need to restrict apps,
+                    // so push really high quota.
+                    quotaBytes = Long.MAX_VALUE;
+                } else {
+                    // remaining "quota" bytes are based on total usage in
+                    // current cycle. kernel doesn't like 0-byte rules, so we
+                    // set 1-byte quota and disable the radio later.
+                    quotaBytes = Math.max(1, policy.limitBytes - totalBytes);
+                }
 
                 if (ifaces.length > 1) {
                     // TODO: switch to shared quota once NMS supports
@@ -811,10 +883,8 @@
 
                 for (String iface : ifaces) {
                     removeInterfaceQuota(iface);
-                    if (quotaBytes > 0) {
-                        setInterfaceQuota(iface, quotaBytes);
-                        newMeteredIfaces.add(iface);
-                    }
+                    setInterfaceQuota(iface, quotaBytes);
+                    newMeteredIfaces.add(iface);
                 }
             }
         }
@@ -837,6 +907,8 @@
      */
     private void ensureActiveMobilePolicyLocked() {
         if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyLocked()");
+        if (mSuppressDefaultPolicy) return;
+
         final String subscriberId = getActiveSubscriberId();
         final NetworkIdentity probeIdent = new NetworkIdentity(
                 TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, false);
@@ -1073,6 +1145,7 @@
                 mNetworkPolicy.put(policy.template, policy);
             }
 
+            updateNetworkEnabledLocked();
             updateNetworkRulesLocked();
             updateNotificationsLocked();
             writePolicyLocked();
@@ -1093,14 +1166,7 @@
     public void snoozePolicy(NetworkTemplate template) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
-        // try refreshing time source when stale
-        if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
-            mTime.forceRefresh();
-        }
-
-        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
-                : System.currentTimeMillis();
-
+        final long currentTime = currentTimeMillis(true);
         synchronized (mRulesLock) {
             // find and snooze local policy that matches
             final NetworkPolicy policy = mNetworkPolicy.get(template);
@@ -1110,6 +1176,7 @@
 
             policy.lastSnooze = currentTime;
 
+            updateNetworkEnabledLocked();
             updateNetworkRulesLocked();
             updateNotificationsLocked();
             writePolicyLocked();
@@ -1173,22 +1240,14 @@
             return null;
         }
 
-        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
-                : System.currentTimeMillis();
+        final long currentTime = currentTimeMillis(false);
 
         final long start = computeLastCycleBoundary(currentTime, policy);
         final long end = currentTime;
 
         // find total bytes used under policy
-        long totalBytes = 0;
-        try {
-            final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
-                    policy.template, start, end);
-            final NetworkStats.Entry entry = stats.getValues(0, null);
-            totalBytes = entry.rxBytes + entry.txBytes;
-        } catch (RemoteException e) {
-            Slog.w(TAG, "problem reading summary for template " + policy.template);
-        }
+        final long totalBytes = getTotalBytes(policy.template, start, end);
+        if (totalBytes == UNKNOWN_BYTES) return null;
 
         // report soft and hard limits under policy
         final long softLimitBytes = policy.warningBytes != WARNING_DISABLED ? policy.warningBytes
@@ -1481,12 +1540,54 @@
         }
     }
 
+    /**
+     * Control {@link IConnectivityManager#setPolicyDataEnable(int, boolean)},
+     * dispatching only when actually changed.
+     */
+    private void setPolicyDataEnable(int networkType, boolean enabled) {
+        synchronized (mActiveNetworkEnabled) {
+            final boolean prevEnabled = mActiveNetworkEnabled.get(networkType, true);
+            if (prevEnabled == enabled) return;
+
+            try {
+                mConnManager.setPolicyDataEnable(networkType, enabled);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "problem setting network enabled", e);
+            }
+
+            mActiveNetworkEnabled.put(networkType, enabled);
+        }
+    }
+
     private String getActiveSubscriberId() {
         final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
                 Context.TELEPHONY_SERVICE);
         return telephony.getSubscriberId();
     }
 
+    private static final long UNKNOWN_BYTES = -1;
+
+    private long getTotalBytes(NetworkTemplate template, long start, long end) {
+        try {
+            final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
+                    template, start, end);
+            final NetworkStats.Entry entry = stats.getValues(0, null);
+            return entry.rxBytes + entry.txBytes;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "problem reading summary for template " + template);
+            return UNKNOWN_BYTES;
+        }
+    }
+
+    private long currentTimeMillis(boolean allowRefresh) {
+        // try refreshing time source when stale
+        if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE && allowRefresh) {
+            mTime.forceRefresh();
+        }
+
+        return mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
+    }
+
     private static Intent buildAllowBackgroundDataIntent() {
         return new Intent(ACTION_ALLOW_BACKGROUND);
     }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 845aa3f..f67d251 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -79,6 +79,7 @@
 import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.easymock.IAnswer;
+import org.easymock.IExpectationSetters;
 
 import java.io.File;
 import java.util.LinkedHashSet;
@@ -87,6 +88,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import libcore.io.IoUtils;
+
 /**
  * Tests for {@link NetworkPolicyManagerService}.
  */
@@ -117,6 +120,9 @@
 
     private Binder mStubBinder = new Binder();
 
+    private long mStartTime;
+    private long mElapsedRealtime;
+
     private static final int UID_A = android.os.Process.FIRST_APPLICATION_UID + 800;
     private static final int UID_B = android.os.Process.FIRST_APPLICATION_UID + 801;
 
@@ -128,6 +134,8 @@
     public void setUp() throws Exception {
         super.setUp();
 
+        setCurrentTimeMillis(TEST_START);
+
         // intercept various broadcasts, and pretend that uids have packages
         mServiceContext = new BroadcastInterceptingContext(getContext()) {
             @Override
@@ -160,8 +168,8 @@
         };
 
         mPolicyDir = getContext().getFilesDir();
-        for (File file : mPolicyDir.listFiles()) {
-            file.delete();
+        if (mPolicyDir.exists()) {
+            IoUtils.deleteContents(mPolicyDir);
         }
 
         mActivityManager = createMock(IActivityManager.class);
@@ -173,9 +181,8 @@
         mConnManager = createMock(IConnectivityManager.class);
         mNotifManager = createMock(INotificationManager.class);
 
-        mService = new NetworkPolicyManagerService(
-                mServiceContext, mActivityManager, mPowerManager, mStatsService,
-                mNetworkManager, mTime, mPolicyDir);
+        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, mPowerManager,
+                mStatsService, mNetworkManager, mTime, mPolicyDir, true);
         mService.bindConnectivityManager(mConnManager);
         mService.bindNotificationManager(mNotifManager);
 
@@ -198,7 +205,7 @@
 
         // expect to answer screen status during systemReady()
         expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
-        expectTime(System.currentTimeMillis());
+        expectCurrentTime();
 
         replay();
         mService.systemReady();
@@ -485,7 +492,6 @@
     }
 
     public void testNetworkPolicyAppliedCycleLastMonth() throws Exception {
-        long elapsedRealtime = 0;
         NetworkState[] state = null;
         NetworkStats stats = null;
         Future<Void> future;
@@ -494,11 +500,13 @@
         final long TIME_MAR_10 = 1173484800000L;
         final int CYCLE_DAY = 15;
 
+        setCurrentTimeMillis(TIME_MAR_10);
+
         // first, pretend that wifi network comes online. no policy active,
         // which means we shouldn't push limit to interface.
         state = new NetworkState[] { buildWifi() };
         expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-        expectTime(TIME_MAR_10 + elapsedRealtime);
+        expectCurrentTime();
         expectClearNotifications();
         future = expectMeteredIfacesChanged();
 
@@ -510,10 +518,10 @@
         // now change cycle to be on 15th, and test in early march, to verify we
         // pick cycle day in previous month.
         expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-        expectTime(TIME_MAR_10 + elapsedRealtime);
+        expectCurrentTime();
 
         // pretend that 512 bytes total have happened
-        stats = new NetworkStats(elapsedRealtime, 1)
+        stats = new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 256L, 2L, 256L, 2L);
         expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, TIME_MAR_10))
                 .andReturn(stats).atLeastOnce();
@@ -521,8 +529,6 @@
         // TODO: consider making strongly ordered mock
         expectRemoveInterfaceQuota(TEST_IFACE);
         expectSetInterfaceQuota(TEST_IFACE, 1536L);
-        expectRemoveInterfaceAlert(TEST_IFACE);
-        expectSetInterfaceAlert(TEST_IFACE, 512L);
 
         expectClearNotifications();
         future = expectMeteredIfacesChanged(TEST_IFACE);
@@ -558,8 +564,6 @@
     }
 
     public void testOverWarningLimitNotification() throws Exception {
-        long elapsedRealtime = 0;
-        long currentTime = 0;
         NetworkState[] state = null;
         NetworkStats stats = null;
         Future<Void> future;
@@ -569,14 +573,18 @@
         final long TIME_MAR_10 = 1173484800000L;
         final int CYCLE_DAY = 15;
 
+        setCurrentTimeMillis(TIME_MAR_10);
+
         // assign wifi policy
-        elapsedRealtime = 0;
-        currentTime = TIME_MAR_10 + elapsedRealtime;
         state = new NetworkState[] {};
+        stats = new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 0L, 0L, 0L, 0L);
 
         {
-            expectTime(currentTime);
+            expectCurrentTime();
             expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
+                    .andReturn(stats).atLeastOnce();
 
             expectClearNotifications();
             future = expectMeteredIfacesChanged();
@@ -589,22 +597,19 @@
         }
 
         // bring up wifi network
-        elapsedRealtime += MINUTE_IN_MILLIS;
-        currentTime = TIME_MAR_10 + elapsedRealtime;
-        stats = new NetworkStats(elapsedRealtime, 1)
-                .addIfaceValues(TEST_IFACE, 0L, 0L, 0L, 0L);
+        incrementCurrentTime(MINUTE_IN_MILLIS);
         state = new NetworkState[] { buildWifi() };
+        stats = new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 0L, 0L, 0L, 0L);
 
         {
-            expectTime(currentTime);
+            expectCurrentTime();
             expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
                     .andReturn(stats).atLeastOnce();
 
             expectRemoveInterfaceQuota(TEST_IFACE);
             expectSetInterfaceQuota(TEST_IFACE, 2048L);
-            expectRemoveInterfaceAlert(TEST_IFACE);
-            expectSetInterfaceAlert(TEST_IFACE, 1024L);
 
             expectClearNotifications();
             future = expectMeteredIfacesChanged(TEST_IFACE);
@@ -616,14 +621,13 @@
         }
 
         // go over warning, which should kick notification
-        elapsedRealtime += MINUTE_IN_MILLIS;
-        currentTime = TIME_MAR_10 + elapsedRealtime;
-        stats = new NetworkStats(elapsedRealtime, 1)
+        incrementCurrentTime(MINUTE_IN_MILLIS);
+        stats = new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 1536L, 15L, 0L, 0L);
 
         {
-            expectTime(currentTime);
-            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+            expectCurrentTime();
+            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
                     .andReturn(stats).atLeastOnce();
 
             expectForceUpdate();
@@ -637,15 +641,15 @@
         }
 
         // go over limit, which should kick notification and dialog
-        elapsedRealtime += MINUTE_IN_MILLIS;
-        currentTime = TIME_MAR_10 + elapsedRealtime;
-        stats = new NetworkStats(elapsedRealtime, 1)
+        incrementCurrentTime(MINUTE_IN_MILLIS);
+        stats = new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 5120L, 512L, 0L, 0L);
 
         {
-            expectTime(currentTime);
-            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+            expectCurrentTime();
+            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
                     .andReturn(stats).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, false).atLeastOnce();
 
             expectForceUpdate();
             expectClearNotifications();
@@ -658,21 +662,23 @@
         }
 
         // now snooze policy, which should remove quota
-        elapsedRealtime += MINUTE_IN_MILLIS;
-        currentTime = TIME_MAR_10 + elapsedRealtime;
+        incrementCurrentTime(MINUTE_IN_MILLIS);
 
         {
-            expectTime(currentTime);
+            expectCurrentTime();
             expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
-            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTime))
+            expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
                     .andReturn(stats).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, true).atLeastOnce();
 
+            // snoozed interface still has high quota so background data is
+            // still restricted.
             expectRemoveInterfaceQuota(TEST_IFACE);
-            expectRemoveInterfaceAlert(TEST_IFACE);
+            expectSetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
 
             expectClearNotifications();
             tag = expectEnqueueNotification();
-            future = expectMeteredIfacesChanged();
+            future = expectMeteredIfacesChanged(TEST_IFACE);
 
             replay();
             mService.snoozePolicy(sTemplateWifi);
@@ -700,10 +706,10 @@
         return new NetworkState(info, prop, null);
     }
 
-    private void expectTime(long currentTime) throws Exception {
+    private void expectCurrentTime() throws Exception {
         expect(mTime.forceRefresh()).andReturn(false).anyTimes();
         expect(mTime.hasCache()).andReturn(true).anyTimes();
-        expect(mTime.currentTimeMillis()).andReturn(currentTime).anyTimes();
+        expect(mTime.currentTimeMillis()).andReturn(currentTimeMillis()).anyTimes();
         expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
         expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
     }
@@ -770,6 +776,12 @@
         return future;
     }
 
+    private <T> IExpectationSetters<T> expectPolicyDataEnable(int type, boolean enabled)
+            throws Exception {
+        mConnManager.setPolicyDataEnable(type, enabled);
+        return expectLastCall();
+    }
+
     private static class FutureAnswer extends AbstractFuture<Void> implements IAnswer<Void> {
         @Override
         public Void get() throws InterruptedException, ExecutionException {
@@ -818,6 +830,23 @@
                 Integer.toString(expected), actualTag.substring(actualTag.lastIndexOf(':') + 1));
     }
 
+    private long getElapsedRealtime() {
+        return mElapsedRealtime;
+    }
+
+    private void setCurrentTimeMillis(long currentTimeMillis) {
+        mStartTime = currentTimeMillis;
+        mElapsedRealtime = 0L;
+    }
+
+    private long currentTimeMillis() {
+        return mStartTime + mElapsedRealtime;
+    }
+
+    private void incrementCurrentTime(long duration) {
+        mElapsedRealtime += duration;
+    }
+
     private void replay() {
         EasyMock.replay(mActivityManager, mPowerManager, mStatsService, mPolicyListener,
                 mNetworkManager, mTime, mConnManager, mNotifManager);
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 3bd78e04..42ea4f29 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -36,8 +36,8 @@
 import android.os.SystemProperties;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
-import android.telephony.ServiceState;
 import android.provider.Settings.SettingNotFoundException;
+import android.telephony.ServiceState;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -126,9 +126,10 @@
     protected static final int EVENT_RESTART_RADIO = BASE + 26;
     protected static final int EVENT_SET_INTERNAL_DATA_ENABLE = BASE + 27;
     protected static final int EVENT_RESET_DONE = BASE + 28;
-    public static final int CMD_SET_DATA_ENABLE = BASE + 29;
+    public static final int CMD_SET_USER_DATA_ENABLE = BASE + 29;
     public static final int EVENT_CLEAN_UP_ALL_CONNECTIONS = BASE + 30;
     public static final int CMD_SET_DEPENDENCY_MET = BASE + 31;
+    public static final int CMD_SET_POLICY_DATA_ENABLE = BASE + 32;
 
     /***** Constants *****/
 
@@ -153,6 +154,8 @@
     protected static final int APN_DELAY_MILLIS =
                                 SystemProperties.getInt("persist.radio.apn_delay", 5000);
 
+    protected Object mDataEnabledLock = new Object();
+
     // responds to the setInternalDataEnabled call - used internally to turn off data
     // for example during emergency calls
     protected boolean mInternalDataEnabled = true;
@@ -160,11 +163,12 @@
     // responds to public (user) API to enable/disable data use
     // independent of mInternalDataEnabled and requests for APN access
     // persisted
-    protected boolean mDataEnabled = true;
+    protected boolean mUserDataEnabled = true;
+    protected boolean mPolicyDataEnabled = true;
 
-    protected boolean[] dataEnabled = new boolean[APN_NUM_TYPES];
+    private boolean[] dataEnabled = new boolean[APN_NUM_TYPES];
 
-    protected int enabledCount = 0;
+    private int enabledCount = 0;
 
     /* Currently requested APN type (TODO: This should probably be a parameter not a member) */
     protected String mRequestedApnType = Phone.APN_TYPE_DEFAULT;
@@ -408,8 +412,8 @@
         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(INTENT_SET_FAIL_DATA_SETUP_COUNTER);
 
-        mDataEnabled = Settings.Secure.getInt(mPhone.getContext().getContentResolver(),
-                Settings.Secure.MOBILE_DATA, 1) == 1;
+        mUserDataEnabled = Settings.Secure.getInt(
+                mPhone.getContext().getContentResolver(), Settings.Secure.MOBILE_DATA, 1) == 1;
 
         // TODO: Why is this registering the phone as the receiver of the intent
         //       and not its own handler?
@@ -630,13 +634,12 @@
                 onResetDone((AsyncResult) msg.obj);
                 break;
             }
-            case CMD_SET_DATA_ENABLE: {
-                boolean enabled = (msg.arg1 == ENABLED) ? true : false;
-                if (DBG) log("CMD_SET_DATA_ENABLE enabled=" + enabled);
-                onSetDataEnabled(enabled);
+            case CMD_SET_USER_DATA_ENABLE: {
+                final boolean enabled = (msg.arg1 == ENABLED) ? true : false;
+                if (DBG) log("CMD_SET_USER_DATA_ENABLE enabled=" + enabled);
+                onSetUserDataEnabled(enabled);
                 break;
             }
-
             case CMD_SET_DEPENDENCY_MET: {
                 boolean met = (msg.arg1 == ENABLED) ? true : false;
                 if (DBG) log("CMD_SET_DEPENDENCY_MET met=" + met);
@@ -649,7 +652,11 @@
                 }
                 break;
             }
-
+            case CMD_SET_POLICY_DATA_ENABLE: {
+                final boolean enabled = (msg.arg1 == ENABLED) ? true : false;
+                onSetPolicyDataEnabled(enabled);
+                break;
+            }
             default:
                 Log.e("DATA", "Unidentified event msg=" + msg);
                 break;
@@ -662,8 +669,12 @@
      * @return {@code false} if data connectivity has been explicitly disabled,
      *         {@code true} otherwise.
      */
-    public synchronized boolean getAnyDataEnabled() {
-        boolean result = (mInternalDataEnabled && mDataEnabled && (enabledCount != 0));
+    public boolean getAnyDataEnabled() {
+        final boolean result;
+        synchronized (mDataEnabledLock) {
+            result = (mInternalDataEnabled && mUserDataEnabled && mPolicyDataEnabled
+                    && (enabledCount != 0));
+        }
         if (!result && DBG) log("getAnyDataEnabled " + result);
         return result;
     }
@@ -985,18 +996,18 @@
         return true;
     }
 
-    protected void onSetInternalDataEnabled(boolean enable) {
-        boolean prevEnabled = getAnyDataEnabled();
-        if (mInternalDataEnabled != enable) {
-            synchronized (this) {
-                mInternalDataEnabled = enable;
-            }
-            if (prevEnabled != getAnyDataEnabled()) {
-                if (!prevEnabled) {
-                    resetAllRetryCounts();
-                    onTrySetupData(Phone.REASON_DATA_ENABLED);
-                } else {
-                    cleanUpAllConnections(null);
+    protected void onSetInternalDataEnabled(boolean enabled) {
+        synchronized (mDataEnabledLock) {
+            final boolean prevEnabled = getAnyDataEnabled();
+            if (mInternalDataEnabled != enabled) {
+                mInternalDataEnabled = enabled;
+                if (prevEnabled != getAnyDataEnabled()) {
+                    if (!prevEnabled) {
+                        resetAllRetryCounts();
+                        onTrySetupData(Phone.REASON_DATA_ENABLED);
+                    } else {
+                        cleanUpAllConnections(null);
+                    }
                 }
             }
         }
@@ -1010,20 +1021,20 @@
 
     public abstract boolean isAnyActiveDataConnections();
 
-    protected void onSetDataEnabled(boolean enable) {
-        boolean prevEnabled = getAnyDataEnabled();
-        if (mDataEnabled != enable) {
-            synchronized (this) {
-                mDataEnabled = enable;
-            }
-            Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
-                    Settings.Secure.MOBILE_DATA, enable ? 1 : 0);
-            if (prevEnabled != getAnyDataEnabled()) {
-                if (!prevEnabled) {
-                    resetAllRetryCounts();
-                    onTrySetupData(Phone.REASON_DATA_ENABLED);
-                } else {
-                    onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+    protected void onSetUserDataEnabled(boolean enabled) {
+        synchronized (mDataEnabledLock) {
+            final boolean prevEnabled = getAnyDataEnabled();
+            if (mUserDataEnabled != enabled) {
+                mUserDataEnabled = enabled;
+                Settings.Secure.putInt(mPhone.getContext().getContentResolver(),
+                        Settings.Secure.MOBILE_DATA, enabled ? 1 : 0);
+                if (prevEnabled != getAnyDataEnabled()) {
+                    if (!prevEnabled) {
+                        resetAllRetryCounts();
+                        onTrySetupData(Phone.REASON_DATA_ENABLED);
+                    } else {
+                        onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+                    }
                 }
             }
         }
@@ -1032,6 +1043,23 @@
     protected void onSetDependencyMet(String apnType, boolean met) {
     }
 
+    protected void onSetPolicyDataEnabled(boolean enabled) {
+        synchronized (mDataEnabledLock) {
+            final boolean prevEnabled = getAnyDataEnabled();
+            if (mPolicyDataEnabled != enabled) {
+                mPolicyDataEnabled = enabled;
+                if (prevEnabled != getAnyDataEnabled()) {
+                    if (!prevEnabled) {
+                        resetAllRetryCounts();
+                        onTrySetupData(Phone.REASON_DATA_ENABLED);
+                    } else {
+                        onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+                    }
+                }
+            }
+        }
+    }
+
     protected String getReryConfig(boolean forDefault) {
         int rt = mPhone.getServiceState().getRadioTechnology();
 
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index 800615c..1a077d00e 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -175,6 +175,11 @@
 
     @Override
     protected boolean isDataAllowed() {
+        final boolean internalDataEnabled;
+        synchronized (mDataEnabledLock) {
+            internalDataEnabled = mInternalDataEnabled;
+        }
+
         int psState = mCdmaPhone.mSST.getCurrentDataConnectionState();
         boolean roaming = (mPhone.getServiceState().getRoaming() && !getDataOnRoamingEnabled());
         boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState();
@@ -187,7 +192,7 @@
                     (mCdmaPhone.mSST.isConcurrentVoiceAndDataAllowed() ||
                             mPhone.getState() == Phone.State.IDLE) &&
                     !roaming &&
-                    mInternalDataEnabled &&
+                    internalDataEnabled &&
                     desiredPowerState &&
                     !mPendingRestartRadio &&
                     !mCdmaPhone.needsOtaServiceProvisioning();
@@ -205,7 +210,7 @@
                 reason += " - concurrentVoiceAndData not allowed and state= " + mPhone.getState();
             }
             if (roaming) reason += " - Roaming";
-            if (!mInternalDataEnabled) reason += " - mInternalDataEnabled= false";
+            if (!internalDataEnabled) reason += " - mInternalDataEnabled= false";
             if (!desiredPowerState) reason += " - desiredPowerState= false";
             if (mPendingRestartRadio) reason += " - mPendingRestartRadio= true";
             if (mCdmaPhone.needsOtaServiceProvisioning()) reason += " - needs Provisioning";
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 3236901..c8671c1 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -27,14 +27,14 @@
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
 import android.net.LinkProperties.CompareResult;
+import android.net.NetworkConfig;
 import android.net.NetworkUtils;
 import android.net.ProxyProperties;
 import android.net.TrafficStats;
 import android.net.Uri;
-import android.net.LinkCapabilities;
-import android.net.LinkProperties;
-import android.net.NetworkConfig;
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemClock;
@@ -49,34 +49,27 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
-import android.preference.PreferenceManager;
 
-import com.android.internal.R;
 import com.android.internal.telephony.ApnContext;
 import com.android.internal.telephony.ApnSetting;
 import com.android.internal.telephony.DataCallState;
 import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnection.FailCause;
 import com.android.internal.telephony.DataConnection.UpdateLinkPropertyResult;
 import com.android.internal.telephony.DataConnectionAc;
 import com.android.internal.telephony.DataConnectionTracker;
+import com.android.internal.telephony.EventLogTags;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
-import com.android.internal.telephony.RetryManager;
-import com.android.internal.telephony.EventLogTags;
-import com.android.internal.telephony.DataConnection.FailCause;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.RetryManager;
 import com.android.internal.util.AsyncChannel;
 
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.List;
-import java.util.Map;
 import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * {@hide}
@@ -523,16 +516,18 @@
      * {@code true} otherwise.
      */
     @Override
-    public synchronized boolean getAnyDataEnabled() {
-        if (!(mInternalDataEnabled && mDataEnabled)) return false;
-        for (ApnContext apnContext : mApnContexts.values()) {
-            // Make sure we dont have a context that going down
-            // and is explicitly disabled.
-            if (isDataAllowed(apnContext)) {
-                return true;
+    public boolean getAnyDataEnabled() {
+        synchronized (mDataEnabledLock) {
+            if (!(mInternalDataEnabled && mUserDataEnabled && mPolicyDataEnabled)) return false;
+            for (ApnContext apnContext : mApnContexts.values()) {
+                // Make sure we dont have a context that going down
+                // and is explicitly disabled.
+                if (isDataAllowed(apnContext)) {
+                    return true;
+                }
             }
+            return false;
         }
-        return false;
     }
 
     private boolean isDataAllowed(ApnContext apnContext) {
@@ -570,6 +565,11 @@
 
     @Override
     protected boolean isDataAllowed() {
+        final boolean internalDataEnabled;
+        synchronized (mDataEnabledLock) {
+            internalDataEnabled = mInternalDataEnabled;
+        }
+
         int gprsState = mPhone.getServiceStateTracker().getCurrentDataConnectionState();
         boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
 
@@ -577,7 +577,7 @@
                     (gprsState == ServiceState.STATE_IN_SERVICE || mAutoAttachOnCreation) &&
                     mPhone.mIccRecords.getRecordsLoaded() &&
                     mPhone.getState() == Phone.State.IDLE &&
-                    mInternalDataEnabled &&
+                    internalDataEnabled &&
                     (!mPhone.getServiceState().getRoaming() || getDataOnRoamingEnabled()) &&
                     !mIsPsRestricted &&
                     desiredPowerState;
@@ -590,7 +590,7 @@
             if (mPhone.getState() != Phone.State.IDLE) {
                 reason += " - PhoneState= " + mPhone.getState();
             }
-            if (!mInternalDataEnabled) reason += " - mInternalDataEnabled= false";
+            if (!internalDataEnabled) reason += " - mInternalDataEnabled= false";
             if (mPhone.getServiceState().getRoaming() && !getDataOnRoamingEnabled()) {
                 reason += " - Roaming and data roaming not enabled";
             }
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index c20c716..956c3f2 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -16,23 +16,20 @@
 
 package android.net.wifi;
 
-
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.ConnectivityManager;
 import android.net.LinkCapabilities;
-import android.net.NetworkInfo;
 import android.net.LinkProperties;
+import android.net.NetworkInfo;
 import android.net.NetworkStateTracker;
 import android.net.wifi.p2p.WifiP2pManager;
 import android.os.Handler;
 import android.os.Message;
+import android.util.Slog;
 
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Track the state of wifi for connectivity service.
@@ -44,6 +41,8 @@
     private static final String NETWORKTYPE = "WIFI";
     private static final String TAG = "WifiStateTracker";
 
+    private static final boolean LOGV = true;
+
     private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
     private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
     private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
@@ -135,11 +134,14 @@
         return mNetworkInfo.isAvailable();
     }
 
-    /**
-     * @param enabled
-     */
-    public void setDataEnable(boolean enabled) {
-        android.util.Log.d(TAG, "setDataEnabled: IGNORING enabled=" + enabled);
+    @Override
+    public void setUserDataEnable(boolean enabled) {
+        Slog.w(TAG, "ignoring setUserDataEnable(" + enabled + ")");
+    }
+
+    @Override
+    public void setPolicyDataEnable(boolean enabled) {
+        Slog.w(TAG, "ignoring setPolicyDataEnable(" + enabled + ")");
     }
 
     /**