Persist "tagged" network stats along with UIDs.

Now stores tags other than TAG_NONE (0x0), which are useful for app
debugging.  Combine UID and tag together into single long key, and
expose tag data through AIDL when requested.  Change NMS to track
TAG_NONE as total UID traffic, matching the kernel definition.

Added TAG_MAX_HISTORY to control how long tag-granularity statistics
are stored; overall UID usage is still kept for UID_MAX_HISTORY.  Fix
bug to trim NetworkStatsHistory outside normal polling loops to catch
non-active networks and UIDs.

Test to verify UID and tag packing, and to verify that UID traffic on
two networks are combined to match MOBILE_ALL template.

Change-Id: If0e039416d9e7f63b1a39e04cddfb1133b5a78ee
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 6371500..ae9aa05 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -26,11 +26,11 @@
     /** Return historical stats for traffic that matches template. */
     NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template);
     /** Return historical stats for specific UID traffic that matches template. */
-    NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid);
+    NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int tag);
 
     /** Return usage summary for traffic that matches template. */
     NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
     /** Return usage summary per UID for traffic that matches template. */
-    NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end);
+    NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
 
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 01e028e7..603edf0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3800,6 +3800,8 @@
         public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
         /** {@hide} */
         public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
+        /** {@hide} */
+        public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
 
         /**
          * @hide
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index d5bdd21..d6704f4 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -965,14 +965,9 @@
                     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);
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 79612e3..043a581 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -31,6 +31,7 @@
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
 import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
+import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -63,9 +64,9 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.util.LongSparseArray;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.TrustedTime;
 
 import com.android.internal.os.AtomicFile;
@@ -102,6 +103,7 @@
     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 static final int VERSION_UID_WITH_TAG = 3;
 
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
@@ -122,6 +124,8 @@
     // TODO: listen for kernel push events through netd instead of polling
     // TODO: watch for UID uninstall, and transfer stats into single bucket
 
+    // TODO: trim empty history objects entirely
+
     private static final long KB_IN_BYTES = 1024;
     private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
     private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
@@ -136,6 +140,7 @@
         public long getNetworkMaxHistory();
         public long getUidBucketDuration();
         public long getUidMaxHistory();
+        public long getTagMaxHistory();
         public long getTimeCacheMaxAge();
     }
 
@@ -146,16 +151,16 @@
     /** Set of historical stats for known networks. */
     private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
     /** Set of historical stats for known UIDs. */
-    private HashMap<NetworkIdentitySet, SparseArray<NetworkStatsHistory>> mUidStats =
+    private HashMap<NetworkIdentitySet, LongSparseArray<NetworkStatsHistory>> mUidStats =
             Maps.newHashMap();
 
     /** Flag if {@link #mUidStats} have been loaded from disk. */
     private boolean mUidStatsLoaded = false;
 
-    private NetworkStats mLastNetworkPoll;
-    private NetworkStats mLastNetworkPersist;
+    private NetworkStats mLastNetworkSnapshot;
+    private NetworkStats mLastPersistNetworkSnapshot;
 
-    private NetworkStats mLastUidPoll;
+    private NetworkStats mLastUidSnapshot;
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
@@ -274,7 +279,9 @@
             for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
                 if (templateMatches(template, ident)) {
                     final NetworkStatsHistory history = mNetworkStats.get(ident);
-                    combined.recordEntireHistory(history);
+                    if (history != null) {
+                        combined.recordEntireHistory(history);
+                    }
                 }
             }
             return combined;
@@ -282,18 +289,19 @@
     }
 
     @Override
-    public NetworkStatsHistory getHistoryForUid(NetworkTemplate template, int uid) {
+    public NetworkStatsHistory getHistoryForUid(NetworkTemplate template, int uid, int tag) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
             ensureUidStatsLoadedLocked();
+            final long packed = packUidAndTag(uid, tag);
 
             // 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);
+                    final NetworkStatsHistory history = mUidStats.get(ident).get(packed);
                     if (history != null) {
                         combined.recordEntireHistory(history);
                     }
@@ -329,7 +337,8 @@
     }
 
     @Override
-    public NetworkStats getSummaryForAllUid(NetworkTemplate template, long start, long end) {
+    public NetworkStats getSummaryForAllUid(
+            NetworkTemplate template, long start, long end, boolean includeTags) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
@@ -340,12 +349,23 @@
 
             for (NetworkIdentitySet ident : mUidStats.keySet()) {
                 if (templateMatches(template, ident)) {
-                    final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                    final LongSparseArray<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]);
+                        final long packed = uidStats.keyAt(i);
+                        final int uid = unpackUid(packed);
+                        final int tag = unpackTag(packed);
+
+                        // always include summary under TAG_NONE, and include
+                        // other tags when requested.
+                        if (tag == TAG_NONE || includeTags) {
+                            final NetworkStatsHistory history = uidStats.valueAt(i);
+                            total = history.getTotalData(start, end, total);
+                            final long rx = total[0];
+                            final long tx = total[1];
+                            if (rx > 0 || tx > 0) {
+                                stats.combineEntry(IFACE_ALL, uid, tag, rx, tx);
+                            }
+                        }
                     }
                 }
             }
@@ -464,23 +484,24 @@
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
                 : System.currentTimeMillis();
 
-        final NetworkStats ifaceStats;
-        final NetworkStats uidStats;
+        final NetworkStats networkSnapshot;
+        final NetworkStats uidSnapshot;
         try {
-            ifaceStats = mNetworkManager.getNetworkStatsSummary();
-            uidStats = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
+            networkSnapshot = mNetworkManager.getNetworkStatsSummary();
+            uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
         } catch (RemoteException e) {
             Slog.w(TAG, "problem reading network stats");
             return;
         }
 
-        performNetworkPollLocked(ifaceStats, currentTime);
+        performNetworkPollLocked(networkSnapshot, currentTime);
         if (detailedPoll) {
-            performUidPollLocked(uidStats, currentTime);
+            performUidPollLocked(uidSnapshot, currentTime);
         }
 
         // decide if enough has changed to trigger persist
-        final NetworkStats persistDelta = computeStatsDelta(mLastNetworkPersist, ifaceStats);
+        final NetworkStats persistDelta = computeStatsDelta(
+                mLastPersistNetworkSnapshot, networkSnapshot);
         final long persistThreshold = mSettings.getPersistThreshold();
         for (String iface : persistDelta.getUniqueIfaces()) {
             final int index = persistDelta.findIndex(iface, UID_ALL, TAG_NONE);
@@ -490,7 +511,7 @@
                 if (mUidStatsLoaded) {
                     writeUidStatsLocked();
                 }
-                mLastNetworkPersist = ifaceStats;
+                mLastPersistNetworkSnapshot = networkSnapshot;
                 break;
             }
         }
@@ -504,12 +525,11 @@
     /**
      * Update {@link #mNetworkStats} historical usage.
      */
-    private void performNetworkPollLocked(NetworkStats networkStats, long currentTime) {
+    private void performNetworkPollLocked(NetworkStats networkSnapshot, long currentTime) {
         final HashSet<String> unknownIface = Sets.newHashSet();
 
-        final NetworkStats delta = computeStatsDelta(mLastNetworkPoll, networkStats);
+        final NetworkStats delta = computeStatsDelta(mLastNetworkSnapshot, networkSnapshot);
         final long timeStart = currentTime - delta.elapsedRealtime;
-        final long maxHistory = mSettings.getNetworkMaxHistory();
         for (int i = 0; i < delta.size; i++) {
             final String iface = delta.iface[i];
             final NetworkIdentitySet ident = mActiveIfaces.get(iface);
@@ -523,9 +543,15 @@
 
             final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident);
             history.recordData(timeStart, currentTime, rx, tx);
+        }
+
+        // trim any history beyond max
+        final long maxHistory = mSettings.getNetworkMaxHistory();
+        for (NetworkStatsHistory history : mNetworkStats.values()) {
             history.removeBucketsBefore(currentTime - maxHistory);
         }
-        mLastNetworkPoll = networkStats;
+
+        mLastNetworkSnapshot = networkSnapshot;
 
         if (LOGD && unknownIface.size() > 0) {
             Slog.w(TAG, "unknown interfaces " + unknownIface.toString() + ", ignoring those stats");
@@ -535,15 +561,11 @@
     /**
      * Update {@link #mUidStats} historical usage.
      */
-    private void performUidPollLocked(NetworkStats uidStats, long currentTime) {
+    private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
         ensureUidStatsLoadedLocked();
 
-        final NetworkStats delta = computeStatsDelta(mLastUidPoll, uidStats);
+        final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot);
         final long timeStart = currentTime - delta.elapsedRealtime;
-        final long maxHistory = mSettings.getUidMaxHistory();
-
-        // 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];
@@ -553,15 +575,32 @@
             }
 
             final int uid = delta.uid[i];
+            final int tag = delta.tag[i];
             final long rx = delta.rx[i];
             final long tx = delta.tx[i];
 
-            final NetworkStatsHistory history = findOrCreateUidStatsLocked(ident, uid);
+            final NetworkStatsHistory history = findOrCreateUidStatsLocked(ident, uid, tag);
             history.recordData(timeStart, currentTime, rx, tx);
-            history.removeBucketsBefore(currentTime - maxHistory);
         }
 
-        mLastUidPoll = uidStats;
+        // trim any history beyond max
+        final long maxUidHistory = mSettings.getUidMaxHistory();
+        final long maxTagHistory = mSettings.getTagMaxHistory();
+        for (LongSparseArray<NetworkStatsHistory> uidStats : mUidStats.values()) {
+            for (int i = 0; i < uidStats.size(); i++) {
+                final long packed = uidStats.keyAt(i);
+                final NetworkStatsHistory history = uidStats.valueAt(i);
+
+                // detailed tags are trimmed sooner than summary in TAG_NONE
+                if (unpackTag(packed) == TAG_NONE) {
+                    history.removeBucketsBefore(currentTime - maxUidHistory);
+                } else {
+                    history.removeBucketsBefore(currentTime - maxTagHistory);
+                }
+            }
+        }
+
+        mLastUidSnapshot = uidSnapshot;
     }
 
     /**
@@ -572,13 +611,19 @@
 
         // migrate all UID stats into special "removed" bucket
         for (NetworkIdentitySet ident : mUidStats.keySet()) {
-            final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
-            final NetworkStatsHistory uidHistory = uidStats.get(uid);
-            if (uidHistory != null) {
-                final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked(
-                        ident, UID_REMOVED);
-                removedHistory.recordEntireHistory(uidHistory);
-                uidStats.remove(uid);
+            final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+            for (int i = 0; i < uidStats.size(); i++) {
+                final long packed = uidStats.keyAt(i);
+                if (unpackUid(packed) == uid) {
+                    // only migrate combined TAG_NONE history
+                    if (unpackTag(packed) == TAG_NONE) {
+                        final NetworkStatsHistory uidHistory = uidStats.valueAt(i);
+                        final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked(
+                                ident, UID_REMOVED, TAG_NONE);
+                        removedHistory.recordEntireHistory(uidHistory);
+                    }
+                    uidStats.remove(packed);
+                }
             }
         }
 
@@ -590,10 +635,10 @@
     }
 
     private NetworkStatsHistory findOrCreateNetworkStatsLocked(NetworkIdentitySet ident) {
-        final long bucketDuration = mSettings.getNetworkBucketDuration();
         final NetworkStatsHistory existing = mNetworkStats.get(ident);
 
         // update when no existing, or when bucket duration changed
+        final long bucketDuration = mSettings.getNetworkBucketDuration();
         NetworkStatsHistory updated = null;
         if (existing == null) {
             updated = new NetworkStatsHistory(bucketDuration, 10);
@@ -611,20 +656,21 @@
         }
     }
 
-    private NetworkStatsHistory findOrCreateUidStatsLocked(NetworkIdentitySet ident, int uid) {
+    private NetworkStatsHistory findOrCreateUidStatsLocked(
+            NetworkIdentitySet ident, int uid, int tag) {
         ensureUidStatsLoadedLocked();
 
-        // find bucket for identity first, then find uid
-        SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+        LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
         if (uidStats == null) {
-            uidStats = new SparseArray<NetworkStatsHistory>();
+            uidStats = new LongSparseArray<NetworkStatsHistory>();
             mUidStats.put(ident, uidStats);
         }
 
-        final long bucketDuration = mSettings.getUidBucketDuration();
-        final NetworkStatsHistory existing = uidStats.get(uid);
+        final long packed = packUidAndTag(uid, tag);
+        final NetworkStatsHistory existing = uidStats.get(packed);
 
         // update when no existing, or when bucket duration changed
+        final long bucketDuration = mSettings.getUidBucketDuration();
         NetworkStatsHistory updated = null;
         if (existing == null) {
             updated = new NetworkStatsHistory(bucketDuration, 10);
@@ -635,7 +681,7 @@
         }
 
         if (updated != null) {
-            uidStats.put(uid, updated);
+            uidStats.put(packed, updated);
             return updated;
         } else {
             return existing;
@@ -719,17 +765,27 @@
                 }
                 case VERSION_UID_WITH_IDENT: {
                     // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
+
+                    // drop this data version, since this version only existed
+                    // for a short time.
+                    break;
+                }
+                case VERSION_UID_WITH_TAG: {
+                    // uid := size *(NetworkIdentitySet size *(UID tag NetworkStatsHistory))
                     final int ifaceSize = in.readInt();
                     for (int i = 0; i < ifaceSize; i++) {
                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
 
-                        final int uidSize = in.readInt();
-                        final SparseArray<NetworkStatsHistory> uidStats = new SparseArray<
-                                NetworkStatsHistory>(uidSize);
-                        for (int j = 0; j < uidSize; j++) {
+                        final int childSize = in.readInt();
+                        final LongSparseArray<NetworkStatsHistory> uidStats = new LongSparseArray<
+                                NetworkStatsHistory>(childSize);
+                        for (int j = 0; j < childSize; j++) {
                             final int uid = in.readInt();
+                            final int tag = in.readInt();
+                            final long packed = packUidAndTag(uid, tag);
+
                             final NetworkStatsHistory history = new NetworkStatsHistory(in);
-                            uidStats.put(uid, history);
+                            uidStats.put(packed, history);
                         }
 
                         mUidStats.put(ident, uidStats);
@@ -793,19 +849,23 @@
             final DataOutputStream out = new DataOutputStream(fos);
 
             out.writeInt(FILE_MAGIC);
-            out.writeInt(VERSION_UID_WITH_IDENT);
+            out.writeInt(VERSION_UID_WITH_TAG);
 
-            out.writeInt(mUidStats.size());
+            final int size = mUidStats.size();
+            out.writeInt(size);
             for (NetworkIdentitySet ident : mUidStats.keySet()) {
-                final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
                 ident.writeToStream(out);
 
-                final int size = uidStats.size();
-                out.writeInt(size);
-                for (int i = 0; i < size; i++) {
-                    final int uid = uidStats.keyAt(i);
+                final int childSize = uidStats.size();
+                out.writeInt(childSize);
+                for (int i = 0; i < childSize; i++) {
+                    final long packed = uidStats.keyAt(i);
+                    final int uid = unpackUid(packed);
+                    final int tag = unpackTag(packed);
                     final NetworkStatsHistory history = uidStats.valueAt(i);
                     out.writeInt(uid);
+                    out.writeInt(tag);
                     history.writeToStream(out);
                 }
             }
@@ -864,11 +924,14 @@
                 for (NetworkIdentitySet ident : mUidStats.keySet()) {
                     pw.print("  ident="); pw.println(ident.toString());
 
-                    final SparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
+                    final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
                     for (int i = 0; i < uidStats.size(); i++) {
-                        final int uid = uidStats.keyAt(i);
+                        final long packed = uidStats.keyAt(i);
+                        final int uid = unpackUid(packed);
+                        final int tag = unpackTag(packed);
                         final NetworkStatsHistory history = uidStats.valueAt(i);
-                        pw.print("    UID="); pw.println(uid);
+                        pw.print("    UID="); pw.print(uid);
+                        pw.print(" tag="); pw.println(tag);
                         history.dump("    ", pw);
                     }
                 }
@@ -902,7 +965,7 @@
 
             for (ApplicationInfo info : installedApps) {
                 final int uid = info.uid;
-                findOrCreateUidStatsLocked(ident, uid).generateRandom(
+                findOrCreateUidStatsLocked(ident, uid, TAG_NONE).generateRandom(
                         uidStart, uidEnd, uidRx, uidTx);
             }
         }
@@ -932,6 +995,23 @@
         return (int) (existing.bucketCount * existing.bucketDuration / newBucketDuration);
     }
 
+    // @VisibleForTesting
+    public static long packUidAndTag(int uid, int tag) {
+        final long uidLong = uid;
+        final long tagLong = tag;
+        return (uidLong << 32) | (tagLong & 0xFFFFFFFFL);
+    }
+
+    // @VisibleForTesting
+    public static int unpackUid(long packed) {
+        return (int) (packed >> 32);
+    }
+
+    // @VisibleForTesting
+    public static int unpackTag(long packed) {
+        return (int) (packed & 0xFFFFFFFFL);
+    }
+
     /**
      * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
      * in the given {@link NetworkIdentitySet}.
@@ -978,6 +1058,9 @@
         public long getUidMaxHistory() {
             return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS);
         }
+        public long getTagMaxHistory() {
+            return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS);
+        }
         public long getTimeCacheMaxAge() {
             return DAY_IN_MILLIS;
         }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 2c6dbbf..636d059 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -21,6 +21,8 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
@@ -31,6 +33,9 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static com.android.server.net.NetworkStatsService.packUidAndTag;
+import static com.android.server.net.NetworkStatsService.unpackTag;
+import static com.android.server.net.NetworkStatsService.unpackUid;
 import static org.easymock.EasyMock.anyLong;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.eq;
@@ -54,6 +59,7 @@
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
 import android.util.TrustedTime;
 
 import com.android.server.net.NetworkStatsService;
@@ -80,9 +86,9 @@
     private static NetworkTemplate sTemplateImsi1 = new NetworkTemplate(MATCH_MOBILE_ALL, IMSI_1);
     private static NetworkTemplate sTemplateImsi2 = new NetworkTemplate(MATCH_MOBILE_ALL, IMSI_2);
 
-    private static final int TEST_UID_RED = 1001;
-    private static final int TEST_UID_BLUE = 1002;
-    private static final int TEST_UID_GREEN = 1003;
+    private static final int UID_RED = 1001;
+    private static final int UID_BLUE = 1002;
+    private static final int UID_GREEN = 1003;
 
     private BroadcastInterceptingContext mServiceContext;
     private File mStatsDir;
@@ -216,16 +222,16 @@
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
                 .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 1024L, 2048L));
         expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 2)
-                .addEntry(TEST_IFACE, TEST_UID_RED, TAG_NONE, 512L, 256L)
-                .addEntry(TEST_IFACE, TEST_UID_BLUE, TAG_NONE, 128L, 128L));
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 512L, 256L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 128L, 128L));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 1024L, 2048L);
-        assertUidTotal(sTemplateWifi, TEST_UID_RED, 512L, 256L);
-        assertUidTotal(sTemplateWifi, TEST_UID_BLUE, 128L, 128L);
+        assertUidTotal(sTemplateWifi, UID_RED, 512L, 256L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 128L);
         verifyAndReset();
 
         // graceful shutdown system, which should trigger persist of stats, and
@@ -250,8 +256,8 @@
 
         // after systemReady(), we should have historical stats loaded again
         assertNetworkTotal(sTemplateWifi, 1024L, 2048L);
-        assertUidTotal(sTemplateWifi, TEST_UID_RED, 512L, 256L);
-        assertUidTotal(sTemplateWifi, TEST_UID_BLUE, 128L, 128L);
+        assertUidTotal(sTemplateWifi, UID_RED, 512L, 256L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 128L, 128L);
         verifyAndReset();
 
     }
@@ -335,9 +341,9 @@
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
                 .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 2048L, 512L));
         expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 3)
-                .addEntry(TEST_IFACE, TEST_UID_RED, TAG_NONE, 1024L, 0L)
-                .addEntry(TEST_IFACE, TEST_UID_RED, 0xF00D, 512L, 512L)
-                .addEntry(TEST_IFACE, TEST_UID_BLUE, TAG_NONE, 512L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 1536L, 512L)
+                .addEntry(TEST_IFACE, UID_RED, 0xF00D, 512L, 512L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 512L, 0L));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
@@ -345,8 +351,8 @@
         // verify service recorded history
         assertNetworkTotal(sTemplateImsi1, 2048L, 512L);
         assertNetworkTotal(sTemplateWifi, 0L, 0L);
-        assertUidTotal(sTemplateImsi1, TEST_UID_RED, 1536L, 512L);
-        assertUidTotal(sTemplateImsi1, TEST_UID_BLUE, 512L, 0L);
+        assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 512L);
+        assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 0L);
         verifyAndReset();
 
         // now switch networks; this also tests that we're okay with interfaces
@@ -370,21 +376,21 @@
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
                 .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 128L, 1024L));
         expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
-                .addEntry(TEST_IFACE, TEST_UID_BLUE, TAG_NONE, 128L, 1024L));
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 128L, 1024L));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify original history still intact
         assertNetworkTotal(sTemplateImsi1, 2048L, 512L);
-        assertUidTotal(sTemplateImsi1, TEST_UID_RED, 1536L, 512L);
-        assertUidTotal(sTemplateImsi1, TEST_UID_BLUE, 512L, 0L);
+        assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 512L);
+        assertUidTotal(sTemplateImsi1, UID_BLUE, 512L, 0L);
 
         // and verify new history also recorded under different template, which
         // verifies that we didn't cross the streams.
         assertNetworkTotal(sTemplateImsi2, 128L, 1024L);
         assertNetworkTotal(sTemplateWifi, 0L, 0L);
-        assertUidTotal(sTemplateImsi2, TEST_UID_BLUE, 128L, 1024L);
+        assertUidTotal(sTemplateImsi2, UID_BLUE, 128L, 1024L);
         verifyAndReset();
 
     }
@@ -409,18 +415,18 @@
         expectNetworkStatsSummary(new NetworkStats(elapsedRealtime, 1)
                 .addEntry(TEST_IFACE, UID_ALL, TAG_NONE, 4128L, 544L));
         expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
-                .addEntry(TEST_IFACE, TEST_UID_RED, TAG_NONE, 16L, 16L)
-                .addEntry(TEST_IFACE, TEST_UID_BLUE, TAG_NONE, 4096L, 512L)
-                .addEntry(TEST_IFACE, TEST_UID_GREEN, TAG_NONE, 16L, 16L));
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 16L, 16L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 4096L, 512L)
+                .addEntry(TEST_IFACE, UID_GREEN, TAG_NONE, 16L, 16L));
 
         replay();
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify service recorded history
         assertNetworkTotal(sTemplateWifi, 4128L, 544L);
-        assertUidTotal(sTemplateWifi, TEST_UID_RED, 16L, 16L);
-        assertUidTotal(sTemplateWifi, TEST_UID_BLUE, 4096L, 512L);
-        assertUidTotal(sTemplateWifi, TEST_UID_GREEN, 16L, 16L);
+        assertUidTotal(sTemplateWifi, UID_RED, 16L, 16L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 4096L, 512L);
+        assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 16L);
         verifyAndReset();
 
         // now pretend two UIDs are uninstalled, which should migrate stats to
@@ -428,22 +434,162 @@
         expectDefaultSettings();
         replay();
         final Intent intent = new Intent(ACTION_UID_REMOVED);
-        intent.putExtra(EXTRA_UID, TEST_UID_BLUE);
+        intent.putExtra(EXTRA_UID, UID_BLUE);
         mServiceContext.sendBroadcast(intent);
-        intent.putExtra(EXTRA_UID, TEST_UID_RED);
+        intent.putExtra(EXTRA_UID, UID_RED);
         mServiceContext.sendBroadcast(intent);
 
         // existing uid and total should remain unchanged; but removed UID
         // should be gone completely.
         assertNetworkTotal(sTemplateWifi, 4128L, 544L);
-        assertUidTotal(sTemplateWifi, TEST_UID_RED, 0L, 0L);
-        assertUidTotal(sTemplateWifi, TEST_UID_BLUE, 0L, 0L);
-        assertUidTotal(sTemplateWifi, TEST_UID_GREEN, 16L, 16L);
+        assertUidTotal(sTemplateWifi, UID_RED, 0L, 0L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 0L, 0L);
+        assertUidTotal(sTemplateWifi, UID_GREEN, 16L, 16L);
         assertUidTotal(sTemplateWifi, UID_REMOVED, 4112L, 528L);
         verifyAndReset();
 
     }
 
+    public void testUid3g4gCombinedByTemplate() throws Exception {
+        long elapsedRealtime = 0;
+
+        // pretend that network comes online
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildMobile3gState(IMSI_1));
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        verifyAndReset();
+
+        // create some traffic
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 1024L, 1024L)
+                .addEntry(TEST_IFACE, UID_RED, 0xF00D, 512L, 512L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 1024L);
+        verifyAndReset();
+
+        // now switch over to 4g network
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildMobile4gState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+        verifyAndReset();
+
+        // create traffic on second network
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 512L, 256L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify that ALL_MOBILE template combines both
+        assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 1280L);
+
+        verifyAndReset();
+
+    }
+    
+    public void testPackedUidAndTag() throws Exception {
+        assertEquals(0x0000000000000000L, packUidAndTag(0, 0x0));
+        assertEquals(0x000003E900000000L, packUidAndTag(1001, 0x0));
+        assertEquals(0x000003E90000F00DL, packUidAndTag(1001, 0xF00D));
+
+        long packed;
+        packed = packUidAndTag(Integer.MAX_VALUE, Integer.MIN_VALUE);
+        assertEquals(Integer.MAX_VALUE, unpackUid(packed));
+        assertEquals(Integer.MIN_VALUE, unpackTag(packed));
+
+        packed = packUidAndTag(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        assertEquals(Integer.MIN_VALUE, unpackUid(packed));
+        assertEquals(Integer.MAX_VALUE, unpackTag(packed));
+
+        packed = packUidAndTag(10005, 0xFFFFFFFF);
+        assertEquals(10005, unpackUid(packed));
+        assertEquals(0xFFFFFFFF, unpackTag(packed));
+        
+    }
+
+    public void testSummaryForAllUid() throws Exception {
+        long elapsedRealtime = 0;
+
+        // pretend that network comes online
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkState(buildWifiState());
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+        verifyAndReset();
+
+        // create some traffic for two apps
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_RED, TAG_NONE, 50L, 50L)
+                .addEntry(TEST_IFACE, UID_RED, 0xF00D, 10L, 10L)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 1024L, 512L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        assertUidTotal(sTemplateWifi, UID_RED, 50L, 50L);
+        assertUidTotal(sTemplateWifi, UID_BLUE, 1024L, 512L);
+        verifyAndReset();
+        
+        // now create more traffic in next hour, but only for one app
+        elapsedRealtime += HOUR_IN_MILLIS;
+        expectTime(TEST_START + elapsedRealtime);
+        expectDefaultSettings();
+        expectNetworkStatsSummary(buildEmptyStats(elapsedRealtime));
+        expectNetworkStatsDetail(new NetworkStats(elapsedRealtime, 1)
+                .addEntry(TEST_IFACE, UID_BLUE, TAG_NONE, 2048L, 1024L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // first verify entire history present
+        NetworkStats stats = mService.getSummaryForAllUid(
+                sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(3, stats.size);
+        assertStatsEntry(stats, 0, IFACE_ALL, UID_RED, TAG_NONE, 50L, 50L);
+        assertStatsEntry(stats, 1, IFACE_ALL, UID_RED, 0xF00D, 10L, 10L);
+        assertStatsEntry(stats, 2, IFACE_ALL, UID_BLUE, TAG_NONE, 2048L, 1024L);
+
+        // now verify that recent history only contains one uid
+        final long currentTime = TEST_START + elapsedRealtime;
+        stats = mService.getSummaryForAllUid(
+                sTemplateWifi, currentTime - HOUR_IN_MILLIS, currentTime, true);
+        assertEquals(1, stats.size);
+        assertStatsEntry(stats, 0, IFACE_ALL, UID_BLUE, TAG_NONE, 1024L, 512L);
+
+        verifyAndReset();
+    }
+
     private void assertNetworkTotal(NetworkTemplate template, long rx, long tx) {
         final NetworkStatsHistory history = mService.getHistoryForNetwork(template);
         final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
@@ -452,7 +598,7 @@
     }
 
     private void assertUidTotal(NetworkTemplate template, int uid, long rx, long tx) {
-        final NetworkStatsHistory history = mService.getHistoryForUid(template, uid);
+        final NetworkStatsHistory history = mService.getHistoryForUid(template, uid, TAG_NONE);
         final long[] total = history.getTotalData(Long.MIN_VALUE, Long.MAX_VALUE, null);
         assertEquals(rx, total[0]);
         assertEquals(tx, total[1]);
@@ -491,6 +637,7 @@
         expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
         expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
         expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
+        expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
         expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
     }
 
@@ -514,6 +661,15 @@
         }
     }
 
+    private static void assertStatsEntry(
+            NetworkStats stats, int i, String iface, int uid, int tag, long rx, long tx) {
+        assertEquals(iface, stats.iface[i]);
+        assertEquals(uid, stats.uid[i]);
+        assertEquals(tag, stats.tag[i]);
+        assertEquals(rx, stats.rx[i]);
+        assertEquals(tx, stats.tx[i]);
+    }
+
     private static NetworkState buildWifiState() {
         final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
@@ -531,6 +687,14 @@
         return new NetworkState(info, prop, null, subscriberId);
     }
 
+    private static NetworkState buildMobile4gState() {
+        final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
+        info.setDetailedState(DetailedState.CONNECTED, null, null);
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(TEST_IFACE);
+        return new NetworkState(info, prop, null);
+    }
+
     private static NetworkStats buildEmptyStats(long elapsedRealtime) {
         return new NetworkStats(elapsedRealtime, 0);
     }