Track xtables summary, move tether stats, time.

Begin tracking xtables summary of data usage to compare with values
reported from /proc/net/dev.  Roll tethering directly into UID stats
to trigger UID stats persisting when crossing threshold.

Include xtables summary and authoritative time in samples.

Bug: 5373561, 5397882, 5381980
Change-Id: Ib7945522caadfbe0864fdf391582dc820f4f371e
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index f3be39c..e554975 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -272,6 +272,17 @@
     }
 
     /**
+     * Combine all values from another {@link NetworkStats} into this object.
+     */
+    public void combineAllValues(NetworkStats another) {
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < another.size; i++) {
+            entry = another.getValues(i, entry);
+            combineValues(entry);
+        }
+    }
+
+    /**
      * Find first stats index that matches the requested parameters.
      */
     public int findIndex(String iface, int uid, int set, int tag) {
@@ -456,6 +467,34 @@
         return result;
     }
 
+    /**
+     * Return total statistics grouped by {@link #iface}; doesn't mutate the
+     * original structure.
+     */
+    public NetworkStats groupedByIface() {
+        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+        final Entry entry = new Entry();
+        entry.uid = UID_ALL;
+        entry.set = SET_ALL;
+        entry.tag = TAG_NONE;
+        entry.operations = 0L;
+
+        for (int i = 0; i < size; i++) {
+            // skip specific tags, since already counted in TAG_NONE
+            if (tag[i] != TAG_NONE) continue;
+
+            entry.iface = iface[i];
+            entry.rxBytes = rxBytes[i];
+            entry.rxPackets = rxPackets[i];
+            entry.txBytes = txBytes[i];
+            entry.txPackets = txPackets[i];
+            stats.combineValues(entry);
+        }
+
+        return stats;
+    }
+
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index c36685d..d78d2ef 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -18,6 +18,8 @@
 
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 
@@ -176,8 +178,63 @@
         assertEquals(64L, uidTag.getTotalBytes());
     }
 
+    public void testGroupedByIfaceEmpty() throws Exception {
+        final NetworkStats uidStats = new NetworkStats(TEST_START, 3);
+        final NetworkStats grouped = uidStats.groupedByIface();
+
+        assertEquals(0, uidStats.size());
+        assertEquals(0, grouped.size());
+    }
+
+    public void testGroupedByIfaceAll() throws Exception {
+        final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
+                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, 128L, 8L, 0L, 2L, 20L);
+        final NetworkStats grouped = uidStats.groupedByIface();
+
+        assertEquals(2, uidStats.size());
+        assertEquals(1, grouped.size());
+
+        assertValues(grouped, 0, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, 256L, 16L, 0L, 4L, 0L);
+    }
+
+    public void testGroupedByIface() throws Exception {
+        final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 64L, 4L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 512L, 32L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 128L, 8L, 0L, 0L, 0L);
+
+        final NetworkStats grouped = uidStats.groupedByIface();
+
+        assertEquals(6, uidStats.size());
+
+        assertEquals(2, grouped.size());
+        assertValues(grouped, 0, TEST_IFACE, UID_ALL, SET_ALL, TAG_NONE, 256L, 16L, 0L, 2L, 0L);
+        assertValues(grouped, 1, TEST_IFACE2, UID_ALL, SET_ALL, TAG_NONE, 1024L, 64L, 0L, 0L, 0L);
+    }
+
+    public void testAddAllValues() {
+        final NetworkStats first = new NetworkStats(TEST_START, 5)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+
+        final NetworkStats second = new NetworkStats(TEST_START, 2)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+
+        first.combineAllValues(second);
+
+        assertEquals(3, first.size());
+        assertValues(first, 0, TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 64L, 0L, 0L, 0L, 0L);
+        assertValues(first, 1, TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+        assertValues(first, 2, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+    }
+
     private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
-            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, int operations) {
+            int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
         final NetworkStats.Entry entry = stats.getValues(index, null);
         assertEquals(iface, entry.iface);
         assertEquals(uid, entry.uid);
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 99f6b8e..a7eff93 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -142,5 +142,5 @@
 # ---------------------------
 # NetworkStatsService.java
 # ---------------------------
-51100 netstats_mobile_sample (iface_rx_bytes|2|2),(iface_tx_bytes|2|2),(iface_rx_pkts|2|1),(iface_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1)
-51101 netstats_wifi_sample (iface_rx_bytes|2|2),(iface_tx_bytes|2|2),(iface_rx_pkts|2|1),(iface_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1)
+51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index aacaa6a..6baf5fe 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.UID_TETHERING;
 import static android.net.NetworkStats.UID_ALL;
 import static android.provider.Settings.Secure.NETSTATS_ENABLED;
 import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
@@ -1522,7 +1523,7 @@
         try {
             final NetworkStats.Entry entry = new NetworkStats.Entry();
             entry.iface = ifaceIn;
-            entry.uid = UID_ALL;
+            entry.uid = UID_TETHERING;
             entry.set = SET_DEFAULT;
             entry.tag = TAG_NONE;
             entry.rxBytes = Long.parseLong(tok[3]);
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index aa46795..e5f0a77 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -35,7 +35,6 @@
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.UID_REMOVED;
-import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
 import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
 import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
@@ -72,7 +71,6 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
-import android.net.TrafficStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -140,11 +138,14 @@
     private static final int MSG_UPDATE_IFACES = 2;
 
     /** Flags to control detail level of poll event. */
-    private static final int FLAG_PERSIST_NETWORK = 0x10;
-    private static final int FLAG_PERSIST_UID = 0x20;
+    private static final int FLAG_PERSIST_NETWORK = 0x1;
+    private static final int FLAG_PERSIST_UID = 0x2;
     private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
     private static final int FLAG_PERSIST_FORCE = 0x100;
 
+    /** Sample recent usage after each poll event. */
+    private static final boolean ENABLE_SAMPLE_AFTER_POLL = true;
+
     private final Context mContext;
     private final INetworkManagementService mNetworkManager;
     private final IAlarmManager mAlarmManager;
@@ -188,20 +189,23 @@
 
     /** Set of currently active ifaces. */
     private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
-    /** Set of historical network layer stats for known networks. */
-    private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
-    /** Set of historical network layer stats for known UIDs. */
+    /** Set of historical {@code dev} stats for known networks. */
+    private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkDevStats = Maps.newHashMap();
+    /** Set of historical {@code xtables} stats for known networks. */
+    private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkXtStats = Maps.newHashMap();
+    /** Set of historical {@code xtables} stats for known UIDs. */
     private HashMap<UidStatsKey, NetworkStatsHistory> mUidStats = Maps.newHashMap();
 
     /** Flag if {@link #mUidStats} have been loaded from disk. */
     private boolean mUidStatsLoaded = false;
 
-    private NetworkStats mLastPollNetworkSnapshot;
+    private NetworkStats mLastPollNetworkDevSnapshot;
+    private NetworkStats mLastPollNetworkXtSnapshot;
     private NetworkStats mLastPollUidSnapshot;
     private NetworkStats mLastPollOperationsSnapshot;
-    private NetworkStats mLastPollTetherSnapshot;
 
-    private NetworkStats mLastPersistNetworkSnapshot;
+    private NetworkStats mLastPersistNetworkDevSnapshot;
+    private NetworkStats mLastPersistNetworkXtSnapshot;
     private NetworkStats mLastPersistUidSnapshot;
 
     /** Current counter sets for each UID. */
@@ -213,7 +217,8 @@
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
 
-    private final AtomicFile mNetworkFile;
+    private final AtomicFile mNetworkDevFile;
+    private final AtomicFile mNetworkXtFile;
     private final AtomicFile mUidFile;
 
     public NetworkStatsService(
@@ -244,7 +249,8 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
 
-        mNetworkFile = new AtomicFile(new File(systemDir, "netstats.bin"));
+        mNetworkDevFile = new AtomicFile(new File(systemDir, "netstats.bin"));
+        mNetworkXtFile = new AtomicFile(new File(systemDir, "netstats_xt.bin"));
         mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin"));
     }
 
@@ -257,7 +263,8 @@
             // read historical network stats from disk, since policy service
             // might need them right away. we delay loading detailed UID stats
             // until actually needed.
-            readNetworkStatsLocked();
+            readNetworkDevStatsLocked();
+            readNetworkXtStatsLocked();
         }
 
         // watch for network interfaces to be claimed
@@ -306,11 +313,13 @@
 
         mTeleManager.listen(mPhoneListener, LISTEN_NONE);
 
-        writeNetworkStatsLocked();
+        writeNetworkDevStatsLocked();
+        writeNetworkXtStatsLocked();
         if (mUidStatsLoaded) {
             writeUidStatsLocked();
         }
-        mNetworkStats.clear();
+        mNetworkDevStats.clear();
+        mNetworkXtStats.clear();
         mUidStats.clear();
         mUidStatsLoaded = false;
     }
@@ -355,14 +364,26 @@
     @Override
     public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+        return getHistoryForNetworkDev(template, fields);
+    }
 
+    private NetworkStatsHistory getHistoryForNetworkDev(NetworkTemplate template, int fields) {
+        return getHistoryForNetwork(template, fields, mNetworkDevStats);
+    }
+
+    private NetworkStatsHistory getHistoryForNetworkXt(NetworkTemplate template, int fields) {
+        return getHistoryForNetwork(template, fields, mNetworkXtStats);
+    }
+
+    private NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields,
+            HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
         synchronized (mStatsLock) {
             // combine all interfaces that match template
             final NetworkStatsHistory combined = new NetworkStatsHistory(
                     mSettings.getNetworkBucketDuration(), estimateNetworkBuckets(), fields);
-            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+            for (NetworkIdentitySet ident : source.keySet()) {
                 if (templateMatches(template, ident)) {
-                    final NetworkStatsHistory history = mNetworkStats.get(ident);
+                    final NetworkStatsHistory history = source.get(ident);
                     if (history != null) {
                         combined.recordEntireHistory(history);
                     }
@@ -399,7 +420,19 @@
     @Override
     public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+        return getSummaryForNetworkDev(template, start, end);
+    }
 
+    private NetworkStats getSummaryForNetworkDev(NetworkTemplate template, long start, long end) {
+        return getSummaryForNetwork(template, start, end, mNetworkDevStats);
+    }
+
+    private NetworkStats getSummaryForNetworkXt(NetworkTemplate template, long start, long end) {
+        return getSummaryForNetwork(template, start, end, mNetworkXtStats);
+    }
+
+    private NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end,
+            HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
         synchronized (mStatsLock) {
             // use system clock to be externally consistent
             final long now = System.currentTimeMillis();
@@ -409,9 +442,9 @@
             NetworkStatsHistory.Entry historyEntry = null;
 
             // combine total from all interfaces that match template
-            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
+            for (NetworkIdentitySet ident : source.keySet()) {
                 if (templateMatches(template, ident)) {
-                    final NetworkStatsHistory history = mNetworkStats.get(ident);
+                    final NetworkStatsHistory history = source.get(ident);
                     historyEntry = history.getValues(start, end, now, historyEntry);
 
                     entry.iface = IFACE_ALL;
@@ -716,8 +749,9 @@
      */
     private void bootstrapStats() {
         try {
-            mLastPollNetworkSnapshot = mNetworkManager.getNetworkStatsSummary();
             mLastPollUidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+            mLastPollNetworkDevSnapshot = mNetworkManager.getNetworkStatsSummary();
+            mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
             mLastPollOperationsSnapshot = new NetworkStats(0L, 0);
         } catch (IllegalStateException e) {
             Slog.w(TAG, "problem reading network stats: " + e);
@@ -759,42 +793,56 @@
                 : System.currentTimeMillis();
         final long threshold = mSettings.getPersistThreshold();
 
+        final NetworkStats uidSnapshot;
+        final NetworkStats networkXtSnapshot;
+        final NetworkStats networkDevSnapshot;
         try {
-            // record tethering stats; persisted during normal UID cycle below
-            final String[] ifacePairs = mConnManager.getTetheredIfacePairs();
+            // collect any tethering stats
+            final String[] tetheredIfacePairs = mConnManager.getTetheredIfacePairs();
             final NetworkStats tetherSnapshot = mNetworkManager.getNetworkStatsTethering(
-                    ifacePairs);
-            performTetherPollLocked(tetherSnapshot, currentTime);
+                    tetheredIfacePairs);
 
-            // record uid stats
-            final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+            // record uid stats, folding in tethering stats
+            uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+            uidSnapshot.combineAllValues(tetherSnapshot);
             performUidPollLocked(uidSnapshot, currentTime);
 
-            // persist when enough network data has occurred
-            final NetworkStats persistUidDelta = computeStatsDelta(
-                    mLastPersistUidSnapshot, uidSnapshot, true);
-            final boolean uidPastThreshold = persistUidDelta.getTotalBytes() > threshold;
-            if (persistForce || (persistUid && uidPastThreshold)) {
-                writeUidStatsLocked();
-                mLastPersistUidSnapshot = uidSnapshot;
-            }
+            // record dev network stats
+            networkDevSnapshot = mNetworkManager.getNetworkStatsSummary();
+            performNetworkDevPollLocked(networkDevSnapshot, currentTime);
 
-            // record network stats
-            final NetworkStats networkSnapshot = mNetworkManager.getNetworkStatsSummary();
-            performNetworkPollLocked(networkSnapshot, currentTime);
+            // record xt network stats
+            networkXtSnapshot = computeNetworkXtSnapshotFromUid(uidSnapshot);
+            performNetworkXtPollLocked(networkXtSnapshot, currentTime);
 
-            // persist when enough network data has occurred
-            final NetworkStats persistNetworkDelta = computeStatsDelta(
-                    mLastPersistNetworkSnapshot, networkSnapshot, true);
-            final boolean networkPastThreshold = persistNetworkDelta.getTotalBytes() > threshold;
-            if (persistForce || (persistNetwork && networkPastThreshold)) {
-                writeNetworkStatsLocked();
-                mLastPersistNetworkSnapshot = networkSnapshot;
-            }
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem reading network stats", e);
+            return;
         } catch (RemoteException e) {
             // ignored; service lives in system_server
+            return;
+        }
+
+        // persist when enough network data has occurred
+        final long persistNetworkDevDelta = computeStatsDelta(
+                mLastPersistNetworkDevSnapshot, networkDevSnapshot, true).getTotalBytes();
+        final long persistNetworkXtDelta = computeStatsDelta(
+                mLastPersistNetworkXtSnapshot, networkXtSnapshot, true).getTotalBytes();
+        final boolean networkOverThreshold = persistNetworkDevDelta > threshold
+                || persistNetworkXtDelta > threshold;
+        if (persistForce || (persistNetwork && networkOverThreshold)) {
+            writeNetworkDevStatsLocked();
+            writeNetworkXtStatsLocked();
+            mLastPersistNetworkDevSnapshot = networkDevSnapshot;
+            mLastPersistNetworkXtSnapshot = networkXtSnapshot;
+        }
+
+        // persist when enough uid data has occurred
+        final long persistUidDelta = computeStatsDelta(mLastPersistUidSnapshot, uidSnapshot, true)
+                .getTotalBytes();
+        if (persistForce || (persistUid && persistUidDelta > threshold)) {
+            writeUidStatsLocked();
+            mLastPersistUidSnapshot = uidSnapshot;
         }
 
         if (LOGV) {
@@ -802,8 +850,10 @@
             Slog.v(TAG, "performPollLocked() took " + duration + "ms");
         }
 
-        // sample stats after each full poll
-        performSample();
+        if (ENABLE_SAMPLE_AFTER_POLL) {
+            // sample stats after each full poll
+            performSample();
+        }
 
         // finally, dispatch updated event to any listeners
         final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
@@ -812,12 +862,13 @@
     }
 
     /**
-     * Update {@link #mNetworkStats} historical usage.
+     * Update {@link #mNetworkDevStats} historical usage.
      */
-    private void performNetworkPollLocked(NetworkStats networkSnapshot, long currentTime) {
+    private void performNetworkDevPollLocked(NetworkStats networkDevSnapshot, long currentTime) {
         final HashSet<String> unknownIface = Sets.newHashSet();
 
-        final NetworkStats delta = computeStatsDelta(mLastPollNetworkSnapshot, networkSnapshot, false);
+        final NetworkStats delta = computeStatsDelta(
+                mLastPollNetworkDevSnapshot, networkDevSnapshot, false);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -829,14 +880,44 @@
                 continue;
             }
 
-            final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident);
+            final NetworkStatsHistory history = findOrCreateNetworkDevStatsLocked(ident);
             history.recordData(timeStart, currentTime, entry);
         }
 
-        mLastPollNetworkSnapshot = networkSnapshot;
+        mLastPollNetworkDevSnapshot = networkDevSnapshot;
 
         if (LOGD && unknownIface.size() > 0) {
-            Slog.w(TAG, "unknown interfaces " + unknownIface.toString() + ", ignoring those stats");
+            Slog.w(TAG, "unknown dev interfaces " + unknownIface + ", ignoring those stats");
+        }
+    }
+
+    /**
+     * Update {@link #mNetworkXtStats} historical usage.
+     */
+    private void performNetworkXtPollLocked(NetworkStats networkXtSnapshot, long currentTime) {
+        final HashSet<String> unknownIface = Sets.newHashSet();
+
+        final NetworkStats delta = computeStatsDelta(
+                mLastPollNetworkXtSnapshot, networkXtSnapshot, false);
+        final long timeStart = currentTime - delta.getElapsedRealtime();
+
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < delta.size(); i++) {
+            entry = delta.getValues(i, entry);
+            final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
+            if (ident == null) {
+                unknownIface.add(entry.iface);
+                continue;
+            }
+
+            final NetworkStatsHistory history = findOrCreateNetworkXtStatsLocked(ident);
+            history.recordData(timeStart, currentTime, entry);
+        }
+
+        mLastPollNetworkXtSnapshot = networkXtSnapshot;
+
+        if (LOGD && unknownIface.size() > 0) {
+            Slog.w(TAG, "unknown xt interfaces " + unknownIface + ", ignoring those stats");
         }
     }
 
@@ -882,38 +963,6 @@
     }
 
     /**
-     * Update {@link #mUidStats} historical usage for
-     * {@link TrafficStats#UID_TETHERING} based on tethering statistics.
-     */
-    private void performTetherPollLocked(NetworkStats tetherSnapshot, long currentTime) {
-        ensureUidStatsLoadedLocked();
-
-        final NetworkStats delta = computeStatsDelta(
-                mLastPollTetherSnapshot, tetherSnapshot, false);
-        final long timeStart = currentTime - delta.getElapsedRealtime();
-
-        NetworkStats.Entry entry = null;
-        for (int i = 0; i < delta.size(); i++) {
-            entry = delta.getValues(i, entry);
-            final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
-            if (ident == null) {
-                if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
-                        || entry.txPackets > 0) {
-                    Log.w(TAG, "dropping tether delta from unknown iface: " + entry);
-                }
-                continue;
-            }
-
-            final NetworkStatsHistory history = findOrCreateUidStatsLocked(
-                    ident, UID_TETHERING, SET_DEFAULT, TAG_NONE);
-            history.recordData(timeStart, currentTime, entry);
-        }
-
-        // normal UID poll will trim any history beyond max
-        mLastPollTetherSnapshot = tetherSnapshot;
-    }
-
-    /**
      * Sample recent statistics summary into {@link EventLog}.
      */
     private void performSample() {
@@ -925,25 +974,34 @@
         final long end = now - (now % largestBucketSize) + largestBucketSize;
         final long start = end - largestBucketSize;
 
+        final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1;
+
         NetworkTemplate template = null;
-        NetworkStats.Entry ifaceTotal = null;
+        NetworkStats.Entry devTotal = null;
+        NetworkStats.Entry xtTotal = null;
         NetworkStats.Entry uidTotal = null;
 
         // collect mobile sample
         template = buildTemplateMobileAll(getActiveSubscriberId(mContext));
-        ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
+        devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal);
+        xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal);
         uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
-        EventLogTags.writeNetstatsMobileSample(ifaceTotal.rxBytes, ifaceTotal.rxPackets,
-                ifaceTotal.txBytes, ifaceTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets,
-                uidTotal.txBytes, uidTotal.txPackets);
+        EventLogTags.writeNetstatsMobileSample(
+                devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
+                xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+                trustedTime);
 
         // collect wifi sample
         template = buildTemplateWifi();
-        ifaceTotal = getSummaryForNetwork(template, start, end).getTotal(ifaceTotal);
+        devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal);
+        xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal);
         uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
-        EventLogTags.writeNetstatsWifiSample(ifaceTotal.rxBytes, ifaceTotal.rxPackets,
-                ifaceTotal.txBytes, ifaceTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets,
-                uidTotal.txBytes, uidTotal.txPackets);
+        EventLogTags.writeNetstatsWifiSample(
+                devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
+                xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+                trustedTime);
     }
 
     /**
@@ -976,8 +1034,17 @@
         writeUidStatsLocked();
     }
 
-    private NetworkStatsHistory findOrCreateNetworkStatsLocked(NetworkIdentitySet ident) {
-        final NetworkStatsHistory existing = mNetworkStats.get(ident);
+    private NetworkStatsHistory findOrCreateNetworkXtStatsLocked(NetworkIdentitySet ident) {
+        return findOrCreateNetworkStatsLocked(ident, mNetworkXtStats);
+    }
+
+    private NetworkStatsHistory findOrCreateNetworkDevStatsLocked(NetworkIdentitySet ident) {
+        return findOrCreateNetworkStatsLocked(ident, mNetworkDevStats);
+    }
+
+    private NetworkStatsHistory findOrCreateNetworkStatsLocked(
+            NetworkIdentitySet ident, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
+        final NetworkStatsHistory existing = source.get(ident);
 
         // update when no existing, or when bucket duration changed
         final long bucketDuration = mSettings.getNetworkBucketDuration();
@@ -991,7 +1058,7 @@
         }
 
         if (updated != null) {
-            mNetworkStats.put(ident, updated);
+            source.put(ident, updated);
             return updated;
         } else {
             return existing;
@@ -1024,15 +1091,24 @@
         }
     }
 
-    private void readNetworkStatsLocked() {
-        if (LOGV) Slog.v(TAG, "readNetworkStatsLocked()");
+    private void readNetworkDevStatsLocked() {
+        if (LOGV) Slog.v(TAG, "readNetworkDevStatsLocked()");
+        readNetworkStats(mNetworkDevFile, mNetworkDevStats);
+    }
 
+    private void readNetworkXtStatsLocked() {
+        if (LOGV) Slog.v(TAG, "readNetworkXtStatsLocked()");
+        readNetworkStats(mNetworkXtFile, mNetworkXtStats);
+    }
+
+    private static void readNetworkStats(
+            AtomicFile inputFile, HashMap<NetworkIdentitySet, NetworkStatsHistory> output) {
         // clear any existing stats and read from disk
-        mNetworkStats.clear();
+        output.clear();
 
         DataInputStream in = null;
         try {
-            in = new DataInputStream(new BufferedInputStream(mNetworkFile.openRead()));
+            in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
 
             // verify file magic header intact
             final int magic = in.readInt();
@@ -1048,7 +1124,7 @@
                     for (int i = 0; i < size; i++) {
                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
-                        mNetworkStats.put(ident, history);
+                        output.put(ident, history);
                     }
                     break;
                 }
@@ -1138,41 +1214,50 @@
         }
     }
 
-    private void writeNetworkStatsLocked() {
-        if (LOGV) Slog.v(TAG, "writeNetworkStatsLocked()");
+    private void writeNetworkDevStatsLocked() {
+        if (LOGV) Slog.v(TAG, "writeNetworkDevStatsLocked()");
+        writeNetworkStats(mNetworkDevStats, mNetworkDevFile);
+    }
 
+    private void writeNetworkXtStatsLocked() {
+        if (LOGV) Slog.v(TAG, "writeNetworkXtStatsLocked()");
+        writeNetworkStats(mNetworkXtStats, mNetworkXtFile);
+    }
+
+    private void writeNetworkStats(
+            HashMap<NetworkIdentitySet, NetworkStatsHistory> input, AtomicFile outputFile) {
         // TODO: consider duplicating stats and releasing lock while writing
 
         // trim any history beyond max
         if (mTime.hasCache()) {
             final long currentTime = mTime.currentTimeMillis();
             final long maxHistory = mSettings.getNetworkMaxHistory();
-            for (NetworkStatsHistory history : mNetworkStats.values()) {
+            for (NetworkStatsHistory history : input.values()) {
                 history.removeBucketsBefore(currentTime - maxHistory);
             }
         }
 
         FileOutputStream fos = null;
         try {
-            fos = mNetworkFile.startWrite();
+            fos = outputFile.startWrite();
             final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));
 
             out.writeInt(FILE_MAGIC);
             out.writeInt(VERSION_NETWORK_INIT);
 
-            out.writeInt(mNetworkStats.size());
-            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory history = mNetworkStats.get(ident);
+            out.writeInt(input.size());
+            for (NetworkIdentitySet ident : input.keySet()) {
+                final NetworkStatsHistory history = input.get(ident);
                 ident.writeToStream(out);
                 history.writeToStream(out);
             }
 
             out.flush();
-            mNetworkFile.finishWrite(fos);
+            outputFile.finishWrite(fos);
         } catch (IOException e) {
             Log.wtf(TAG, "problem writing stats", e);
             if (fos != null) {
-                mNetworkFile.failWrite(fos);
+                outputFile.failWrite(fos);
             }
         }
     }
@@ -1280,9 +1365,16 @@
                 pw.print(" ident="); pw.println(ident.toString());
             }
 
-            pw.println("Known historical stats:");
-            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
-                final NetworkStatsHistory history = mNetworkStats.get(ident);
+            pw.println("Known historical dev stats:");
+            for (NetworkIdentitySet ident : mNetworkDevStats.keySet()) {
+                final NetworkStatsHistory history = mNetworkDevStats.get(ident);
+                pw.print("  ident="); pw.println(ident.toString());
+                history.dump("  ", pw, fullHistory);
+            }
+
+            pw.println("Known historical xt stats:");
+            for (NetworkIdentitySet ident : mNetworkXtStats.keySet()) {
+                final NetworkStatsHistory history = mNetworkXtStats.get(ident);
                 pw.print("  ident="); pw.println(ident.toString());
                 history.dump("  ", pw, fullHistory);
             }
@@ -1333,10 +1425,13 @@
         final List<ApplicationInfo> installedApps = mContext
                 .getPackageManager().getInstalledApplications(0);
 
-        mNetworkStats.clear();
+        mNetworkDevStats.clear();
+        mNetworkXtStats.clear();
         mUidStats.clear();
         for (NetworkIdentitySet ident : mActiveIfaces.values()) {
-            findOrCreateNetworkStatsLocked(ident).generateRandom(NET_START, NET_END, NET_RX_BYTES,
+            findOrCreateNetworkDevStatsLocked(ident).generateRandom(NET_START, NET_END,
+                    NET_RX_BYTES, NET_RX_PACKETS, NET_TX_BYTES, NET_TX_PACKETS, 0L);
+            findOrCreateNetworkXtStatsLocked(ident).generateRandom(NET_START, NET_END, NET_RX_BYTES,
                     NET_RX_PACKETS, NET_TX_BYTES, NET_TX_PACKETS, 0L);
 
             for (ApplicationInfo info : installedApps) {
@@ -1369,6 +1464,10 @@
         }
     }
 
+    private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) {
+        return uidSnapshot.groupedByIface();
+    }
+
     private int estimateNetworkBuckets() {
         return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration());
     }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 2ead254..f7dff23 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -32,6 +32,7 @@
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.UID_REMOVED;
+import static android.net.TrafficStats.UID_TETHERING;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -179,6 +180,7 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -232,6 +234,7 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -327,6 +330,7 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -378,6 +382,7 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -459,6 +464,7 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -518,6 +524,7 @@
         expectNetworkState(buildMobile3gState(IMSI_1));
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -585,6 +592,7 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -648,6 +656,7 @@
         expectNetworkState(buildWifiState());
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
 
         replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
@@ -705,6 +714,42 @@
         verifyAndReset();
     }
 
+    public void testTethering() throws Exception {
+        // pretend first mobile network comes online
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkState(buildMobile3gState(IMSI_1));
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        expectNetworkStatsPoll();
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+        verifyAndReset();
+
+        // create some tethering traffic
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        expectCurrentTime();
+        expectDefaultSettings();
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
+                .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L));
+        final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
+        expectNetworkStatsPoll(tetherIfacePairs, new NetworkStats(getElapsedRealtime(), 1)
+                .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L));
+
+        replay();
+        mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
+
+        // verify service recorded history
+        assertNetworkTotal(sTemplateImsi1, 2048L, 16L, 512L, 4L, 0);
+        assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 0);
+        assertUidTotal(sTemplateImsi1, UID_TETHERING, 1920L, 14L, 384L, 2L, 0);
+        verifyAndReset();
+
+    }
+
     private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
             long txBytes, long txPackets, int operations) {
         final NetworkStatsHistory history = mService.getHistoryForNetwork(template, FIELD_ALL);
@@ -774,9 +819,16 @@
     }
 
     private void expectNetworkStatsPoll() throws Exception {
+        expectNetworkStatsPoll(new String[0], new NetworkStats(getElapsedRealtime(), 0));
+    }
+
+    private void expectNetworkStatsPoll(String[] tetherIfacePairs, NetworkStats tetherStats)
+            throws Exception {
         mNetManager.setGlobalAlert(anyLong());
         expectLastCall().anyTimes();
-        expect(mConnManager.getTetheredIfacePairs()).andReturn(null).anyTimes();
+        expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).anyTimes();
+        expect(mNetManager.getNetworkStatsTethering(eq(tetherIfacePairs)))
+                .andReturn(tetherStats).anyTimes();
     }
 
     private void assertStatsFilesExist(boolean exist) {