Read "qtaguid" network stats, refactor templates.

Teach NMS to read qtaguid stats from kernel, but fall back to older
stats when kernel doesn't support.  Add "tags" to NetworkStats entries
to support qtaguid.  To work around double-reporting bug, subtract
tagged stats from TAG_NONE entry.

Flesh out stronger NetworkTemplate.  All NetworkStatsService requests
now require a template, and moved matching logic into template.

Record UID stats keyed on complete NetworkIdentitySet definition,
similar to how interface stats are stored.  Since previous UID stats
didn't have iface breakdown, discard during file format upgrade.

Change-Id: I0447b5e7d205d73d28e71c889c568e536e91b8e4
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index bb0c671..d5bdd21 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -16,6 +16,10 @@
 
 package com.android.server;
 
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.net.INetworkManagementEventObserver;
@@ -37,6 +41,7 @@
 import java.io.DataInputStream;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.Inet4Address;
@@ -59,8 +64,9 @@
     private static final int ADD = 1;
     private static final int REMOVE = 2;
 
-    /** Base path to UID-granularity network statistics. */
-    private static final File PATH_PROC_UID_STAT = new File("/proc/uid_stat");
+    @Deprecated
+    private static final File STATS_UIDSTAT = new File("/proc/uid_stat");
+    private static final File STATS_NETFILTER = new File("/proc/net/xt_qtaguid/stats");
 
     class NetdResponseCode {
         public static final int InterfaceListResult       = 110;
@@ -899,7 +905,7 @@
         for (String iface : ifaces) {
             final long rx = getInterfaceCounter(iface, true);
             final long tx = getInterfaceCounter(iface, false);
-            stats.addEntry(iface, NetworkStats.UID_ALL, rx, tx);
+            stats.addEntry(iface, UID_ALL, TAG_NONE, rx, tx);
         }
 
         return stats;
@@ -910,16 +916,11 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
 
-        final String[] knownUids = PATH_PROC_UID_STAT.list();
-        final NetworkStats stats = new NetworkStats(
-                SystemClock.elapsedRealtime(), knownUids.length);
-
-        for (String uid : knownUids) {
-            final int uidInt = Integer.parseInt(uid);
-            collectNetworkStatsDetail(stats, uidInt);
+        if (STATS_NETFILTER.exists()) {
+            return getNetworkStatsDetailNetfilter(UID_ALL);
+        } else {
+            return getNetworkStatsDetailUidstat(UID_ALL);
         }
-
-        return stats;
     }
 
     @Override
@@ -929,19 +930,89 @@
                     android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
         }
 
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-        collectNetworkStatsDetail(stats, uid);
+        if (STATS_NETFILTER.exists()) {
+            return getNetworkStatsDetailNetfilter(uid);
+        } else {
+            return getNetworkStatsDetailUidstat(uid);
+        }
+    }
+
+    /**
+     * Build {@link NetworkStats} with detailed UID statistics.
+     */
+    private NetworkStats getNetworkStatsDetailNetfilter(int limitUid) {
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
+
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader(STATS_NETFILTER));
+
+            // assumes format from kernel:
+            // idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
+
+            // skip first line, which is legend
+            String line = reader.readLine();
+            while ((line = reader.readLine()) != null) {
+                final StringTokenizer t = new StringTokenizer(line);
+
+                final String idx = t.nextToken();
+                final String iface = t.nextToken();
+
+                try {
+                    // TODO: kernel currently emits tag in upper half of long;
+                    // eventually switch to directly using int.
+                    final int tag = (int) (Long.parseLong(t.nextToken().substring(2), 16) >> 32);
+                    final int uid = Integer.parseInt(t.nextToken());
+                    final long rx = Long.parseLong(t.nextToken());
+                    final long tx = Long.parseLong(t.nextToken());
+    
+                    if (limitUid == UID_ALL || limitUid == uid) {
+                        stats.addEntry(iface, uid, tag, rx, tx);
+                        if (tag != TAG_NONE) {
+                            // proc also counts tagged data in generic tag, so
+                            // we subtract it here to avoid double-counting.
+                            stats.combineEntry(iface, uid, TAG_NONE, -rx, -tx);
+                        }
+                    }
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "problem parsing stats for idx " + idx + ": " + e);
+                }
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "problem parsing stats: " + e);
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+
         return stats;
     }
 
-    private void collectNetworkStatsDetail(NetworkStats stats, int uid) {
-        // TODO: kernel module will provide interface-level stats in future
-        // TODO: migrate these stats to come across netd in bulk, instead of all
-        // these individual file reads.
-        final File uidPath = new File(PATH_PROC_UID_STAT, Integer.toString(uid));
-        final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
-        final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
-        stats.addEntry(NetworkStats.IFACE_ALL, uid, rx, tx);
+    /**
+     * Build {@link NetworkStats} with detailed UID statistics.
+     *
+     * @deprecated since this uses older "uid_stat" data, and doesn't provide
+     *             tag-level granularity or additional variables.
+     */
+    @Deprecated
+    private NetworkStats getNetworkStatsDetailUidstat(int limitUid) {
+        final String[] knownUids;
+        if (limitUid == UID_ALL) {
+            knownUids = STATS_UIDSTAT.list();
+        } else {
+            knownUids = new String[] { String.valueOf(limitUid) };
+        }
+
+        final NetworkStats stats = new NetworkStats(
+                SystemClock.elapsedRealtime(), knownUids.length);
+        for (String uid : knownUids) {
+            final int uidInt = Integer.parseInt(uid);
+            final File uidPath = new File(STATS_UIDSTAT, uid);
+            final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
+            final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
+            stats.addEntry(IFACE_ALL, uidInt, TAG_NONE, rx, tx);
+        }
+
+        return stats;
     }
 
     public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) {
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index 510ff62..7266d7d 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -533,7 +533,8 @@
             long incWrite = 0;
             try {
                 final NetworkStats stats = mNMService.getNetworkStatsSummary();
-                final int index = stats.findIndex(mIface, NetworkStats.UID_ALL);
+                final int index = stats.findIndex(
+                        mIface, NetworkStats.UID_ALL, NetworkStats.TAG_NONE);
 
                 if (index != -1) {
                     incRead = stats.rx[index] - mLastRead;
diff --git a/services/java/com/android/server/net/InterfaceIdentity.java b/services/java/com/android/server/net/InterfaceIdentity.java
deleted file mode 100644
index ff86581..0000000
--- a/services/java/com/android/server/net/InterfaceIdentity.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.net;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.net.ProtocolException;
-import java.util.HashSet;
-
-/**
- * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
- * active on that interface.
- *
- * @hide
- */
-public class InterfaceIdentity extends HashSet<NetworkIdentity> {
-    private static final int VERSION_CURRENT = 1;
-
-    public InterfaceIdentity() {
-    }
-
-    public InterfaceIdentity(DataInputStream in) throws IOException {
-        final int version = in.readInt();
-        switch (version) {
-            case VERSION_CURRENT: {
-                final int size = in.readInt();
-                for (int i = 0; i < size; i++) {
-                    add(new NetworkIdentity(in));
-                }
-                break;
-            }
-            default: {
-                throw new ProtocolException("unexpected version: " + version);
-            }
-        }
-    }
-
-    public void writeToStream(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_CURRENT);
-        out.writeInt(size());
-        for (NetworkIdentity ident : this) {
-            ident.writeToStream(out);
-        }
-    }
-
-    /**
-     * Test if any {@link NetworkIdentity} on this interface matches the given
-     * template and IMEI.
-     */
-    public boolean matchesTemplate(int networkTemplate, String subscriberId) {
-        for (NetworkIdentity ident : this) {
-            if (ident.matchesTemplate(networkTemplate, subscriberId)) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/services/java/com/android/server/net/NetworkIdentity.java b/services/java/com/android/server/net/NetworkIdentity.java
deleted file mode 100644
index 4a207f7..0000000
--- a/services/java/com/android/server/net/NetworkIdentity.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.net;
-
-import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
-import static android.net.ConnectivityManager.isNetworkTypeMobile;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
-import static android.net.TrafficStats.TEMPLATE_WIFI;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
-import static android.telephony.TelephonyManager.NETWORK_CLASS_UNKNOWN;
-import static android.telephony.TelephonyManager.getNetworkClass;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.NetworkState;
-import android.telephony.TelephonyManager;
-
-import com.android.internal.util.Objects;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.net.ProtocolException;
-
-/**
- * Identity of a {@link NetworkInfo}, defined by network type and billing
- * relationship (such as IMSI).
- *
- * @hide
- */
-public class NetworkIdentity {
-    private static final int VERSION_CURRENT = 1;
-
-    public final int type;
-    public final int subType;
-    public final String subscriberId;
-
-    public NetworkIdentity(int type, int subType, String subscriberId) {
-        this.type = type;
-        this.subType = subType;
-        this.subscriberId = subscriberId;
-    }
-
-    public NetworkIdentity(DataInputStream in) throws IOException {
-        final int version = in.readInt();
-        switch (version) {
-            case VERSION_CURRENT: {
-                type = in.readInt();
-                subType = in.readInt();
-                subscriberId = readOptionalString(in);
-                break;
-            }
-            default: {
-                throw new ProtocolException("unexpected version: " + version);
-            }
-        }
-    }
-
-    public void writeToStream(DataOutputStream out) throws IOException {
-        out.writeInt(VERSION_CURRENT);
-        out.writeInt(type);
-        out.writeInt(subType);
-        writeOptionalString(out, subscriberId);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(type, subType, subscriberId);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof NetworkIdentity) {
-            final NetworkIdentity ident = (NetworkIdentity) obj;
-            return type == ident.type && subType == ident.subType
-                    && Objects.equal(subscriberId, ident.subscriberId);
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        final String typeName = ConnectivityManager.getNetworkTypeName(type);
-        final String subTypeName;
-        if (ConnectivityManager.isNetworkTypeMobile(type)) {
-            subTypeName = TelephonyManager.getNetworkTypeName(subType);
-        } else {
-            subTypeName = Integer.toString(subType);
-        }
-
-        return "[type=" + typeName + ", subType=" + subTypeName + ", subId=" + subscriberId + "]";
-    }
-
-    /**
-     * Test if this network matches the given template and IMEI.
-     */
-    public boolean matchesTemplate(int networkTemplate, String subscriberId) {
-        switch (networkTemplate) {
-            case TEMPLATE_MOBILE_ALL:
-                return matchesMobile(subscriberId);
-            case TEMPLATE_MOBILE_3G_LOWER:
-                return matchesMobile3gLower(subscriberId);
-            case TEMPLATE_MOBILE_4G:
-                return matchesMobile4g(subscriberId);
-            case TEMPLATE_WIFI:
-                return matchesWifi();
-            default:
-                throw new IllegalArgumentException("unknown network template");
-        }
-    }
-
-    /**
-     * Check if mobile network with matching IMEI. Also matches
-     * {@link #TYPE_WIMAX}.
-     */
-    private boolean matchesMobile(String subscriberId) {
-        if (isNetworkTypeMobile(type) && Objects.equal(this.subscriberId, subscriberId)) {
-            return true;
-        } else if (type == TYPE_WIMAX) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Check if mobile network classified 3G or lower with matching IMEI.
-     */
-    private boolean matchesMobile3gLower(String subscriberId) {
-        if (isNetworkTypeMobile(type)
-                && Objects.equal(this.subscriberId, subscriberId)) {
-            switch (getNetworkClass(subType)) {
-                case NETWORK_CLASS_UNKNOWN:
-                case NETWORK_CLASS_2_G:
-                case NETWORK_CLASS_3_G:
-                    return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Check if mobile network classified 4G with matching IMEI. Also matches
-     * {@link #TYPE_WIMAX}.
-     */
-    private boolean matchesMobile4g(String subscriberId) {
-        if (isNetworkTypeMobile(type)
-                && Objects.equal(this.subscriberId, subscriberId)) {
-            switch (getNetworkClass(subType)) {
-                case NETWORK_CLASS_4_G:
-                    return true;
-            }
-        } else if (type == TYPE_WIMAX) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Check if matches Wi-Fi network template.
-     */
-    private boolean matchesWifi() {
-        if (type == TYPE_WIFI) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Build a {@link NetworkIdentity} from the given {@link NetworkState},
-     * assuming that any mobile networks are using the current IMSI.
-     */
-    public static NetworkIdentity buildNetworkIdentity(Context context, NetworkState state) {
-        final int type = state.networkInfo.getType();
-        final int subType = state.networkInfo.getSubtype();
-
-        // TODO: consider moving subscriberId over to LinkCapabilities, so it
-        // comes from an authoritative source.
-
-        final String subscriberId;
-        if (isNetworkTypeMobile(type)) {
-            final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
-                    Context.TELEPHONY_SERVICE);
-            subscriberId = telephony.getSubscriberId();
-        } else {
-            subscriberId = null;
-        }
-        return new NetworkIdentity(type, subType, subscriberId);
-    }
-
-    private static void writeOptionalString(DataOutputStream out, String value) throws IOException {
-        if (value != null) {
-            out.writeByte(1);
-            out.writeUTF(value);
-        } else {
-            out.writeByte(0);
-        }
-    }
-
-    private static String readOptionalString(DataInputStream in) throws IOException {
-        if (in.readByte() != 0) {
-            return in.readUTF();
-        } else {
-            return null;
-        }
-    }
-
-}
diff --git a/services/java/com/android/server/net/NetworkIdentitySet.java b/services/java/com/android/server/net/NetworkIdentitySet.java
new file mode 100644
index 0000000..757d3bc
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkIdentitySet.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.net.NetworkIdentity;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.HashSet;
+
+/**
+ * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
+ * active on that interface.
+ *
+ * @hide
+ */
+public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
+    private static final int VERSION_INIT = 1;
+
+    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));
+                }
+                break;
+            }
+            default: {
+                throw new ProtocolException("unexpected version: " + version);
+            }
+        }
+    }
+
+    public void writeToStream(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION_INIT);
+        out.writeInt(size());
+        for (NetworkIdentity ident : this) {
+            out.writeInt(VERSION_INIT);
+            out.writeInt(ident.getType());
+            out.writeInt(ident.getSubType());
+            writeOptionalString(out, ident.getSubscriberId());
+        }
+    }
+
+    private static void writeOptionalString(DataOutputStream out, String value) throws IOException {
+        if (value != null) {
+            out.writeByte(1);
+            out.writeUTF(value);
+        } else {
+            out.writeByte(0);
+        }
+    }
+
+    private static String readOptionalString(DataInputStream in) throws IOException {
+        if (in.readByte() != 0) {
+            return in.readUTF();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 43f3c63..ada9ba4 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
@@ -36,10 +37,9 @@
 import static android.net.NetworkPolicyManager.dumpPolicy;
 import static android.net.NetworkPolicyManager.dumpRules;
 import static android.net.NetworkPolicyManager.isUidValidForPolicy;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
-import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
-import static android.net.TrafficStats.isNetworkTemplateMobile;
+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.text.format.DateUtils.DAY_IN_MILLIS;
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
@@ -61,9 +61,11 @@
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
+import android.net.NetworkIdentity;
 import android.net.NetworkPolicy;
 import android.net.NetworkState;
 import android.net.NetworkStats;
+import android.net.NetworkTemplate;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -84,7 +86,6 @@
 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;
@@ -353,10 +354,10 @@
             final long total;
             try {
                 final NetworkStats stats = mNetworkStats.getSummaryForNetwork(
-                        start, end, policy.networkTemplate, policy.subscriberId);
+                        policy.template, start, end);
                 total = stats.rx[0] + stats.tx[0];
             } catch (RemoteException e) {
-                Slog.w(TAG, "problem reading summary for template " + policy.networkTemplate);
+                Slog.w(TAG, "problem reading summary for template " + policy.template);
                 continue;
             }
 
@@ -380,8 +381,7 @@
      * notification of a specific type, like {@link #TYPE_LIMIT}.
      */
     private String buildNotificationTag(NetworkPolicy policy, int type) {
-        // TODO: consider splicing subscriberId hash into mix
-        return TAG + ":" + policy.networkTemplate + ":" + type;
+        return TAG + ":" + policy.template.hashCode() + ":" + type;
     }
 
     /**
@@ -408,7 +408,7 @@
 
                 final Intent intent = new Intent(ACTION_DATA_USAGE_WARNING);
                 intent.addCategory(Intent.CATEGORY_DEFAULT);
-                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.networkTemplate);
+                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.template.getMatchRule());
                 builder.setContentIntent(PendingIntent.getActivity(
                         mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                 break;
@@ -416,11 +416,11 @@
             case TYPE_LIMIT: {
                 final String title;
                 final String body = res.getString(R.string.data_usage_limit_body);
-                switch (policy.networkTemplate) {
-                    case TEMPLATE_MOBILE_3G_LOWER:
+                switch (policy.template.getMatchRule()) {
+                    case MATCH_MOBILE_3G_LOWER:
                         title = res.getString(R.string.data_usage_3g_limit_title);
                         break;
-                    case TEMPLATE_MOBILE_4G:
+                    case MATCH_MOBILE_4G:
                         title = res.getString(R.string.data_usage_4g_limit_title);
                         break;
                     default:
@@ -435,7 +435,7 @@
 
                 final Intent intent = new Intent(ACTION_DATA_USAGE_LIMIT);
                 intent.addCategory(Intent.CATEGORY_DEFAULT);
-                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.networkTemplate);
+                intent.putExtra(EXTRA_NETWORK_TEMPLATE, policy.template.getMatchRule());
                 builder.setContentIntent(PendingIntent.getActivity(
                         mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
                 break;
@@ -521,7 +521,7 @@
             // collect all active ifaces that match this template
             ifaceList.clear();
             for (NetworkIdentity ident : networks.keySet()) {
-                if (ident.matchesTemplate(policy.networkTemplate, policy.subscriberId)) {
+                if (policy.template.matches(ident)) {
                     final String iface = networks.get(ident);
                     ifaceList.add(iface);
                 }
@@ -554,11 +554,10 @@
             final NetworkStats stats;
             final long total;
             try {
-                stats = mNetworkStats.getSummaryForNetwork(
-                        start, end, policy.networkTemplate, policy.subscriberId);
+                stats = mNetworkStats.getSummaryForNetwork(policy.template, start, end);
                 total = stats.rx[0] + stats.tx[0];
             } catch (RemoteException e) {
-                Slog.w(TAG, "problem reading summary for template " + policy.networkTemplate);
+                Slog.w(TAG, "problem reading summary for template " + policy.template);
                 continue;
             }
 
@@ -603,12 +602,13 @@
     private void ensureActiveMobilePolicyLocked() {
         if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyLocked()");
         final String subscriberId = getActiveSubscriberId();
+        final NetworkIdentity probeIdent = new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId);
 
         // examine to see if any policy is defined for active mobile
         boolean mobileDefined = false;
         for (NetworkPolicy policy : mNetworkPolicy) {
-            if (isNetworkTemplateMobile(policy.networkTemplate)
-                    && Objects.equal(subscriberId, policy.subscriberId)) {
+            if (policy.template.matches(probeIdent)) {
                 mobileDefined = true;
             }
         }
@@ -624,8 +624,9 @@
             time.setToNow();
             final int cycleDay = time.monthDay;
 
-            mNetworkPolicy.add(new NetworkPolicy(
-                    TEMPLATE_MOBILE_ALL, subscriberId, cycleDay, 4 * GB_IN_BYTES, LIMIT_DISABLED));
+            final NetworkTemplate template = new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId);
+            mNetworkPolicy.add(
+                    new NetworkPolicy(template, cycleDay, 4 * GB_IN_BYTES, LIMIT_DISABLED));
             writePolicyLocked();
         }
     }
@@ -658,8 +659,10 @@
                         final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES);
                         final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES);
 
-                        mNetworkPolicy.add(new NetworkPolicy(
-                                networkTemplate, subscriberId, cycleDay, warningBytes, limitBytes));
+                        final NetworkTemplate template = new NetworkTemplate(
+                                networkTemplate, subscriberId);
+                        mNetworkPolicy.add(
+                                new NetworkPolicy(template, cycleDay, warningBytes, limitBytes));
 
                     } else if (TAG_UID_POLICY.equals(tag)) {
                         final int uid = readIntAttribute(in, ATTR_UID);
@@ -701,10 +704,13 @@
 
             // write all known network policies
             for (NetworkPolicy policy : mNetworkPolicy) {
+                final NetworkTemplate template = policy.template;
+
                 out.startTag(null, TAG_NETWORK_POLICY);
-                writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, policy.networkTemplate);
-                if (policy.subscriberId != null) {
-                    out.attribute(null, ATTR_SUBSCRIBER_ID, policy.subscriberId);
+                writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
+                final String subscriberId = template.getSubscriberId();
+                if (subscriberId != null) {
+                    out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
                 }
                 writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
                 writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 0a84bc7..54a806a 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.SHUTDOWN;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
@@ -45,10 +46,12 @@
 import android.content.pm.ApplicationInfo;
 import android.net.IConnectivityManager;
 import android.net.INetworkStatsService;
+import android.net.NetworkIdentity;
 import android.net.NetworkInfo;
 import android.net.NetworkState;
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -63,8 +66,8 @@
 import android.util.TrustedTime;
 
 import com.android.internal.os.AtomicFile;
-import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -76,9 +79,9 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.ProtocolException;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 
 import libcore.io.IoUtils;
 
@@ -93,7 +96,9 @@
 
     /** File header magic number: "ANET" */
     private static final int FILE_MAGIC = 0x414E4554;
-    private static final int VERSION_CURRENT = 1;
+    private static final int VERSION_NETWORK_INIT = 1;
+    private static final int VERSION_UID_INIT = 1;
+    private static final int VERSION_UID_WITH_IDENT = 2;
 
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
@@ -112,6 +117,7 @@
     private PendingIntent mPollIntent;
 
     // TODO: listen for kernel push events through netd instead of polling
+    // TODO: watch for UID uninstall, and transfer stats into single bucket
 
     private static final long KB_IN_BYTES = 1024;
     private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
@@ -132,13 +138,13 @@
 
     private final Object mStatsLock = new Object();
 
-    /** Set of active ifaces during this boot. */
-    private HashMap<String, InterfaceIdentity> mActiveIface = Maps.newHashMap();
-
-    /** Set of historical stats for known ifaces. */
-    private HashMap<InterfaceIdentity, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
+    /** Set of currently active ifaces. */
+    private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
+    /** Set of historical stats for known networks. */
+    private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
     /** Set of historical stats for known UIDs. */
-    private SparseArray<NetworkStatsHistory> mUidStats = new SparseArray<NetworkStatsHistory>();
+    private HashMap<NetworkIdentitySet, SparseArray<NetworkStatsHistory>> mUidStats =
+            Maps.newHashMap();
 
     /** Flag if {@link #mUidStats} have been loaded from disk. */
     private boolean mUidStatsLoaded = false;
@@ -251,17 +257,16 @@
     }
 
     @Override
-    public NetworkStatsHistory getHistoryForNetwork(int networkTemplate) {
+    public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
             // combine all interfaces that match template
-            final String subscriberId = getActiveSubscriberId();
             final NetworkStatsHistory combined = new NetworkStatsHistory(
                     mSettings.getNetworkBucketDuration(), estimateNetworkBuckets());
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory history = mNetworkStats.get(ident);
-                if (ident.matchesTemplate(networkTemplate, subscriberId)) {
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final NetworkStatsHistory history = mNetworkStats.get(ident);
                     combined.recordEntireHistory(history);
                 }
             }
@@ -270,19 +275,29 @@
     }
 
     @Override
-    public NetworkStatsHistory getHistoryForUid(int uid, int networkTemplate) {
+    public NetworkStatsHistory getHistoryForUid(NetworkTemplate template, int uid) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
-            // TODO: combine based on template, if we store that granularity
             ensureUidStatsLoadedLocked();
-            return mUidStats.get(uid);
+
+            // combine all interfaces that match template
+            final NetworkStatsHistory combined = new NetworkStatsHistory(
+                    mSettings.getUidBucketDuration(), estimateUidBuckets());
+            for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final NetworkStatsHistory history = mUidStats.get(ident).get(uid);
+                    if (history != null) {
+                        combined.recordEntireHistory(history);
+                    }
+                }
+            }
+            return combined;
         }
     }
 
     @Override
-    public NetworkStats getSummaryForNetwork(
-            long start, long end, int networkTemplate, String subscriberId) {
+    public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
@@ -291,9 +306,9 @@
             long[] networkTotal = new long[2];
 
             // combine total from all interfaces that match template
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory history = mNetworkStats.get(ident);
-                if (ident.matchesTemplate(networkTemplate, subscriberId)) {
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final NetworkStatsHistory history = mNetworkStats.get(ident);
                     networkTotal = history.getTotalData(start, end, networkTotal);
                     rx += networkTotal[0];
                     tx += networkTotal[1];
@@ -301,30 +316,33 @@
             }
 
             final NetworkStats stats = new NetworkStats(end - start, 1);
-            stats.addEntry(IFACE_ALL, UID_ALL, rx, tx);
+            stats.addEntry(IFACE_ALL, UID_ALL, TAG_NONE, rx, tx);
             return stats;
         }
     }
 
     @Override
-    public NetworkStats getSummaryForAllUid(long start, long end, int networkTemplate) {
+    public NetworkStats getSummaryForAllUid(NetworkTemplate template, long start, long end) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
-        // TODO: apply networktemplate once granular uid stats are stored.
-
         synchronized (mStatsLock) {
             ensureUidStatsLoadedLocked();
 
-            final int size = mUidStats.size();
-            final NetworkStats stats = new NetworkStats(end - start, size);
-
+            final NetworkStats stats = new NetworkStats(end - start, 24);
             long[] total = new long[2];
-            for (int i = 0; i < size; i++) {
-                final int uid = mUidStats.keyAt(i);
-                final NetworkStatsHistory history = mUidStats.valueAt(i);
-                total = history.getTotalData(start, end, total);
-                stats.addEntry(IFACE_ALL, uid, total[0], total[1]);
+
+            for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                if (templateMatches(template, ident)) {
+                    final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                    for (int i = 0; i < uidStats.size(); i++) {
+                        final int uid = uidStats.keyAt(i);
+                        final NetworkStatsHistory history = uidStats.valueAt(i);
+                        total = history.getTotalData(start, end, total);
+                        stats.combineEntry(IFACE_ALL, uid, TAG_NONE, total[0], total[1]);
+                    }
+                }
             }
+
             return stats;
         }
     }
@@ -352,7 +370,7 @@
             // permission above.
             synchronized (mStatsLock) {
                 // TODO: acquire wakelock while performing poll
-                performPollLocked(true);
+                performPollLocked(true, false);
             }
         }
     };
@@ -371,7 +389,7 @@
      * Inspect all current {@link NetworkState} to derive mapping from {@code
      * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
      * are active on a single {@code iface}, they are combined under a single
-     * {@link InterfaceIdentity}.
+     * {@link NetworkIdentitySet}.
      */
     private void updateIfacesLocked() {
         if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
@@ -379,7 +397,7 @@
         // take one last stats snapshot before updating iface mapping. this
         // isn't perfect, since the kernel may already be counting traffic from
         // the updated network.
-        performPollLocked(false);
+        performPollLocked(false, false);
 
         final NetworkState[] states;
         try {
@@ -390,13 +408,19 @@
         }
 
         // rebuild active interfaces based on connected networks
-        mActiveIface.clear();
+        mActiveIfaces.clear();
 
         for (NetworkState state : states) {
             if (state.networkInfo.isConnected()) {
                 // collect networks under their parent interfaces
                 final String iface = state.linkProperties.getInterfaceName();
-                final InterfaceIdentity ident = findOrCreateInterfaceLocked(iface);
+
+                NetworkIdentitySet ident = mActiveIfaces.get(iface);
+                if (ident == null) {
+                    ident = new NetworkIdentitySet();
+                    mActiveIfaces.put(iface, ident);
+                }
+
                 ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state));
             }
         }
@@ -409,7 +433,7 @@
      * @param detailedPoll Indicate if detailed UID stats should be collected
      *            during this poll operation.
      */
-    private void performPollLocked(boolean detailedPoll) {
+    private void performPollLocked(boolean detailedPoll, boolean forcePersist) {
         if (LOGV) Slog.v(TAG, "performPollLocked()");
 
         // try refreshing time source when stale
@@ -421,33 +445,33 @@
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
                 : System.currentTimeMillis();
 
-        final NetworkStats networkStats;
+        final NetworkStats ifaceStats;
         final NetworkStats uidStats;
         try {
-            networkStats = mNetworkManager.getNetworkStatsSummary();
+            ifaceStats = mNetworkManager.getNetworkStatsSummary();
             uidStats = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
         } catch (RemoteException e) {
             Slog.w(TAG, "problem reading network stats");
             return;
         }
 
-        performNetworkPollLocked(networkStats, currentTime);
+        performNetworkPollLocked(ifaceStats, currentTime);
         if (detailedPoll) {
             performUidPollLocked(uidStats, currentTime);
         }
 
         // decide if enough has changed to trigger persist
-        final NetworkStats persistDelta = computeStatsDelta(mLastNetworkPersist, networkStats);
+        final NetworkStats persistDelta = computeStatsDelta(mLastNetworkPersist, ifaceStats);
         final long persistThreshold = mSettings.getPersistThreshold();
         for (String iface : persistDelta.getUniqueIfaces()) {
-            final int index = persistDelta.findIndex(iface, UID_ALL);
-            if (persistDelta.rx[index] > persistThreshold
+            final int index = persistDelta.findIndex(iface, UID_ALL, TAG_NONE);
+            if (forcePersist || persistDelta.rx[index] > persistThreshold
                     || persistDelta.tx[index] > persistThreshold) {
                 writeNetworkStatsLocked();
                 if (mUidStatsLoaded) {
                     writeUidStatsLocked();
                 }
-                mLastNetworkPersist = networkStats;
+                mLastNetworkPersist = ifaceStats;
                 break;
             }
         }
@@ -462,23 +486,23 @@
      * Update {@link #mNetworkStats} historical usage.
      */
     private void performNetworkPollLocked(NetworkStats networkStats, long currentTime) {
-        final ArrayList<String> unknownIface = Lists.newArrayList();
+        final HashSet<String> unknownIface = Sets.newHashSet();
 
         final NetworkStats delta = computeStatsDelta(mLastNetworkPoll, networkStats);
         final long timeStart = currentTime - delta.elapsedRealtime;
         final long maxHistory = mSettings.getNetworkMaxHistory();
-        for (String iface : delta.getUniqueIfaces()) {
-            final InterfaceIdentity ident = mActiveIface.get(iface);
+        for (int i = 0; i < delta.size; i++) {
+            final String iface = delta.iface[i];
+            final NetworkIdentitySet ident = mActiveIfaces.get(iface);
             if (ident == null) {
                 unknownIface.add(iface);
                 continue;
             }
 
-            final int index = delta.findIndex(iface, UID_ALL);
-            final long rx = delta.rx[index];
-            final long tx = delta.tx[index];
+            final long rx = delta.rx[i];
+            final long tx = delta.tx[i];
 
-            final NetworkStatsHistory history = findOrCreateNetworkLocked(ident);
+            final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident);
             history.recordData(timeStart, currentTime, rx, tx);
             history.removeBucketsBefore(currentTime - maxHistory);
         }
@@ -498,22 +522,30 @@
         final NetworkStats delta = computeStatsDelta(mLastUidPoll, uidStats);
         final long timeStart = currentTime - delta.elapsedRealtime;
         final long maxHistory = mSettings.getUidMaxHistory();
-        for (int uid : delta.getUniqueUids()) {
-            // TODO: traverse all ifaces once surfaced in stats
-            final int index = delta.findIndex(IFACE_ALL, uid);
-            if (index != -1) {
-                final long rx = delta.rx[index];
-                final long tx = delta.tx[index];
 
-                final NetworkStatsHistory history = findOrCreateUidLocked(uid);
-                history.recordData(timeStart, currentTime, rx, tx);
-                history.removeBucketsBefore(currentTime - maxHistory);
+        // NOTE: historical UID stats ignore tags, and simply records all stats
+        // entries into a single UID bucket.
+
+        for (int i = 0; i < delta.size; i++) {
+            final String iface = delta.iface[i];
+            final NetworkIdentitySet ident = mActiveIfaces.get(iface);
+            if (ident == null) {
+                continue;
             }
+
+            final int uid = delta.uid[i];
+            final long rx = delta.rx[i];
+            final long tx = delta.tx[i];
+
+            final NetworkStatsHistory history = findOrCreateUidStatsLocked(ident, uid);
+            history.recordData(timeStart, currentTime, rx, tx);
+            history.removeBucketsBefore(currentTime - maxHistory);
         }
+
         mLastUidPoll = uidStats;
     }
 
-    private NetworkStatsHistory findOrCreateNetworkLocked(InterfaceIdentity ident) {
+    private NetworkStatsHistory findOrCreateNetworkStatsLocked(NetworkIdentitySet ident) {
         final long bucketDuration = mSettings.getNetworkBucketDuration();
         final NetworkStatsHistory existing = mNetworkStats.get(ident);
 
@@ -535,9 +567,16 @@
         }
     }
 
-    private NetworkStatsHistory findOrCreateUidLocked(int uid) {
+    private NetworkStatsHistory findOrCreateUidStatsLocked(NetworkIdentitySet ident, int uid) {
+        // find bucket for identity first, then find uid
+        SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+        if (uidStats == null) {
+            uidStats = new SparseArray<NetworkStatsHistory>();
+            mUidStats.put(ident, uidStats);
+        }
+
         final long bucketDuration = mSettings.getUidBucketDuration();
-        final NetworkStatsHistory existing = mUidStats.get(uid);
+        final NetworkStatsHistory existing = uidStats.get(uid);
 
         // update when no existing, or when bucket duration changed
         NetworkStatsHistory updated = null;
@@ -550,22 +589,13 @@
         }
 
         if (updated != null) {
-            mUidStats.put(uid, updated);
+            uidStats.put(uid, updated);
             return updated;
         } else {
             return existing;
         }
     }
 
-    private InterfaceIdentity findOrCreateInterfaceLocked(String iface) {
-        InterfaceIdentity ident = mActiveIface.get(iface);
-        if (ident == null) {
-            ident = new InterfaceIdentity();
-            mActiveIface.put(iface, ident);
-        }
-        return ident;
-    }
-
     private void readNetworkStatsLocked() {
         if (LOGV) Slog.v(TAG, "readNetworkStatsLocked()");
 
@@ -585,15 +615,12 @@
 
             final int version = in.readInt();
             switch (version) {
-                case VERSION_CURRENT: {
-                    // file format is pairs of interfaces and stats:
-                    // network := size *(InterfaceIdentity NetworkStatsHistory)
-
+                case VERSION_NETWORK_INIT: {
+                    // network := size *(NetworkIdentitySet NetworkStatsHistory)
                     final int size = in.readInt();
                     for (int i = 0; i < size; i++) {
-                        final InterfaceIdentity ident = new InterfaceIdentity(in);
+                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
-
                         mNetworkStats.put(ident, history);
                     }
                     break;
@@ -637,16 +664,29 @@
 
             final int version = in.readInt();
             switch (version) {
-                case VERSION_CURRENT: {
-                    // file format is pairs of UIDs and stats:
+                case VERSION_UID_INIT: {
                     // uid := size *(UID NetworkStatsHistory)
 
-                    final int size = in.readInt();
-                    for (int i = 0; i < size; i++) {
-                        final int uid = in.readInt();
-                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
+                    // drop this data version, since we don't have a good
+                    // mapping into NetworkIdentitySet.
+                    break;
+                }
+                case VERSION_UID_WITH_IDENT: {
+                    // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
+                    final int ifaceSize = in.readInt();
+                    for (int i = 0; i < ifaceSize; i++) {
+                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
 
-                        mUidStats.put(uid, history);
+                        final int uidSize = in.readInt();
+                        final SparseArray<NetworkStatsHistory> uidStats = new SparseArray<
+                                NetworkStatsHistory>(uidSize);
+                        for (int j = 0; j < uidSize; j++) {
+                            final int uid = in.readInt();
+                            final NetworkStatsHistory history = new NetworkStatsHistory(in);
+                            uidStats.put(uid, history);
+                        }
+
+                        mUidStats.put(ident, uidStats);
                     }
                     break;
                 }
@@ -674,10 +714,10 @@
             final DataOutputStream out = new DataOutputStream(fos);
 
             out.writeInt(FILE_MAGIC);
-            out.writeInt(VERSION_CURRENT);
+            out.writeInt(VERSION_NETWORK_INIT);
 
             out.writeInt(mNetworkStats.size());
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
                 final NetworkStatsHistory history = mNetworkStats.get(ident);
                 ident.writeToStream(out);
                 history.writeToStream(out);
@@ -702,16 +742,21 @@
             final DataOutputStream out = new DataOutputStream(fos);
 
             out.writeInt(FILE_MAGIC);
-            out.writeInt(VERSION_CURRENT);
+            out.writeInt(VERSION_UID_WITH_IDENT);
 
-            final int size = mUidStats.size();
+            out.writeInt(mUidStats.size());
+            for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                ident.writeToStream(out);
 
-            out.writeInt(size);
-            for (int i = 0; i < size; i++) {
-                final int uid = mUidStats.keyAt(i);
-                final NetworkStatsHistory history = mUidStats.valueAt(i);
-                out.writeInt(uid);
-                history.writeToStream(out);
+                final int size = uidStats.size();
+                out.writeInt(size);
+                for (int i = 0; i < size; i++) {
+                    final int uid = uidStats.keyAt(i);
+                    final NetworkStatsHistory history = uidStats.valueAt(i);
+                    out.writeInt(uid);
+                    history.writeToStream(out);
+                }
             }
 
             mUidFile.finishWrite(fos);
@@ -740,35 +785,41 @@
             }
 
             if (argSet.contains("poll")) {
-                performPollLocked(true);
+                performPollLocked(true, true);
                 pw.println("Forced poll");
                 return;
             }
 
             pw.println("Active interfaces:");
-            for (String iface : mActiveIface.keySet()) {
-                final InterfaceIdentity ident = mActiveIface.get(iface);
+            for (String iface : mActiveIfaces.keySet()) {
+                final NetworkIdentitySet ident = mActiveIfaces.get(iface);
                 pw.print("  iface="); pw.print(iface);
                 pw.print(" ident="); pw.println(ident.toString());
             }
 
             pw.println("Known historical stats:");
-            for (InterfaceIdentity ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory stats = mNetworkStats.get(ident);
+            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+                final NetworkStatsHistory history = mNetworkStats.get(ident);
                 pw.print("  ident="); pw.println(ident.toString());
-                stats.dump("    ", pw);
+                history.dump("  ", pw);
             }
 
             if (argSet.contains("detail")) {
                 // since explicitly requested with argument, we're okay to load
                 // from disk if not already in memory.
                 ensureUidStatsLoadedLocked();
-                pw.println("Known UID stats:");
-                for (int i = 0; i < mUidStats.size(); i++) {
-                    final int uid = mUidStats.keyAt(i);
-                    final NetworkStatsHistory stats = mUidStats.valueAt(i);
-                    pw.print("  UID="); pw.println(uid);
-                    stats.dump("    ", pw);
+
+                pw.println("Detailed UID stats:");
+                for (NetworkIdentitySet ident : mUidStats.keySet()) {
+                    pw.print("  ident="); pw.println(ident.toString());
+
+                    final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                    for (int i = 0; i < uidStats.size(); i++) {
+                        final int uid = uidStats.keyAt(i);
+                        final NetworkStatsHistory history = uidStats.valueAt(i);
+                        pw.print("    UID="); pw.println(uid);
+                        history.dump("    ", pw);
+                    }
                 }
             }
         }
@@ -779,27 +830,30 @@
      */
     @Deprecated
     private void generateRandomLocked() {
-        long end = System.currentTimeMillis();
-        long start = end - mSettings.getNetworkMaxHistory();
-        long rx = 3 * GB_IN_BYTES;
-        long tx = 2 * GB_IN_BYTES;
+        long networkEnd = System.currentTimeMillis();
+        long networkStart = networkEnd - mSettings.getNetworkMaxHistory();
+        long networkRx = 3 * GB_IN_BYTES;
+        long networkTx = 2 * GB_IN_BYTES;
+
+        long uidEnd = System.currentTimeMillis();
+        long uidStart = uidEnd - mSettings.getUidMaxHistory();
+        long uidRx = 500 * MB_IN_BYTES;
+        long uidTx = 100 * MB_IN_BYTES;
+
+        final List<ApplicationInfo> installedApps = mContext
+                .getPackageManager().getInstalledApplications(0);
 
         mNetworkStats.clear();
-        for (InterfaceIdentity ident : mActiveIface.values()) {
-            final NetworkStatsHistory stats = findOrCreateNetworkLocked(ident);
-            stats.generateRandom(start, end, rx, tx);
-        }
-
-        end = System.currentTimeMillis();
-        start = end - mSettings.getUidMaxHistory();
-        rx = 500 * MB_IN_BYTES;
-        tx = 100 * MB_IN_BYTES;
-
         mUidStats.clear();
-        for (ApplicationInfo info : mContext.getPackageManager().getInstalledApplications(0)) {
-            final int uid = info.uid;
-            final NetworkStatsHistory stats = findOrCreateUidLocked(uid);
-            stats.generateRandom(start, end, rx, tx);
+        for (NetworkIdentitySet ident : mActiveIfaces.values()) {
+            findOrCreateNetworkStatsLocked(ident).generateRandom(
+                    networkStart, networkEnd, networkRx, networkTx);
+
+            for (ApplicationInfo info : installedApps) {
+                final int uid = info.uid;
+                findOrCreateUidStatsLocked(ident, uid).generateRandom(
+                        uidStart, uidEnd, uidRx, uidTx);
+            }
         }
     }
 
@@ -815,12 +869,6 @@
         }
     }
 
-    private String getActiveSubscriberId() {
-        final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        return telephony.getSubscriberId();
-    }
-
     private int estimateNetworkBuckets() {
         return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration());
     }
@@ -834,6 +882,19 @@
     }
 
     /**
+     * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
+     * in the given {@link NetworkIdentitySet}.
+     */
+    private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
+        for (NetworkIdentity ident : identSet) {
+            if (template.matches(ident)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Default external settings that read from {@link Settings.Secure}.
      */
     private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {