Support metered Wi-Fi NetworkPolicy.

Add networkId field to NetworkIdentity to identify Wi-Fi networks by
SSID.  Add support for policies without usage cycles.

Only apply mobile policies when SIM state is ready, which is cleaner
than just checking for airplane mode.  Also avoids creating no-op
default policies when subscriberId is null.

Bug: 3001465, 3291052
Change-Id: I1f8aaa49a5db306df022c402ea7f3f5d4bc0cfc7
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index ad8d41f..dd58397 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1097,6 +1097,18 @@
         }
     }
 
+    /** {@hide} */
+    public static boolean isActiveNetworkExpensive(Context context) {
+        // TODO: connect to NetworkPolicyManager
+        return false;
+    }
+
+    /** {@hide} */
+    public static long getActiveNetworkWarningBytes(Context context) {
+        // TODO: connect to NetworkPolicyManager
+        return -1;
+    }
+
     /**
      * Adds a file to the downloads database system, so it could appear in Downloads App
      * (and thus become eligible for management by the Downloads App).
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2eef8f4..de16985 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -20,6 +20,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.RemoteException;
@@ -610,6 +611,11 @@
         mService = checkNotNull(service, "missing IConnectivityManager");
     }
 
+    /** {@hide} */
+    public static ConnectivityManager from(Context context) {
+        return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
     /**
      * {@hide}
      */
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index ee12989..4ac5e76 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -16,9 +16,13 @@
 
 package android.net;
 
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeMobile;
 
 import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.telephony.TelephonyManager;
 
@@ -42,18 +46,21 @@
     final int mType;
     final int mSubType;
     final String mSubscriberId;
+    final String mNetworkId;
     final boolean mRoaming;
 
-    public NetworkIdentity(int type, int subType, String subscriberId, boolean roaming) {
-        this.mType = type;
-        this.mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
-        this.mSubscriberId = subscriberId;
-        this.mRoaming = roaming;
+    public NetworkIdentity(
+            int type, int subType, String subscriberId, String networkId, boolean roaming) {
+        mType = type;
+        mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType;
+        mSubscriberId = subscriberId;
+        mNetworkId = networkId;
+        mRoaming = roaming;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(mType, mSubType, mSubscriberId, mRoaming);
+        return Objects.hashCode(mType, mSubType, mSubscriberId, mNetworkId, mRoaming);
     }
 
     @Override
@@ -61,27 +68,34 @@
         if (obj instanceof NetworkIdentity) {
             final NetworkIdentity ident = (NetworkIdentity) obj;
             return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming
-                    && Objects.equal(mSubscriberId, ident.mSubscriberId);
+                    && Objects.equal(mSubscriberId, ident.mSubscriberId)
+                    && Objects.equal(mNetworkId, ident.mNetworkId);
         }
         return false;
     }
 
     @Override
     public String toString() {
-        final String typeName = ConnectivityManager.getNetworkTypeName(mType);
-        final String subTypeName;
+        final StringBuilder builder = new StringBuilder("[");
+        builder.append("type=").append(getNetworkTypeName(mType));
+        builder.append(", subType=");
         if (COMBINE_SUBTYPE_ENABLED) {
-            subTypeName = "COMBINED";
+            builder.append("COMBINED");
         } else if (ConnectivityManager.isNetworkTypeMobile(mType)) {
-            subTypeName = TelephonyManager.getNetworkTypeName(mSubType);
+            builder.append(TelephonyManager.getNetworkTypeName(mSubType));
         } else {
-            subTypeName = Integer.toString(mSubType);
+            builder.append(mSubType);
         }
-
-        final String scrubSubscriberId = scrubSubscriberId(mSubscriberId);
-        final String roaming = mRoaming ? ", ROAMING" : "";
-        return "[type=" + typeName + ", subType=" + subTypeName + ", subscriberId="
-                + scrubSubscriberId + roaming + "]";
+        if (mSubscriberId != null) {
+            builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
+        }
+        if (mNetworkId != null) {
+            builder.append(", networkId=").append(mNetworkId);
+        }
+        if (mRoaming) {
+            builder.append(", ROAMING");
+        }
+        return builder.append("]").toString();
     }
 
     public int getType() {
@@ -96,6 +110,10 @@
         return mSubscriberId;
     }
 
+    public String getNetworkId() {
+        return mNetworkId;
+    }
+
     public boolean getRoaming() {
         return mRoaming;
     }
@@ -106,8 +124,11 @@
     public static String scrubSubscriberId(String subscriberId) {
         if ("eng".equals(Build.TYPE)) {
             return subscriberId;
+        } else if (subscriberId != null) {
+            // TODO: parse this as MCC+MNC instead of hard-coding
+            return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
         } else {
-            return subscriberId != null ? "valid" : "null";
+            return "null";
         }
     }
 
@@ -122,8 +143,10 @@
         // TODO: consider moving subscriberId over to LinkCapabilities, so it
         // comes from an authoritative source.
 
-        final String subscriberId;
-        final boolean roaming;
+        String subscriberId = null;
+        String networkId = null;
+        boolean roaming = false;
+
         if (isNetworkTypeMobile(type)) {
             final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
                     Context.TELEPHONY_SERVICE);
@@ -133,10 +156,13 @@
             } else {
                 subscriberId = telephony.getSubscriberId();
             }
-        } else {
-            subscriberId = null;
-            roaming = false;
+
+        } else if (type == TYPE_WIFI) {
+            final WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+            final WifiInfo info = wifi.getConnectionInfo();
+            networkId = info != null ? info.getSSID() : null;
         }
-        return new NetworkIdentity(type, subType, subscriberId, roaming);
+
+        return new NetworkIdentity(type, subType, subscriberId, networkId, roaming);
     }
 }
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index c1f58a3..441db7a 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -30,6 +30,7 @@
  * @hide
  */
 public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
+    public static final int CYCLE_NONE = -1;
     public static final long WARNING_DISABLED = -1;
     public static final long LIMIT_DISABLED = -1;
     public static final long SNOOZE_NEVER = -1;
@@ -123,6 +124,13 @@
         lastLimitSnooze = SNOOZE_NEVER;
     }
 
+    /**
+     * Test if this policy has a cycle defined, after which usage should reset.
+     */
+    public boolean hasCycle() {
+        return cycleDay != CYCLE_NONE;
+    }
+
     @Override
     public int compareTo(NetworkPolicy another) {
         if (another == null || another.limitBytes == LIMIT_DISABLED) {
@@ -159,10 +167,17 @@
 
     @Override
     public String toString() {
-        return "NetworkPolicy[" + template + "]: cycleDay=" + cycleDay + ", cycleTimezone="
-                + cycleTimezone + ", warningBytes=" + warningBytes + ", limitBytes=" + limitBytes
-                + ", lastWarningSnooze=" + lastWarningSnooze + ", lastLimitSnooze="
-                + lastLimitSnooze + ", metered=" + metered + ", inferred=" + inferred;
+        final StringBuilder builder = new StringBuilder("NetworkPolicy");
+        builder.append("[").append(template).append("]:");
+        builder.append(" cycleDay=").append(cycleDay);
+        builder.append(", cycleTimezone=").append(cycleTimezone);
+        builder.append(", warningBytes=").append(warningBytes);
+        builder.append(", limitBytes=").append(limitBytes);
+        builder.append(", lastWarningSnooze=").append(lastWarningSnooze);
+        builder.append(", lastLimitSnooze=").append(lastLimitSnooze);
+        builder.append(", metered=").append(metered);
+        builder.append(", inferred=").append(inferred);
+        return builder.toString();
     }
 
     public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index c09c676..2b36131 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -17,6 +17,7 @@
 package android.net;
 
 import static android.content.pm.PackageManager.GET_SIGNATURES;
+import static android.net.NetworkPolicy.CYCLE_NONE;
 import static android.text.format.Time.MONTH_DAY;
 
 import android.content.Context;
@@ -66,27 +67,10 @@
         mService = service;
     }
 
-    public static NetworkPolicyManager getSystemService(Context context) {
+    public static NetworkPolicyManager from(Context context) {
         return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE);
     }
 
-    /** {@hide} */
-    public void setNetworkPolicies(NetworkPolicy[] policies) {
-        try {
-            mService.setNetworkPolicies(policies);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /** {@hide} */
-    public NetworkPolicy[] getNetworkPolicies() {
-        try {
-            return mService.getNetworkPolicies();
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
     /**
      * Set policy flags for specific application.
      *
@@ -122,6 +106,36 @@
         }
     }
 
+    public void setNetworkPolicies(NetworkPolicy[] policies) {
+        try {
+            mService.setNetworkPolicies(policies);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public NetworkPolicy[] getNetworkPolicies() {
+        try {
+            return mService.getNetworkPolicies();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    public void setRestrictBackground(boolean restrictBackground) {
+        try {
+            mService.setRestrictBackground(restrictBackground);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public boolean getRestrictBackground() {
+        try {
+            return mService.getRestrictBackground();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     /**
      * Compute the last cycle boundary for the given {@link NetworkPolicy}. For
      * example, if cycle day is 20th, and today is June 15th, it will return May
@@ -131,6 +145,10 @@
      * @hide
      */
     public static long computeLastCycleBoundary(long currentTime, NetworkPolicy policy) {
+        if (policy.cycleDay == CYCLE_NONE) {
+            throw new IllegalArgumentException("Unable to compute boundary without cycleDay");
+        }
+
         final Time now = new Time(policy.cycleTimezone);
         now.set(currentTime);
 
@@ -157,6 +175,10 @@
 
     /** {@hide} */
     public static long computeNextCycleBoundary(long currentTime, NetworkPolicy policy) {
+        if (policy.cycleDay == CYCLE_NONE) {
+            throw new IllegalArgumentException("Unable to compute boundary without cycleDay");
+        }
+
         final Time now = new Time(policy.cycleTimezone);
         now.set(currentTime);
 
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index e1fbdcc..50432a1 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -43,15 +43,10 @@
  */
 public class NetworkTemplate implements Parcelable {
 
-    /** {@hide} */
     public static final int MATCH_MOBILE_ALL = 1;
-    /** {@hide} */
     public static final int MATCH_MOBILE_3G_LOWER = 2;
-    /** {@hide} */
     public static final int MATCH_MOBILE_4G = 3;
-    /** {@hide} */
     public static final int MATCH_WIFI = 4;
-    /** {@hide} */
     public static final int MATCH_ETHERNET = 5;
 
     /**
@@ -65,37 +60,50 @@
     }
 
     /**
-     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
-     * networks together. Only uses statistics for requested IMSI.
+     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
+     * the given IMSI.
      */
     public static NetworkTemplate buildTemplateMobileAll(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId);
+        return new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId, null);
     }
 
     /**
-     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
-     * networks together that roughly meet a "3G" definition, or lower. Only
-     * uses statistics for requested IMSI.
+     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
+     * the given IMSI that roughly meet a "3G" definition, or lower.
      */
+    @Deprecated
     public static NetworkTemplate buildTemplateMobile3gLower(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId);
+        return new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId, null);
     }
 
     /**
-     * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style
-     * networks together that meet a "4G" definition. Only uses statistics for
-     * requested IMSI.
+     * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
+     * the given IMSI that roughly meet a "4G" definition.
      */
+    @Deprecated
     public static NetworkTemplate buildTemplateMobile4g(String subscriberId) {
-        return new NetworkTemplate(MATCH_MOBILE_4G, subscriberId);
+        return new NetworkTemplate(MATCH_MOBILE_4G, subscriberId, null);
     }
 
     /**
-     * Template to combine all {@link ConnectivityManager#TYPE_WIFI} style
-     * networks together.
+     * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks,
+     * regardless of SSID.
      */
+    public static NetworkTemplate buildTemplateWifiWildcard() {
+        return new NetworkTemplate(MATCH_WIFI, null, null);
+    }
+
+    @Deprecated
     public static NetworkTemplate buildTemplateWifi() {
-        return new NetworkTemplate(MATCH_WIFI, null);
+        return buildTemplateWifiWildcard();
+    }
+
+    /**
+     * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
+     * given SSID.
+     */
+    public static NetworkTemplate buildTemplateWifi(String networkId) {
+        return new NetworkTemplate(MATCH_WIFI, null, networkId);
     }
 
     /**
@@ -103,44 +111,53 @@
      * networks together.
      */
     public static NetworkTemplate buildTemplateEthernet() {
-        return new NetworkTemplate(MATCH_ETHERNET, null);
+        return new NetworkTemplate(MATCH_ETHERNET, null, null);
     }
 
     private final int mMatchRule;
     private final String mSubscriberId;
+    private final String mNetworkId;
 
-    /** {@hide} */
-    public NetworkTemplate(int matchRule, String subscriberId) {
-        this.mMatchRule = matchRule;
-        this.mSubscriberId = subscriberId;
+    public NetworkTemplate(int matchRule, String subscriberId, String networkId) {
+        mMatchRule = matchRule;
+        mSubscriberId = subscriberId;
+        mNetworkId = networkId;
     }
 
     private NetworkTemplate(Parcel in) {
         mMatchRule = in.readInt();
         mSubscriberId = in.readString();
+        mNetworkId = in.readString();
     }
 
-    /** {@inheritDoc} */
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mMatchRule);
         dest.writeString(mSubscriberId);
+        dest.writeString(mNetworkId);
     }
 
-    /** {@inheritDoc} */
+    @Override
     public int describeContents() {
         return 0;
     }
 
     @Override
     public String toString() {
-        final String scrubSubscriberId = scrubSubscriberId(mSubscriberId);
-        return "NetworkTemplate: matchRule=" + getMatchRuleName(mMatchRule) + ", subscriberId="
-                + scrubSubscriberId;
+        final StringBuilder builder = new StringBuilder("NetworkTemplate: ");
+        builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
+        if (mSubscriberId != null) {
+            builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
+        }
+        if (mNetworkId != null) {
+            builder.append(", networkId=").append(mNetworkId);
+        }
+        return builder.toString();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(mMatchRule, mSubscriberId);
+        return Objects.hashCode(mMatchRule, mSubscriberId, mNetworkId);
     }
 
     @Override
@@ -148,21 +165,24 @@
         if (obj instanceof NetworkTemplate) {
             final NetworkTemplate other = (NetworkTemplate) obj;
             return mMatchRule == other.mMatchRule
-                    && Objects.equal(mSubscriberId, other.mSubscriberId);
+                    && Objects.equal(mSubscriberId, other.mSubscriberId)
+                    && Objects.equal(mNetworkId, other.mNetworkId);
         }
         return false;
     }
 
-    /** {@hide} */
     public int getMatchRule() {
         return mMatchRule;
     }
 
-    /** {@hide} */
     public String getSubscriberId() {
         return mSubscriberId;
     }
 
+    public String getNetworkId() {
+        return mNetworkId;
+    }
+
     /**
      * Test if given {@link NetworkIdentity} matches this template.
      */
@@ -237,8 +257,13 @@
     private boolean matchesWifi(NetworkIdentity ident) {
         switch (ident.mType) {
             case TYPE_WIFI:
+                if (mNetworkId == null) {
+                    return true;
+                } else {
+                    return Objects.equal(mNetworkId, ident.mNetworkId);
+                }
             case TYPE_WIFI_P2P:
-                return true;
+                return mNetworkId == null;
             default:
                 return false;
         }
@@ -279,10 +304,12 @@
     }
 
     public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() {
+        @Override
         public NetworkTemplate createFromParcel(Parcel in) {
             return new NetworkTemplate(in);
         }
 
+        @Override
         public NetworkTemplate[] newArray(int size) {
             return new NetworkTemplate[size];
         }
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index ccd2763..d59585f 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.net;
 
-import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
@@ -101,7 +101,7 @@
             while (reader.hasMoreData()) {
                 entry.iface = reader.nextString();
                 entry.uid = UID_ALL;
-                entry.set = SET_DEFAULT;
+                entry.set = SET_ALL;
                 entry.tag = TAG_NONE;
 
                 final boolean active = reader.nextInt() != 0;
@@ -165,7 +165,7 @@
 
             entry.iface = iface;
             entry.uid = UID_ALL;
-            entry.set = SET_DEFAULT;
+            entry.set = SET_ALL;
             entry.tag = TAG_NONE;
             entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes"));
             entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets"));
@@ -193,7 +193,7 @@
                 try {
                     entry.iface = values.get(0);
                     entry.uid = UID_ALL;
-                    entry.set = SET_DEFAULT;
+                    entry.set = SET_ALL;
                     entry.tag = TAG_NONE;
                     entry.rxBytes = Long.parseLong(values.get(1));
                     entry.rxPackets = Long.parseLong(values.get(2));
diff --git a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
index ea94fa9..58269a8 100644
--- a/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.net;
 
+import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.TAG_NONE;
@@ -84,12 +85,12 @@
 
         final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertEquals(6, stats.size());
-        assertStatsEntry(stats, "lo", UID_ALL, SET_DEFAULT, TAG_NONE, 8308L, 8308L);
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L, 489339L);
-        assertStatsEntry(stats, "ifb0", UID_ALL, SET_DEFAULT, TAG_NONE, 52454L, 0L);
-        assertStatsEntry(stats, "ifb1", UID_ALL, SET_DEFAULT, TAG_NONE, 52454L, 0L);
-        assertStatsEntry(stats, "sit0", UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L);
-        assertStatsEntry(stats, "ip6tnl0", UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L);
+        assertStatsEntry(stats, "lo", UID_ALL, SET_ALL, TAG_NONE, 8308L, 8308L);
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 1507570L, 489339L);
+        assertStatsEntry(stats, "ifb0", UID_ALL, SET_ALL, TAG_NONE, 52454L, 0L);
+        assertStatsEntry(stats, "ifb1", UID_ALL, SET_ALL, TAG_NONE, 52454L, 0L);
+        assertStatsEntry(stats, "sit0", UID_ALL, SET_ALL, TAG_NONE, 0L, 0L);
+        assertStatsEntry(stats, "ip6tnl0", UID_ALL, SET_ALL, TAG_NONE, 0L, 0L);
     }
 
     public void testNetworkStatsSummaryDown() throws Exception {
@@ -102,8 +103,8 @@
 
         final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertEquals(7, stats.size());
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L, 489339L);
-        assertStatsEntry(stats, "wlan0", UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 2048L);
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 1507570L, 489339L);
+        assertStatsEntry(stats, "wlan0", UID_ALL, SET_ALL, TAG_NONE, 1024L, 2048L);
     }
 
     public void testNetworkStatsCombined() throws Exception {
@@ -115,7 +116,7 @@
         stageLong(40L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_packets"));
 
         final NetworkStats stats = mFactory.readNetworkStatsSummary();
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L + 10L,
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 1507570L + 10L,
                 2205L + 20L, 489339L + 30L, 2237L + 40L);
     }
 
@@ -128,7 +129,7 @@
         stageLong(40L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_packets"));
 
         final NetworkStats stats = mFactory.readNetworkStatsSummary();
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 10L, 20L, 30L, 40L);
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 10L, 20L, 30L, 40L);
     }
 
     public void testKernelTags() throws Exception {
@@ -153,9 +154,9 @@
 
         final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertEquals(6, stats.size());
-        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 2112L, 24L, 700L, 10L);
-        assertStatsEntry(stats, "test1", UID_ALL, SET_DEFAULT, TAG_NONE, 6L, 8L, 10L, 12L);
-        assertStatsEntry(stats, "test2", UID_ALL, SET_DEFAULT, TAG_NONE, 1L, 2L, 3L, 4L);
+        assertStatsEntry(stats, "rmnet0", UID_ALL, SET_ALL, TAG_NONE, 2112L, 24L, 700L, 10L);
+        assertStatsEntry(stats, "test1", UID_ALL, SET_ALL, TAG_NONE, 6L, 8L, 10L, 12L);
+        assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
     }
 
     /**
diff --git a/services/java/com/android/server/net/NetworkIdentitySet.java b/services/java/com/android/server/net/NetworkIdentitySet.java
index af03fb3..397f9f4 100644
--- a/services/java/com/android/server/net/NetworkIdentitySet.java
+++ b/services/java/com/android/server/net/NetworkIdentitySet.java
@@ -21,7 +21,6 @@
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
-import java.net.ProtocolException;
 import java.util.HashSet;
 
 /**
@@ -33,48 +32,46 @@
 public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADD_ROAMING = 2;
+    private static final int VERSION_ADD_NETWORK_ID = 3;
 
     public NetworkIdentitySet() {
     }
 
     public NetworkIdentitySet(DataInputStream in) throws IOException {
         final int version = in.readInt();
-        switch (version) {
-            case VERSION_INIT: {
-                final int size = in.readInt();
-                for (int i = 0; i < size; i++) {
-                    final int ignoredVersion = in.readInt();
-                    final int type = in.readInt();
-                    final int subType = in.readInt();
-                    final String subscriberId = readOptionalString(in);
-                    add(new NetworkIdentity(type, subType, subscriberId, false));
-                }
-                break;
+        final int size = in.readInt();
+        for (int i = 0; i < size; i++) {
+            if (version <= VERSION_INIT) {
+                final int ignored = in.readInt();
             }
-            case VERSION_ADD_ROAMING: {
-                final int size = in.readInt();
-                for (int i = 0; i < size; i++) {
-                    final int type = in.readInt();
-                    final int subType = in.readInt();
-                    final String subscriberId = readOptionalString(in);
-                    final boolean roaming = in.readBoolean();
-                    add(new NetworkIdentity(type, subType, subscriberId, roaming));
-                }
-                break;
+            final int type = in.readInt();
+            final int subType = in.readInt();
+            final String subscriberId = readOptionalString(in);
+            final String networkId;
+            if (version >= VERSION_ADD_NETWORK_ID) {
+                networkId = readOptionalString(in);
+            } else {
+                networkId = null;
             }
-            default: {
-                throw new ProtocolException("unexpected version: " + version);
+            final boolean roaming;
+            if (version >= VERSION_ADD_ROAMING) {
+                roaming = in.readBoolean();
+            } else {
+                roaming = false;
             }
+
+            add(new NetworkIdentity(type, subType, subscriberId, networkId, false));
         }
     }
 
     public void writeToStream(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_ADD_ROAMING);
+        out.writeInt(VERSION_ADD_NETWORK_ID);
         out.writeInt(size());
         for (NetworkIdentity ident : this) {
             out.writeInt(ident.getType());
             out.writeInt(ident.getSubType());
             writeOptionalString(out, ident.getSubscriberId());
+            writeOptionalString(out, ident.getNetworkId());
             out.writeBoolean(ident.getRoaming());
         }
     }
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 1f1e720..b0657a6 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -48,6 +48,7 @@
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.telephony.TelephonyManager.SIM_STATE_READY;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
@@ -113,6 +114,7 @@
 import com.android.internal.R;
 import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
@@ -160,6 +162,8 @@
     private static final int VERSION_ADDED_TIMEZONE = 6;
     private static final int VERSION_ADDED_INFERRED = 7;
     private static final int VERSION_SWITCH_APP_ID = 8;
+    private static final int VERSION_ADDED_NETWORK_ID = 9;
+    private static final int VERSION_LATEST = VERSION_ADDED_NETWORK_ID;
 
     // @VisibleForTesting
     public static final int TYPE_WARNING = 0x1;
@@ -175,6 +179,7 @@
     private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground";
     private static final String ATTR_NETWORK_TEMPLATE = "networkTemplate";
     private static final String ATTR_SUBSCRIBER_ID = "subscriberId";
+    private static final String ATTR_NETWORK_ID = "networkId";
     private static final String ATTR_CYCLE_DAY = "cycleDay";
     private static final String ATTR_CYCLE_TIMEZONE = "cycleTimezone";
     private static final String ATTR_WARNING_BYTES = "warningBytes";
@@ -491,6 +496,7 @@
         for (NetworkPolicy policy : mNetworkPolicy.values()) {
             // ignore policies that aren't relevant to user
             if (!isTemplateRelevant(policy.template)) continue;
+            if (!policy.hasCycle()) continue;
 
             final long start = computeLastCycleBoundary(currentTime, policy);
             final long end = currentTime;
@@ -528,21 +534,24 @@
 
     /**
      * 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.
+     * current device state, such as when
+     * {@link TelephonyManager#getSubscriberId()} matches. This is regardless of
+     * data connection status.
      */
     private boolean isTemplateRelevant(NetworkTemplate template) {
+        final TelephonyManager tele = TelephonyManager.from(mContext);
+
         switch (template.getMatchRule()) {
             case MATCH_MOBILE_3G_LOWER:
             case MATCH_MOBILE_4G:
             case MATCH_MOBILE_ALL:
-                // mobile templates aren't relevant in airplane mode
-                if (isAirplaneModeOn(mContext)) {
+                // mobile templates are relevant when SIM is ready and
+                // subscriberId matches.
+                if (tele.getSimState() == SIM_STATE_READY) {
+                    return Objects.equal(tele.getSubscriberId(), template.getSubscriberId());
+                } else {
                     return false;
                 }
-
-                // mobile templates are relevant when subscriberid is active
-                return Objects.equal(getActiveSubscriberId(), template.getSubscriberId());
         }
         return true;
     }
@@ -761,7 +770,7 @@
         final long currentTime = currentTimeMillis();
         for (NetworkPolicy policy : mNetworkPolicy.values()) {
             // shortcut when policy has no limit
-            if (policy.limitBytes == LIMIT_DISABLED) {
+            if (policy.limitBytes == LIMIT_DISABLED || !policy.hasCycle()) {
                 setNetworkTemplateEnabled(policy.template, true);
                 continue;
             }
@@ -784,13 +793,16 @@
      * for the given {@link NetworkTemplate}.
      */
     private void setNetworkTemplateEnabled(NetworkTemplate template, boolean enabled) {
+        final TelephonyManager tele = TelephonyManager.from(mContext);
+
         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())) {
+                if (tele.getSimState() == SIM_STATE_READY
+                        && Objects.equal(tele.getSubscriberId(), template.getSubscriberId())) {
                     setPolicyDataEnable(TYPE_MOBILE, enabled);
                     setPolicyDataEnable(TYPE_WIMAX, enabled);
                 }
@@ -863,9 +875,15 @@
         for (NetworkPolicy policy : mNetworkRules.keySet()) {
             final String[] ifaces = mNetworkRules.get(policy);
 
-            final long start = computeLastCycleBoundary(currentTime, policy);
-            final long end = currentTime;
-            final long totalBytes = getTotalBytes(policy.template, start, end);
+            final long start;
+            final long totalBytes;
+            if (policy.hasCycle()) {
+                start = computeLastCycleBoundary(currentTime, policy);
+                totalBytes = getTotalBytes(policy.template, start, currentTime);
+            } else {
+                start = Long.MAX_VALUE;
+                totalBytes = 0;
+            }
 
             if (LOGD) {
                 Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces "
@@ -923,9 +941,14 @@
         if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyLocked()");
         if (mSuppressDefaultPolicy) return;
 
-        final String subscriberId = getActiveSubscriberId();
+        final TelephonyManager tele = TelephonyManager.from(mContext);
+
+        // avoid creating policy when SIM isn't ready
+        if (tele.getSimState() != SIM_STATE_READY) return;
+
+        final String subscriberId = tele.getSubscriberId();
         final NetworkIdentity probeIdent = new NetworkIdentity(
-                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, false);
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false);
 
         // examine to see if any policy is defined for active mobile
         boolean mobileDefined = false;
@@ -986,6 +1009,12 @@
                     } else if (TAG_NETWORK_POLICY.equals(tag)) {
                         final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE);
                         final String subscriberId = in.getAttributeValue(null, ATTR_SUBSCRIBER_ID);
+                        final String networkId;
+                        if (version >= VERSION_ADDED_NETWORK_ID) {
+                            networkId = in.getAttributeValue(null, ATTR_NETWORK_ID);
+                        } else {
+                            networkId = null;
+                        }
                         final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY);
                         final String cycleTimezone;
                         if (version >= VERSION_ADDED_TIMEZONE) {
@@ -1031,12 +1060,12 @@
                         }
 
                         final NetworkTemplate template = new NetworkTemplate(
-                                networkTemplate, subscriberId);
+                                networkTemplate, subscriberId, networkId);
                         mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay,
                                 cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
                                 lastLimitSnooze, metered, inferred));
 
-                    } else if (TAG_UID_POLICY.equals(tag)) {
+                    } else if (TAG_UID_POLICY.equals(tag) && version < VERSION_SWITCH_APP_ID) {
                         final int uid = readIntAttribute(in, ATTR_UID);
                         final int policy = readIntAttribute(in, ATTR_POLICY);
 
@@ -1046,7 +1075,7 @@
                         } else {
                             Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
                         }
-                    } else if (TAG_APP_POLICY.equals(tag)) {
+                    } else if (TAG_APP_POLICY.equals(tag) && version >= VERSION_SWITCH_APP_ID) {
                         final int appId = readIntAttribute(in, ATTR_APP_ID);
                         final int policy = readIntAttribute(in, ATTR_POLICY);
 
@@ -1099,7 +1128,7 @@
             out.startDocument(null, true);
 
             out.startTag(null, TAG_POLICY_LIST);
-            writeIntAttribute(out, ATTR_VERSION, VERSION_SWITCH_APP_ID);
+            writeIntAttribute(out, ATTR_VERSION, VERSION_LATEST);
             writeBooleanAttribute(out, ATTR_RESTRICT_BACKGROUND, mRestrictBackground);
 
             // write all known network policies
@@ -1112,6 +1141,10 @@
                 if (subscriberId != null) {
                     out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
                 }
+                final String networkId = template.getNetworkId();
+                if (networkId != null) {
+                    out.attribute(null, ATTR_NETWORK_ID, networkId);
+                }
                 writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
                 out.attribute(null, ATTR_CYCLE_TIMEZONE, policy.cycleTimezone);
                 writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
@@ -1318,7 +1351,7 @@
             policy = findPolicyForNetworkLocked(ident);
         }
 
-        if (policy == null) {
+        if (policy == null || !policy.hasCycle()) {
             // missing policy means we can't derive useful quota info
             return null;
         }
@@ -1340,9 +1373,11 @@
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         mContext.enforceCallingOrSelfPermission(DUMP, TAG);
 
+        final IndentingPrintWriter fout = new IndentingPrintWriter(writer, "  ");
+
         final HashSet<String> argSet = new HashSet<String>();
         for (String arg : args) {
             argSet.add(arg);
@@ -1365,31 +1400,36 @@
 
             fout.print("Restrict background: "); fout.println(mRestrictBackground);
             fout.println("Network policies:");
+            fout.increaseIndent();
             for (NetworkPolicy policy : mNetworkPolicy.values()) {
-                fout.print("  "); fout.println(policy.toString());
+                fout.println(policy.toString());
             }
+            fout.decreaseIndent();
 
             fout.println("Policy for apps:");
+            fout.increaseIndent();
             int size = mAppPolicy.size();
             for (int i = 0; i < size; i++) {
                 final int appId = mAppPolicy.keyAt(i);
                 final int policy = mAppPolicy.valueAt(i);
-                fout.print("  appId=");
+                fout.print("appId=");
                 fout.print(appId);
                 fout.print(" policy=");
                 dumpPolicy(fout, policy);
                 fout.println();
             }
+            fout.decreaseIndent();
 
             final SparseBooleanArray knownUids = new SparseBooleanArray();
             collectKeys(mUidForeground, knownUids);
             collectKeys(mUidRules, knownUids);
 
             fout.println("Status for known UIDs:");
+            fout.increaseIndent();
             size = knownUids.size();
             for (int i = 0; i < size; i++) {
                 final int uid = knownUids.keyAt(i);
-                fout.print("  UID=");
+                fout.print("UID=");
                 fout.print(uid);
 
                 fout.print(" foreground=");
@@ -1410,6 +1450,7 @@
 
                 fout.println();
             }
+            fout.decreaseIndent();
         }
     }
 
@@ -1697,12 +1738,6 @@
         }
     }
 
-    private String getActiveSubscriberId() {
-        final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        return telephony.getSubscriberId();
-    }
-
     private long getTotalBytes(NetworkTemplate template, long start, long end) {
         try {
             return mNetworkStats.getSummaryForNetwork(template, start, end).getTotalBytes();
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 8796afc..b847673 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -34,7 +34,7 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
-import static android.net.NetworkTemplate.buildTemplateWifi;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE;
@@ -836,7 +836,7 @@
                 trustedTime);
 
         // collect wifi sample
-        template = buildTemplateWifi();
+        template = buildTemplateWifiWildcard();
         devTotal = mDevRecorder.getTotalSinceBootLocked(template);
         xtTotal = new NetworkStats.Entry();
         uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 60be35a..cc3c328 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -32,6 +32,7 @@
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
     <uses-permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 88ee867..1773e33 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -70,6 +70,7 @@
 import android.os.INetworkManagementService;
 import android.os.IPowerManager;
 import android.os.MessageQueue.IdleHandler;
+import android.os.SystemClock;
 import android.os.UserId;
 import android.test.AndroidTestCase;
 import android.test.mock.MockPackageManager;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index daf2018..103d8e1 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -84,7 +84,7 @@
  */
 @LargeTest
 public class NetworkStatsServiceTest extends AndroidTestCase {
-    private static final String TAG = "NetworkStatsServiceTest";
+    private static final String TAG  = "NetworkStatsServiceTest";
 
     private static final String TEST_IFACE = "test0";
     private static final String TEST_IFACE2 = "test1";
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
index 7f05f56..e40f166 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
@@ -20,8 +20,6 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
 import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.NetworkIdentity;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 import android.test.AndroidTestCase;
@@ -145,12 +143,6 @@
         }
     }
 
-    public static NetworkIdentitySet buildWifiIdent() {
-        final NetworkIdentitySet set = new NetworkIdentitySet();
-        set.add(new NetworkIdentity(ConnectivityManager.TYPE_WIFI, 0, null, false));
-        return set;
-    }
-
     private static void assertSummaryTotal(NetworkStatsCollection collection,
             NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
         final NetworkStats.Entry entry = collection.getSummary(
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index db78e2e..1984926 100755
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -23,7 +23,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.util.Log;
 
 import com.android.internal.telephony.IPhoneSubInfo;
 import com.android.internal.telephony.ITelephony;
@@ -85,6 +84,10 @@
         return sInstance;
     }
 
+    /** {@hide} */
+    public static TelephonyManager from(Context context) {
+        return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+    }
 
     //
     // Broadcast Intent actions
diff --git a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java
index a13c0c9..b7e80d4 100644
--- a/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java
+++ b/tests/DataIdleTest/src/com/android/tests/dataidle/DataIdleTest.java
@@ -52,7 +52,7 @@
      * Test that dumps all the data usage metrics for wifi to instrumentation out.
      */
     public void testWifiIdle() {
-        NetworkTemplate template = NetworkTemplate.buildTemplateWifi();
+        NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard();
         fetchStats(template);
     }