Revise data limit notifs, watch kernel alerts.

Teach NetworkPolicy limits to "snooze" when requested by user, and
notify with both dialog and notification.  Register for network alerts
through NMS to trigger updates immediately instead of waiting for
next stats update.

Enforce that all NetworkPolicy are unique on a template basis, and
move SCREEN_ON/OFF broadcasts to background thread.  Launch SystemUI
and Settings directly instead of using actions, and include full
NetworkTemplate in extras.

Tests to verify notification and snooze behavior.

Bug: 5057979, 5023579, 4723336, 5045721
Change-Id: I03724beff94a7c0547cb5220431ba8d4cd44d077
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index a16f748..39d2b1c 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -125,10 +125,14 @@
     private Thread mThread;
     private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
 
+    // TODO: replace with RemoteCallbackList
     private ArrayList<INetworkManagementEventObserver> mObservers;
 
+    private Object mQuotaLock = new Object();
     /** Set of interfaces with active quotas. */
-    private HashSet<String> mInterfaceQuota = Sets.newHashSet();
+    private HashSet<String> mActiveQuotaIfaces = Sets.newHashSet();
+    /** Set of interfaces with active alerts. */
+    private HashSet<String> mActiveAlertIfaces = Sets.newHashSet();
     /** Set of UIDs with active reject rules. */
     private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();
 
@@ -1058,26 +1062,25 @@
     }
 
     @Override
-    public void setInterfaceQuota(String iface, long quota) {
+    public void setInterfaceQuota(String iface, long quotaBytes) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
         // silently discard when control disabled
         // TODO: eventually migrate to be always enabled
         if (!mBandwidthControlEnabled) return;
 
-        synchronized (mInterfaceQuota) {
-            if (mInterfaceQuota.contains(iface)) {
-                // TODO: eventually consider throwing
-                return;
+        synchronized (mQuotaLock) {
+            if (mActiveQuotaIfaces.contains(iface)) {
+                throw new IllegalStateException("iface " + iface + " already has quota");
             }
 
             final StringBuilder command = new StringBuilder();
-            command.append("bandwidth setiquota ").append(iface).append(" ").append(quota);
+            command.append("bandwidth setiquota ").append(iface).append(" ").append(quotaBytes);
 
             try {
-                // TODO: add support for quota shared across interfaces
+                // TODO: support quota shared across interfaces
                 mConnector.doCommand(command.toString());
-                mInterfaceQuota.add(iface);
+                mActiveQuotaIfaces.add(iface);
             } catch (NativeDaemonConnectorException e) {
                 throw new IllegalStateException("Error communicating to native daemon", e);
             }
@@ -1092,8 +1095,8 @@
         // TODO: eventually migrate to be always enabled
         if (!mBandwidthControlEnabled) return;
 
-        synchronized (mInterfaceQuota) {
-            if (!mInterfaceQuota.contains(iface)) {
+        synchronized (mQuotaLock) {
+            if (!mActiveQuotaIfaces.contains(iface)) {
                 // TODO: eventually consider throwing
                 return;
             }
@@ -1102,9 +1105,10 @@
             command.append("bandwidth removeiquota ").append(iface);
 
             try {
-                // TODO: add support for quota shared across interfaces
+                // TODO: support quota shared across interfaces
                 mConnector.doCommand(command.toString());
-                mInterfaceQuota.remove(iface);
+                mActiveQuotaIfaces.remove(iface);
+                mActiveAlertIfaces.remove(iface);
             } catch (NativeDaemonConnectorException e) {
                 throw new IllegalStateException("Error communicating to native daemon", e);
             }
@@ -1112,6 +1116,83 @@
     }
 
     @Override
+    public void setInterfaceAlert(String iface, long alertBytes) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        // quick sanity check
+        if (!mActiveQuotaIfaces.contains(iface)) {
+            throw new IllegalStateException("setting alert requires existing quota on iface");
+        }
+
+        synchronized (mQuotaLock) {
+            if (mActiveAlertIfaces.contains(iface)) {
+                throw new IllegalStateException("iface " + iface + " already has alert");
+            }
+
+            final StringBuilder command = new StringBuilder();
+            command.append("bandwidth setinterfacealert ").append(iface).append(" ").append(
+                    alertBytes);
+
+            try {
+                // TODO: support alert shared across interfaces
+                mConnector.doCommand(command.toString());
+                mActiveAlertIfaces.add(iface);
+            } catch (NativeDaemonConnectorException e) {
+                throw new IllegalStateException("Error communicating to native daemon", e);
+            }
+        }
+    }
+
+    @Override
+    public void removeInterfaceAlert(String iface) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        synchronized (mQuotaLock) {
+            if (!mActiveAlertIfaces.contains(iface)) {
+                // TODO: eventually consider throwing
+                return;
+            }
+
+            final StringBuilder command = new StringBuilder();
+            command.append("bandwidth removeinterfacealert ").append(iface);
+
+            try {
+                // TODO: support alert shared across interfaces
+                mConnector.doCommand(command.toString());
+                mActiveAlertIfaces.remove(iface);
+            } catch (NativeDaemonConnectorException e) {
+                throw new IllegalStateException("Error communicating to native daemon", e);
+            }
+        }
+    }
+
+    @Override
+    public void setGlobalAlert(long alertBytes) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        final StringBuilder command = new StringBuilder();
+        command.append("bandwidth setglobalalert ").append(alertBytes);
+
+        try {
+            mConnector.doCommand(command.toString());
+        } catch (NativeDaemonConnectorException e) {
+            throw new IllegalStateException("Error communicating to native daemon", e);
+        }
+    }
+
+    @Override
     public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);