Move battery stats to xt_qtaguid for data stats.

Replace TrafficStats calls by reading values from xt_qtaguid kernel
module. To keep BatteryStatsImpl changes lightweight, cache recently
parsed stats. Tracks mobile ifaces from ConnectivityService.

Refactor xt_qtaguid parsing into factory outside of NMS. Add stats
grouping based on UID, and total based on limiting filters like iface
prefix and UID.

Bug: 4902271
Change-Id: I533f116c434b77f93355bf95b839e7478528505b
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e554975..69ac1e7 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -229,6 +229,14 @@
         return elapsedRealtime;
     }
 
+    /**
+     * Return age of this {@link NetworkStats} object with respect to
+     * {@link SystemClock#elapsedRealtime()}.
+     */
+    public long getElapsedRealtimeAge() {
+        return SystemClock.elapsedRealtime() - elapsedRealtime;
+    }
+
     public int size() {
         return size;
     }
@@ -354,26 +362,59 @@
      * Return total of all fields represented by this snapshot object.
      */
     public Entry getTotal(Entry recycle) {
+        return getTotal(recycle, null, UID_ALL);
+    }
+
+    /**
+     * Return total of all fields represented by this snapshot object matching
+     * the requested {@link #uid}.
+     */
+    public Entry getTotal(Entry recycle, int limitUid) {
+        return getTotal(recycle, null, limitUid);
+    }
+
+    /**
+     * Return total of all fields represented by this snapshot object matching
+     * the requested {@link #iface}.
+     */
+    public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
+        return getTotal(recycle, limitIface, UID_ALL);
+    }
+
+    /**
+     * Return total of all fields represented by this snapshot object matching
+     * the requested {@link #iface} and {@link #uid}.
+     *
+     * @param limitIface Set of {@link #iface} to include in total; or {@code
+     *            null} to include all ifaces.
+     */
+    private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
         final Entry entry = recycle != null ? recycle : new Entry();
 
         entry.iface = IFACE_ALL;
-        entry.uid = UID_ALL;
+        entry.uid = limitUid;
         entry.set = SET_ALL;
         entry.tag = TAG_NONE;
         entry.rxBytes = 0;
         entry.rxPackets = 0;
         entry.txBytes = 0;
         entry.txPackets = 0;
+        entry.operations = 0;
 
         for (int i = 0; i < size; i++) {
-            // skip specific tags, since already counted in TAG_NONE
-            if (tag[i] != TAG_NONE) continue;
+            final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
+            final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
 
-            entry.rxBytes += rxBytes[i];
-            entry.rxPackets += rxPackets[i];
-            entry.txBytes += txBytes[i];
-            entry.txPackets += txPackets[i];
-            entry.operations += operations[i];
+            if (matchesUid && matchesIface) {
+                // skip specific tags, since already counted in TAG_NONE
+                if (tag[i] != TAG_NONE) continue;
+
+                entry.rxBytes += rxBytes[i];
+                entry.rxPackets += rxPackets[i];
+                entry.txBytes += txBytes[i];
+                entry.txPackets += txPackets[i];
+                entry.operations += operations[i];
+            }
         }
         return entry;
     }
@@ -495,6 +536,34 @@
         return stats;
     }
 
+    /**
+     * Return total statistics grouped by {@link #uid}; doesn't mutate the
+     * original structure.
+     */
+    public NetworkStats groupedByUid() {
+        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+        final Entry entry = new Entry();
+        entry.iface = IFACE_ALL;
+        entry.set = SET_ALL;
+        entry.tag = TAG_NONE;
+
+        for (int i = 0; i < size; i++) {
+            // skip specific tags, since already counted in TAG_NONE
+            if (tag[i] != TAG_NONE) continue;
+
+            entry.uid = uid[i];
+            entry.rxBytes = rxBytes[i];
+            entry.rxPackets = rxPackets[i];
+            entry.txBytes = txBytes[i];
+            entry.txPackets = txPackets[i];
+            entry.operations = operations[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/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 351714e..6a0cd36 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -68,6 +68,7 @@
     void noteScanWifiLockReleasedFromSource(in WorkSource ws);
     void noteWifiMulticastEnabledFromSource(in WorkSource ws);
     void noteWifiMulticastDisabledFromSource(in WorkSource ws);
+    void noteNetworkInterfaceType(String iface, int type);
     void setBatteryState(int status, int health, int plugType, int level, int temp, int volt);
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
new file mode 100644
index 0000000..ee3f23b
--- /dev/null
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -0,0 +1,379 @@
+/*
+ * 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.internal.net;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+
+import android.net.NetworkStats;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+import libcore.io.IoUtils;
+
+/**
+ * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
+ * files as needed.
+ */
+public class NetworkStatsFactory {
+    private static final String TAG = "NetworkStatsFactory";
+
+    // TODO: consider moving parsing to native code
+
+    /** Path to {@code /proc/net/dev}. */
+    @Deprecated
+    private final File mStatsIface;
+    /** Path to {@code /proc/net/xt_qtaguid/iface_stat}. */
+    @Deprecated
+    private final File mStatsXtIface;
+    /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
+    private final File mStatsXtIfaceAll;
+    /** Path to {@code /proc/net/xt_qtaguid/stats}. */
+    private final File mStatsXtUid;
+
+    /** {@link #mStatsXtUid} and {@link #mStatsXtIfaceAll} headers. */
+    private static final String KEY_IDX = "idx";
+    private static final String KEY_IFACE = "iface";
+    private static final String KEY_ACTIVE = "active";
+    private static final String KEY_UID = "uid_tag_int";
+    private static final String KEY_COUNTER_SET = "cnt_set";
+    private static final String KEY_TAG_HEX = "acct_tag_hex";
+    private static final String KEY_SNAP_RX_BYTES = "snap_rx_bytes";
+    private static final String KEY_SNAP_RX_PACKETS = "snap_rx_packets";
+    private static final String KEY_SNAP_TX_BYTES = "snap_tx_bytes";
+    private static final String KEY_SNAP_TX_PACKETS = "snap_tx_packets";
+    private static final String KEY_RX_BYTES = "rx_bytes";
+    private static final String KEY_RX_PACKETS = "rx_packets";
+    private static final String KEY_TX_BYTES = "tx_bytes";
+    private static final String KEY_TX_PACKETS = "tx_packets";
+
+    public NetworkStatsFactory() {
+        this(new File("/proc/"));
+    }
+
+    // @VisibleForTesting
+    public NetworkStatsFactory(File procRoot) {
+        mStatsIface = new File(procRoot, "net/dev");
+        mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
+        mStatsXtIface = new File(procRoot, "net/xt_qtaguid/iface_stat");
+        mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
+    }
+
+    /**
+     * Parse and return interface-level summary {@link NetworkStats}. Values
+     * monotonically increase since device boot, and may include details about
+     * inactive interfaces.
+     *
+     * @throws IllegalStateException when problem parsing stats.
+     */
+    public NetworkStats readNetworkStatsSummary() throws IllegalStateException {
+        if (mStatsXtIfaceAll.exists()) {
+            return readNetworkStatsSummarySingleFile();
+        } else {
+            return readNetworkStatsSummaryMultipleFiles();
+        }
+    }
+
+    private NetworkStats readNetworkStatsSummarySingleFile() {
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+        // TODO: read directly from proc once headers are added
+        final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
+                KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
+                KEY_RX_PACKETS, KEY_TX_BYTES, KEY_TX_PACKETS);
+        final ArrayList<String> values = Lists.newArrayList();
+        final HashMap<String, String> parsed = Maps.newHashMap();
+
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader(mStatsXtIfaceAll));
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                splitLine(line, values);
+                parseLine(keys, values, parsed);
+
+                entry.iface = parsed.get(KEY_IFACE);
+                entry.uid = UID_ALL;
+                entry.set = SET_DEFAULT;
+                entry.tag = TAG_NONE;
+
+                // always include snapshot values
+                entry.rxBytes = getParsedLong(parsed, KEY_SNAP_RX_BYTES);
+                entry.rxPackets = getParsedLong(parsed, KEY_SNAP_RX_PACKETS);
+                entry.txBytes = getParsedLong(parsed, KEY_SNAP_TX_BYTES);
+                entry.txPackets = getParsedLong(parsed, KEY_SNAP_TX_PACKETS);
+
+                // fold in active numbers, but only when active
+                final boolean active = getParsedInt(parsed, KEY_ACTIVE) != 0;
+                if (active) {
+                    entry.rxBytes += getParsedLong(parsed, KEY_RX_BYTES);
+                    entry.rxPackets += getParsedLong(parsed, KEY_RX_PACKETS);
+                    entry.txBytes += getParsedLong(parsed, KEY_TX_BYTES);
+                    entry.txPackets += getParsedLong(parsed, KEY_TX_PACKETS);
+                }
+
+                stats.addValues(entry);
+            }
+        } catch (NullPointerException e) {
+            throw new IllegalStateException("problem parsing stats: " + e);
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException("problem parsing stats: " + e);
+        } catch (IOException e) {
+            throw new IllegalStateException("problem parsing stats: " + e);
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+        return stats;
+    }
+
+    /**
+     * @deprecated remove once {@code iface_stat_all} is merged to all kernels.
+     */
+    @Deprecated
+    private NetworkStats readNetworkStatsSummaryMultipleFiles() {
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+        final HashSet<String> knownIfaces = Sets.newHashSet();
+        final HashSet<String> activeIfaces = Sets.newHashSet();
+
+        // collect any historical stats and active state
+        for (String iface : fileListWithoutNull(mStatsXtIface)) {
+            final File ifacePath = new File(mStatsXtIface, iface);
+
+            final long active = readSingleLongFromFile(new File(ifacePath, "active"));
+            if (active == 1) {
+                knownIfaces.add(iface);
+                activeIfaces.add(iface);
+            } else if (active == 0) {
+                knownIfaces.add(iface);
+            } else {
+                continue;
+            }
+
+            entry.iface = iface;
+            entry.uid = UID_ALL;
+            entry.set = SET_DEFAULT;
+            entry.tag = TAG_NONE;
+            entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes"));
+            entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets"));
+            entry.txBytes = readSingleLongFromFile(new File(ifacePath, "tx_bytes"));
+            entry.txPackets = readSingleLongFromFile(new File(ifacePath, "tx_packets"));
+
+            stats.addValues(entry);
+        }
+
+        final ArrayList<String> values = Lists.newArrayList();
+
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader(mStatsIface));
+
+            // skip first two header lines
+            reader.readLine();
+            reader.readLine();
+
+            // parse remaining lines
+            String line;
+            while ((line = reader.readLine()) != null) {
+                splitLine(line, values);
+
+                try {
+                    entry.iface = values.get(0);
+                    entry.uid = UID_ALL;
+                    entry.set = SET_DEFAULT;
+                    entry.tag = TAG_NONE;
+                    entry.rxBytes = Long.parseLong(values.get(1));
+                    entry.rxPackets = Long.parseLong(values.get(2));
+                    entry.txBytes = Long.parseLong(values.get(9));
+                    entry.txPackets = Long.parseLong(values.get(10));
+
+                    if (activeIfaces.contains(entry.iface)) {
+                        // combine stats when iface is active
+                        stats.combineValues(entry);
+                    } else if (!knownIfaces.contains(entry.iface)) {
+                        // add stats when iface is unknown
+                        stats.addValues(entry);
+                    }
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
+                }
+            }
+        } catch (NullPointerException e) {
+            throw new IllegalStateException("problem parsing stats: " + e);
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException("problem parsing stats: " + e);
+        } catch (IOException e) {
+            throw new IllegalStateException("problem parsing stats: " + e);
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+
+        return stats;
+    }
+
+    public NetworkStats readNetworkStatsDetail() {
+        return readNetworkStatsDetail(UID_ALL);
+    }
+
+    /**
+     * Parse and return {@link NetworkStats} with UID-level details. Values
+     * monotonically increase since device boot.
+     *
+     * @throws IllegalStateException when problem parsing stats.
+     */
+    public NetworkStats readNetworkStatsDetail(int limitUid) throws IllegalStateException {
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+        // TODO: remove knownLines check once 5087722 verified
+        final HashSet<String> knownLines = Sets.newHashSet();
+        // TODO: remove lastIdx check once 5270106 verified
+        int lastIdx;
+
+        final ArrayList<String> keys = Lists.newArrayList();
+        final ArrayList<String> values = Lists.newArrayList();
+        final HashMap<String, String> parsed = Maps.newHashMap();
+
+        BufferedReader reader = null;
+        String line = null;
+        try {
+            reader = new BufferedReader(new FileReader(mStatsXtUid));
+
+            // parse first line as header
+            line = reader.readLine();
+            splitLine(line, keys);
+            lastIdx = 1;
+
+            // parse remaining lines
+            while ((line = reader.readLine()) != null) {
+                splitLine(line, values);
+                parseLine(keys, values, parsed);
+
+                if (!knownLines.add(line)) {
+                    throw new IllegalStateException("duplicate proc entry: " + line);
+                }
+
+                final int idx = getParsedInt(parsed, KEY_IDX);
+                if (idx != lastIdx + 1) {
+                    throw new IllegalStateException(
+                            "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
+                }
+                lastIdx = idx;
+
+                entry.iface = parsed.get(KEY_IFACE);
+                entry.uid = getParsedInt(parsed, KEY_UID);
+                entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
+                entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
+                entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
+                entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
+                entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
+                entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+
+                if (limitUid == UID_ALL || limitUid == entry.uid) {
+                    stats.addValues(entry);
+                }
+            }
+        } catch (NullPointerException e) {
+            throw new IllegalStateException("problem parsing line: " + line, e);
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException("problem parsing line: " + line, e);
+        } catch (IOException e) {
+            throw new IllegalStateException("problem parsing line: " + line, e);
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+        return stats;
+    }
+
+    private static int getParsedInt(HashMap<String, String> parsed, String key) {
+        final String value = parsed.get(key);
+        return value != null ? Integer.parseInt(value) : 0;
+    }
+
+    private static long getParsedLong(HashMap<String, String> parsed, String key) {
+        final String value = parsed.get(key);
+        return value != null ? Long.parseLong(value) : 0;
+    }
+
+    /**
+     * Split given line into {@link ArrayList}.
+     */
+    private static void splitLine(String line, ArrayList<String> outSplit) {
+        outSplit.clear();
+
+        final StringTokenizer t = new StringTokenizer(line, " \t\n\r\f:");
+        while (t.hasMoreTokens()) {
+            outSplit.add(t.nextToken());
+        }
+    }
+
+    /**
+     * Zip the two given {@link ArrayList} as key and value pairs into
+     * {@link HashMap}.
+     */
+    private static void parseLine(
+            ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
+        outParsed.clear();
+
+        final int size = Math.min(keys.size(), values.size());
+        for (int i = 0; i < size; i++) {
+            outParsed.put(keys.get(i), values.get(i));
+        }
+    }
+
+    /**
+     * Utility method to read a single plain-text {@link Long} from the given
+     * {@link File}, usually from a {@code /proc/} filesystem.
+     */
+    private static long readSingleLongFromFile(File file) {
+        try {
+            final byte[] buffer = IoUtils.readFileAsByteArray(file.toString());
+            return Long.parseLong(new String(buffer).trim());
+        } catch (NumberFormatException e) {
+            return -1;
+        } catch (IOException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Wrapper for {@link File#list()} that returns empty array instead of
+     * {@code null}.
+     */
+    private static String[] fileListWithoutNull(File file) {
+        final String[] list = file.list();
+        return list != null ? list : new String[0];
+    }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index df5071f..e2a2566 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,11 +16,15 @@
 
 package com.android.internal.os;
 
-import com.android.internal.util.JournaledFile;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.UID_ALL;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.net.TrafficStats;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkStats;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.FileUtils;
@@ -43,6 +47,11 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 
+import com.android.internal.R;
+import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.util.JournaledFile;
+import com.google.android.collect.Sets;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
@@ -52,6 +61,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -69,6 +79,8 @@
     private static final boolean DEBUG_HISTORY = false;
     private static final boolean USE_OLD_HISTORY = false;   // for debugging.
 
+    // TODO: remove "tcp" from network methods, since we measure total stats.
+
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
@@ -314,6 +326,11 @@
 
     private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>();
 
+    private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
+
+    /** Network ifaces that {@link ConnectivityManager} has claimed as mobile. */
+    private HashSet<String> mMobileIfaces = Sets.newHashSet();
+
     // For debugging
     public BatteryStatsImpl() {
         mFile = null;
@@ -1036,7 +1053,8 @@
         String name;
         int count;
         long totalTime;
-        int startIndex, endIndex;
+        int startIndex;
+        int endIndex;
         int numUpdatedWlNames = 0;
 
         // Advance past the first line.
@@ -1390,30 +1408,48 @@
     }
 
     public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
-        for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
-            Uid u = mUidStats.valueAt(iu);
-            u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid);
-            u.mStartedTcpBytesSent = TrafficStats.getUidTxBytes(u.mUid);
+        NetworkStats.Entry entry = null;
+
+        // Track UID data usage
+        final NetworkStats uidStats = getNetworkStatsDetailGroupedByUid();
+        final int size = uidStats.size();
+        for (int i = 0; i < size; i++) {
+            entry = uidStats.getValues(i, entry);
+
+            final Uid u = mUidStats.get(entry.uid);
+            if (u == null) continue;
+
+            u.mStartedTcpBytesReceived = entry.rxBytes;
+            u.mStartedTcpBytesSent = entry.txBytes;
             u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
             u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
         }
+
         for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
             mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
         }
-        // Track total mobile data
-        doDataUnplug(mMobileDataRx, TrafficStats.getMobileRxBytes());
-        doDataUnplug(mMobileDataTx, TrafficStats.getMobileTxBytes());
-        doDataUnplug(mTotalDataRx, TrafficStats.getTotalRxBytes());
-        doDataUnplug(mTotalDataTx, TrafficStats.getTotalTxBytes());
+
+        // Track both mobile and total overall data
+        final NetworkStats ifaceStats = getNetworkStatsSummary();
+        entry = ifaceStats.getTotal(entry, mMobileIfaces);
+        doDataUnplug(mMobileDataRx, entry.rxBytes);
+        doDataUnplug(mMobileDataTx, entry.txBytes);
+        entry = ifaceStats.getTotal(entry);
+        doDataUnplug(mTotalDataRx, entry.rxBytes);
+        doDataUnplug(mTotalDataTx, entry.txBytes);
+
         // Track radio awake time
         mRadioDataStart = getCurrentRadioDataUptime();
         mRadioDataUptime = 0;
+
         // Track bt headset ping count
         mBluetoothPingStart = getCurrentBluetoothPingCount();
         mBluetoothPingCount = 0;
     }
 
     public void doPlugLocked(long batteryUptime, long batteryRealtime) {
+        NetworkStats.Entry entry = null;
+
         for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
             Uid u = mUidStats.valueAt(iu);
             if (u.mStartedTcpBytesReceived >= 0) {
@@ -1428,10 +1464,16 @@
         for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
             mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
         }
-        doDataPlug(mMobileDataRx, TrafficStats.getMobileRxBytes());
-        doDataPlug(mMobileDataTx, TrafficStats.getMobileTxBytes());
-        doDataPlug(mTotalDataRx, TrafficStats.getTotalRxBytes());
-        doDataPlug(mTotalDataTx, TrafficStats.getTotalTxBytes());
+
+        // Track both mobile and total overall data
+        final NetworkStats ifaceStats = getNetworkStatsSummary();
+        entry = ifaceStats.getTotal(entry, mMobileIfaces);
+        doDataPlug(mMobileDataRx, entry.rxBytes);
+        doDataPlug(mMobileDataTx, entry.txBytes);
+        entry = ifaceStats.getTotal(entry);
+        doDataPlug(mTotalDataRx, entry.rxBytes);
+        doDataPlug(mTotalDataTx, entry.txBytes);
+
         // Track radio awake time
         mRadioDataUptime = getRadioDataUptime();
         mRadioDataStart = -1;
@@ -2216,6 +2258,14 @@
         }
     }
 
+    public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
+        if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
+            mMobileIfaces.add(iface);
+        } else {
+            mMobileIfaces.remove(iface);
+        }
+    }
+
     @Override public long getScreenOnTime(long batteryRealtime, int which) {
         return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
     }
@@ -2400,8 +2450,10 @@
         }
 
         public long computeCurrentTcpBytesReceived() {
+            final long uidRxBytes = getNetworkStatsDetailGroupedByUid().getTotal(
+                    null, mUid).rxBytes;
             return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
-                    ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+                    ? (uidRxBytes - mStartedTcpBytesReceived) : 0);
         }
 
         @Override
@@ -2619,8 +2671,10 @@
         }
 
         public long computeCurrentTcpBytesSent() {
+            final long uidTxBytes = getNetworkStatsDetailGroupedByUid().getTotal(
+                    null, mUid).txBytes;
             return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
-                    ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+                    ? (uidTxBytes - mStartedTcpBytesSent) : 0);
         }
 
         /**
@@ -4518,22 +4572,26 @@
 
     /** Only STATS_UNPLUGGED works properly */
     public long getMobileTcpBytesSent(int which) {
-        return getTcpBytes(TrafficStats.getMobileTxBytes(), mMobileDataTx, which);
+        final long mobileTxBytes = getNetworkStatsSummary().getTotal(null, mMobileIfaces).txBytes;
+        return getTcpBytes(mobileTxBytes, mMobileDataTx, which);
     }
 
     /** Only STATS_UNPLUGGED works properly */
     public long getMobileTcpBytesReceived(int which) {
-        return getTcpBytes(TrafficStats.getMobileRxBytes(), mMobileDataRx, which);
+        final long mobileRxBytes = getNetworkStatsSummary().getTotal(null, mMobileIfaces).rxBytes;
+        return getTcpBytes(mobileRxBytes, mMobileDataRx, which);
     }
 
     /** Only STATS_UNPLUGGED works properly */
     public long getTotalTcpBytesSent(int which) {
-        return getTcpBytes(TrafficStats.getTotalTxBytes(), mTotalDataTx, which);
+        final long totalTxBytes = getNetworkStatsSummary().getTotal(null).txBytes;
+        return getTcpBytes(totalTxBytes, mTotalDataTx, which);
     }
 
     /** Only STATS_UNPLUGGED works properly */
     public long getTotalTcpBytesReceived(int which) {
-        return getTcpBytes(TrafficStats.getTotalRxBytes(), mTotalDataRx, which);
+        final long totalRxBytes = getNetworkStatsSummary().getTotal(null).rxBytes;
+        return getTcpBytes(totalRxBytes, mTotalDataRx, which);
     }
 
     @Override
@@ -5637,7 +5695,47 @@
             mGlobalWifiRunningTimer.logState(pr, "  ");
             pr.println("*** Bluetooth timer:");
             mBluetoothOnTimer.logState(pr, "  ");
+            pr.println("*** Mobile ifaces:");
+            pr.println(mMobileIfaces.toString());
         }
         super.dumpLocked(pw);
     }
+
+    private NetworkStats mNetworkSummaryCache;
+    private NetworkStats mNetworkDetailCache;
+
+    private NetworkStats getNetworkStatsSummary() {
+        // NOTE: calls from BatteryStatsService already hold this lock
+        synchronized (this) {
+            if (mNetworkSummaryCache == null
+                    || mNetworkSummaryCache.getElapsedRealtimeAge() > SECOND_IN_MILLIS) {
+                try {
+                    mNetworkSummaryCache = mNetworkStatsFactory.readNetworkStatsSummary();
+                } catch (IllegalStateException e) {
+                    // log problem and return empty object
+                    Log.wtf(TAG, "problem reading network stats", e);
+                    mNetworkSummaryCache = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+                }
+            }
+            return mNetworkSummaryCache;
+        }
+    }
+
+    private NetworkStats getNetworkStatsDetailGroupedByUid() {
+        // NOTE: calls from BatteryStatsService already hold this lock
+        synchronized (this) {
+            if (mNetworkDetailCache == null
+                    || mNetworkDetailCache.getElapsedRealtimeAge() > SECOND_IN_MILLIS) {
+                try {
+                    mNetworkDetailCache = mNetworkStatsFactory
+                            .readNetworkStatsDetail().groupedByUid();
+                } catch (IllegalStateException e) {
+                    // log problem and return empty object
+                    Log.wtf(TAG, "problem reading network stats", e);
+                    mNetworkDetailCache = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+                }
+            }
+            return mNetworkDetailCache;
+        }
+    }
 }
diff --git a/services/tests/servicestests/res/raw/net_dev_typical b/core/tests/coretests/res/raw/net_dev_typical
similarity index 100%
rename from services/tests/servicestests/res/raw/net_dev_typical
rename to core/tests/coretests/res/raw/net_dev_typical
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_extended b/core/tests/coretests/res/raw/xt_qtaguid_extended
similarity index 100%
rename from services/tests/servicestests/res/raw/xt_qtaguid_extended
rename to core/tests/coretests/res/raw/xt_qtaguid_extended
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_iface_typical b/core/tests/coretests/res/raw/xt_qtaguid_iface_typical
similarity index 99%
rename from services/tests/servicestests/res/raw/xt_qtaguid_iface_typical
rename to core/tests/coretests/res/raw/xt_qtaguid_iface_typical
index efa4cd5..610723a 100644
--- a/services/tests/servicestests/res/raw/xt_qtaguid_iface_typical
+++ b/core/tests/coretests/res/raw/xt_qtaguid_iface_typical
@@ -1,6 +1,6 @@
-rmnet3 1 0 0 0 0 20822 501 1149991 815

-rmnet2 1 0 0 0 0 1594 15 1313 15

-rmnet1 1 0 0 0 0 207398 458 166918 565

-rmnet0 1 0 0 0 0 2112 24 700 10

-test1 1 1 2 3 4 5 6 7 8

-test2 0 1 2 3 4 5 6 7 8

+rmnet3 1 0 0 0 0 20822 501 1149991 815
+rmnet2 1 0 0 0 0 1594 15 1313 15
+rmnet1 1 0 0 0 0 207398 458 166918 565
+rmnet0 1 0 0 0 0 2112 24 700 10
+test1 1 1 2 3 4 5 6 7 8
+test2 0 1 2 3 4 5 6 7 8
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_typical b/core/tests/coretests/res/raw/xt_qtaguid_typical
similarity index 100%
rename from services/tests/servicestests/res/raw/xt_qtaguid_typical
rename to core/tests/coretests/res/raw/xt_qtaguid_typical
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_typical_with_set b/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set
similarity index 99%
rename from services/tests/servicestests/res/raw/xt_qtaguid_typical_with_set
rename to core/tests/coretests/res/raw/xt_qtaguid_typical_with_set
index f9f34ac..b302bb7 100644
--- a/services/tests/servicestests/res/raw/xt_qtaguid_typical_with_set
+++ b/core/tests/coretests/res/raw/xt_qtaguid_typical_with_set
@@ -1,13 +1,13 @@
-idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_packets rx_tcp_bytes rx_udp_packets rx_udp_bytes rx_other_packets rx_other_bytes tx_tcp_packets tx_tcp_bytes tx_udp_packets tx_udp_bytes tx_other_packets tx_other_bytes

-2 rmnet0 0x0 0 0 14855 82 2804 47 2000 45 12799 35 56 2 676 13 2128 34 0 0

-3 rmnet0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

-4 rmnet0 0x0 1000 0 278102 253 10487 182 277342 243 760 10 0 0 9727 172 760 10 0 0

-5 rmnet0 0x0 1000 1 26033 30 1401 26 25881 28 152 2 0 0 1249 24 152 2 0 0

-6 rmnet0 0x0 10012 0 40524 272 134138 293 40524 272 0 0 0 0 134138 293 0 0 0 0

-7 rmnet0 0x0 10012 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

-8 rmnet0 0x0 10034 0 15791 59 9905 69 15791 59 0 0 0 0 9905 69 0 0 0 0

-9 rmnet0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

-10 rmnet0 0x0 10055 0 3602 29 7739 59 3602 29 0 0 0 0 7739 59 0 0 0 0

-11 rmnet0 0x0 10055 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

-12 rmnet0 0x7fff000300000000 1000 0 483 4 1931 6 483 4 0 0 0 0 1931 6 0 0 0 0

-13 rmnet0 0x7fff000300000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

+idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_packets rx_tcp_bytes rx_udp_packets rx_udp_bytes rx_other_packets rx_other_bytes tx_tcp_packets tx_tcp_bytes tx_udp_packets tx_udp_bytes tx_other_packets tx_other_bytes
+2 rmnet0 0x0 0 0 14855 82 2804 47 2000 45 12799 35 56 2 676 13 2128 34 0 0
+3 rmnet0 0x0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+4 rmnet0 0x0 1000 0 278102 253 10487 182 277342 243 760 10 0 0 9727 172 760 10 0 0
+5 rmnet0 0x0 1000 1 26033 30 1401 26 25881 28 152 2 0 0 1249 24 152 2 0 0
+6 rmnet0 0x0 10012 0 40524 272 134138 293 40524 272 0 0 0 0 134138 293 0 0 0 0
+7 rmnet0 0x0 10012 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+8 rmnet0 0x0 10034 0 15791 59 9905 69 15791 59 0 0 0 0 9905 69 0 0 0 0
+9 rmnet0 0x0 10034 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+10 rmnet0 0x0 10055 0 3602 29 7739 59 3602 29 0 0 0 0 7739 59 0 0 0 0
+11 rmnet0 0x0 10055 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+12 rmnet0 0x7fff000300000000 1000 0 483 4 1931 6 483 4 0 0 0 0 1931 6 0 0 0 0
+13 rmnet0 0x7fff000300000000 1000 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index d78d2ef..7082deb 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -25,8 +25,12 @@
 
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.google.android.collect.Sets;
+
 import junit.framework.TestCase;
 
+import java.util.HashSet;
+
 @SmallTest
 public class NetworkStatsTest extends TestCase {
 
@@ -233,13 +237,43 @@
         assertValues(first, 2, TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
     }
 
+    public void testGetTotal() {
+        final NetworkStats stats = 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);
+
+        assertValues(stats.getTotal(null), 1280L, 80L, 0L, 2L, 20L);
+        assertValues(stats.getTotal(null, 100), 1152L, 72L, 0L, 2L, 20L);
+        assertValues(stats.getTotal(null, 101), 128L, 8L, 0L, 0L, 0L);
+
+        final HashSet<String> ifaces = Sets.newHashSet();
+        assertValues(stats.getTotal(null, ifaces), 0L, 0L, 0L, 0L, 0L);
+
+        ifaces.add(TEST_IFACE2);
+        assertValues(stats.getTotal(null, ifaces), 1024L, 64L, 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, long operations) {
         final NetworkStats.Entry entry = stats.getValues(index, null);
+        assertValues(entry, iface, uid, set, tag);
+        assertValues(entry, rxBytes, rxPackets, txBytes, txPackets, operations);
+    }
+
+    private static void assertValues(
+            NetworkStats.Entry entry, String iface, int uid, int set, int tag) {
         assertEquals(iface, entry.iface);
         assertEquals(uid, entry.uid);
         assertEquals(set, entry.set);
         assertEquals(tag, entry.tag);
+    }
+
+    private static void assertValues(NetworkStats.Entry entry, long rxBytes, long rxPackets,
+            long txBytes, long txPackets, long operations) {
         assertEquals(rxBytes, entry.rxBytes);
         assertEquals(rxPackets, entry.rxPackets);
         assertEquals(txBytes, entry.txBytes);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
similarity index 89%
rename from services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
rename to core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
index 40640e0..8a64f2b 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
+++ b/core/tests/coretests/src/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.internal.net;
 
 import static android.net.NetworkStats.SET_DEFAULT;
 import static android.net.NetworkStats.SET_FOREGROUND;
@@ -25,9 +25,8 @@
 import android.content.res.Resources;
 import android.net.NetworkStats;
 import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
 
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.coretests.R;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -39,12 +38,11 @@
 import libcore.io.Streams;
 
 /**
- * Tests for {@link NetworkManagementService}.
+ * Tests for {@link NetworkStatsFactory}.
  */
-@LargeTest
-public class NetworkManagementServiceTest extends AndroidTestCase {
+public class NetworkStatsFactoryTest extends AndroidTestCase {
     private File mTestProc;
-    private NetworkManagementService mService;
+    private NetworkStatsFactory mFactory;
 
     @Override
     public void setUp() throws Exception {
@@ -55,12 +53,12 @@
             IoUtils.deleteContents(mTestProc);
         }
 
-        mService = NetworkManagementService.createForTest(mContext, mTestProc, true);
+        mFactory = new NetworkStatsFactory(mTestProc);
     }
 
     @Override
     public void tearDown() throws Exception {
-        mService = null;
+        mFactory = null;
 
         if (mTestProc.exists()) {
             IoUtils.deleteContents(mTestProc);
@@ -72,7 +70,7 @@
     public void testNetworkStatsDetail() throws Exception {
         stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
 
-        final NetworkStats stats = mService.getNetworkStatsDetail();
+        final NetworkStats stats = mFactory.readNetworkStatsDetail();
         assertEquals(31, stats.size());
         assertStatsEntry(stats, "wlan0", 0, SET_DEFAULT, 0, 14615L, 4270L);
         assertStatsEntry(stats, "wlan0", 10004, SET_DEFAULT, 0, 333821L, 53558L);
@@ -84,7 +82,7 @@
     public void testNetworkStatsDetailExtended() throws Exception {
         stageFile(R.raw.xt_qtaguid_extended, new File(mTestProc, "net/xt_qtaguid/stats"));
 
-        final NetworkStats stats = mService.getNetworkStatsDetail();
+        final NetworkStats stats = mFactory.readNetworkStatsDetail();
         assertEquals(2, stats.size());
         assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0, 1024L, 2048L);
         assertStatsEntry(stats, "test0", 1000, SET_DEFAULT, 0xF00D, 512L, 512L);
@@ -93,7 +91,7 @@
     public void testNetworkStatsSummary() throws Exception {
         stageFile(R.raw.net_dev_typical, new File(mTestProc, "net/dev"));
 
-        final NetworkStats stats = mService.getNetworkStatsSummary();
+        final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertEquals(6, stats.size());
         assertStatsEntry(stats, "lo", UID_ALL, SET_DEFAULT, TAG_NONE, 8308L, 8308L);
         assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L, 489339L);
@@ -111,7 +109,7 @@
         stageLong(2048L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/tx_bytes"));
         stageLong(256L, new File(mTestProc, "net/xt_qtaguid/iface_stat/wlan0/tx_packets"));
 
-        final NetworkStats stats = mService.getNetworkStatsSummary();
+        final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertEquals(7, stats.size());
         assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L, 489339L);
         assertStatsEntry(stats, "wlan0", UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 2048L);
@@ -125,7 +123,7 @@
         stageLong(30L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_bytes"));
         stageLong(40L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_packets"));
 
-        final NetworkStats stats = mService.getNetworkStatsSummary();
+        final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 1507570L + 10L,
                 2205L + 20L, 489339L + 30L, 2237L + 40L);
     }
@@ -138,7 +136,7 @@
         stageLong(30L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_bytes"));
         stageLong(40L, new File(mTestProc, "net/xt_qtaguid/iface_stat/rmnet0/tx_packets"));
 
-        final NetworkStats stats = mService.getNetworkStatsSummary();
+        final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 10L, 20L, 30L, 40L);
     }
 
@@ -153,7 +151,7 @@
     public void testNetworkStatsWithSet() throws Exception {
         stageFile(R.raw.xt_qtaguid_typical_with_set, new File(mTestProc, "net/xt_qtaguid/stats"));
 
-        final NetworkStats stats = mService.getNetworkStatsDetail();
+        final NetworkStats stats = mFactory.readNetworkStatsDetail();
         assertEquals(12, stats.size());
         assertStatsEntry(stats, "rmnet0", 1000, SET_DEFAULT, 0, 278102L, 253L, 10487L, 182L);
         assertStatsEntry(stats, "rmnet0", 1000, SET_FOREGROUND, 0, 26033L, 30L, 1401L, 26L);
@@ -162,7 +160,7 @@
     public void testNetworkStatsSingle() throws Exception {
         stageFile(R.raw.xt_qtaguid_iface_typical, new File(mTestProc, "net/xt_qtaguid/iface_stat_all"));
 
-        final NetworkStats stats = mService.getNetworkStatsSummary();
+        final NetworkStats stats = mFactory.readNetworkStatsSummary();
         assertEquals(6, stats.size());
         assertStatsEntry(stats, "rmnet0", UID_ALL, SET_DEFAULT, TAG_NONE, 2112L, 24L, 700L, 10L);
         assertStatsEntry(stats, "test1", UID_ALL, SET_DEFAULT, TAG_NONE, 6L, 8L, 10L, 12L);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index ce31474..498bdfc 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -73,6 +73,7 @@
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.telephony.Phone;
+import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.google.android.collect.Lists;
@@ -1649,11 +1650,11 @@
     }
 
     private void handleConnect(NetworkInfo info) {
-        int type = info.getType();
+        final int type = info.getType();
 
         // snapshot isFailover, because sendConnectedBroadcast() resets it
         boolean isFailover = info.isFailover();
-        NetworkStateTracker thisNet = mNetTrackers[type];
+        final NetworkStateTracker thisNet = mNetTrackers[type];
 
         // if this is a default net and other default is running
         // kill the one not preferred
@@ -1710,6 +1711,16 @@
         updateNetworkSettings(thisNet);
         handleConnectivityChange(type, false);
         sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
+
+        // notify battery stats service about this network
+        final String iface = thisNet.getLinkProperties().getInterfaceName();
+        if (iface != null) {
+            try {
+                BatteryStatsService.getService().noteNetworkInterfaceType(iface, type);
+            } catch (RemoteException e) {
+                // ignored; service lives in system_server
+            }
+        }
     }
 
     /**
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index f1a404a..b05705e 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -18,14 +18,12 @@
 
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
-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.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Secure.NETSTATS_ENABLED;
 import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
-import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -46,8 +44,7 @@
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
+import com.android.internal.net.NetworkStatsFactory;
 import com.google.android.collect.Sets;
 
 import java.io.BufferedReader;
@@ -55,21 +52,17 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.NoSuchElementException;
 import java.util.StringTokenizer;
 import java.util.concurrent.CountDownLatch;
 
-import libcore.io.IoUtils;
-
 /**
  * @hide
  */
@@ -82,42 +75,12 @@
     private static final int ADD = 1;
     private static final int REMOVE = 2;
 
-    /** Path to {@code /proc/uid_stat}. */
-    @Deprecated
-    private final File mStatsUid;
-    /** Path to {@code /proc/net/dev}. */
-    @Deprecated
-    private final File mStatsIface;
-    /** Path to {@code /proc/net/xt_qtaguid/iface_stat}. */
-    @Deprecated
-    private final File mStatsXtIface;
-    /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
-    private final File mStatsXtIfaceAll;
-    /** Path to {@code /proc/net/xt_qtaguid/stats}. */
-    private final File mStatsXtUid;
-
     /**
      * Name representing {@link #setGlobalAlert(long)} limit when delivered to
      * {@link INetworkManagementEventObserver#limitReached(String, String)}.
      */
     public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
 
-    /** {@link #mStatsXtUid} and {@link #mStatsXtIfaceAll} headers. */
-    private static final String KEY_IDX = "idx";
-    private static final String KEY_IFACE = "iface";
-    private static final String KEY_ACTIVE = "active";
-    private static final String KEY_UID = "uid_tag_int";
-    private static final String KEY_COUNTER_SET = "cnt_set";
-    private static final String KEY_TAG_HEX = "acct_tag_hex";
-    private static final String KEY_SNAP_RX_BYTES = "snap_rx_bytes";
-    private static final String KEY_SNAP_RX_PACKETS = "snap_rx_packets";
-    private static final String KEY_SNAP_TX_BYTES = "snap_tx_bytes";
-    private static final String KEY_SNAP_TX_PACKETS = "snap_tx_packets";
-    private static final String KEY_RX_BYTES = "rx_bytes";
-    private static final String KEY_RX_PACKETS = "rx_packets";
-    private static final String KEY_TX_BYTES = "tx_bytes";
-    private static final String KEY_TX_PACKETS = "tx_packets";
-
     class NetdResponseCode {
         /* Keep in sync with system/netd/ResponseCode.h */
         public static final int InterfaceListResult       = 110;
@@ -156,6 +119,8 @@
     // TODO: replace with RemoteCallbackList
     private ArrayList<INetworkManagementEventObserver> mObservers;
 
+    private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
+
     private Object mQuotaLock = new Object();
     /** Set of interfaces with active quotas. */
     private HashSet<String> mActiveQuotaIfaces = Sets.newHashSet();
@@ -171,16 +136,10 @@
      *
      * @param context  Binder context for this service
      */
-    private NetworkManagementService(Context context, File procRoot) {
+    private NetworkManagementService(Context context) {
         mContext = context;
         mObservers = new ArrayList<INetworkManagementEventObserver>();
 
-        mStatsUid = new File(procRoot, "uid_stat");
-        mStatsIface = new File(procRoot, "net/dev");
-        mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
-        mStatsXtIface = new File(procRoot, "net/xt_qtaguid/iface_stat");
-        mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
-
         if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
             return;
         }
@@ -194,8 +153,7 @@
     }
 
     public static NetworkManagementService create(Context context) throws InterruptedException {
-        NetworkManagementService service = new NetworkManagementService(
-                context, new File("/proc/"));
+        NetworkManagementService service = new NetworkManagementService(context);
         if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
         service.mThread.start();
         if (DBG) Slog.d(TAG, "Awaiting socket connection");
@@ -204,15 +162,6 @@
         return service;
     }
 
-    // @VisibleForTesting
-    public static NetworkManagementService createForTest(
-            Context context, File procRoot, boolean bandwidthControlEnabled) {
-        // TODO: eventually connect with mock netd
-        final NetworkManagementService service = new NetworkManagementService(context, procRoot);
-        service.mBandwidthControlEnabled = bandwidthControlEnabled;
-        return service;
-    }
-
     public void systemReady() {
         // only enable bandwidth control when support exists, and requested by
         // system setting.
@@ -226,7 +175,7 @@
                 mConnector.doCommand("bandwidth enable");
                 mBandwidthControlEnabled = true;
             } catch (NativeDaemonConnectorException e) {
-                Slog.e(TAG, "problem enabling bandwidth controls", e);
+                Log.wtf(TAG, "problem enabling bandwidth controls", e);
             }
         } else {
             Slog.d(TAG, "not enabling bandwidth control");
@@ -1081,165 +1030,14 @@
     public NetworkStats getNetworkStatsSummary() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
-
-        if (mBandwidthControlEnabled && mStatsXtIfaceAll.exists()) {
-            return getNetworkStatsSummarySingleFile();
-        } else {
-            return getNetworkStatsSummaryMultipleFiles();
-        }
-    }
-
-    @Deprecated
-    private NetworkStats getNetworkStatsSummaryMultipleFiles() {
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-
-        final HashSet<String> knownIfaces = Sets.newHashSet();
-        final HashSet<String> activeIfaces = Sets.newHashSet();
-
-        // collect any historical stats and active state
-        // TODO: migrate to reading from single file
-        if (mBandwidthControlEnabled) {
-            for (String iface : fileListWithoutNull(mStatsXtIface)) {
-                final File ifacePath = new File(mStatsXtIface, iface);
-
-                final long active = readSingleLongFromFile(new File(ifacePath, "active"));
-                if (active == 1) {
-                    knownIfaces.add(iface);
-                    activeIfaces.add(iface);
-                } else if (active == 0) {
-                    knownIfaces.add(iface);
-                } else {
-                    continue;
-                }
-
-                entry.iface = iface;
-                entry.uid = UID_ALL;
-                entry.set = SET_DEFAULT;
-                entry.tag = TAG_NONE;
-                entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes"));
-                entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets"));
-                entry.txBytes = readSingleLongFromFile(new File(ifacePath, "tx_bytes"));
-                entry.txPackets = readSingleLongFromFile(new File(ifacePath, "tx_packets"));
-
-                stats.addValues(entry);
-            }
-        }
-
-        final ArrayList<String> values = Lists.newArrayList();
-
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new FileReader(mStatsIface));
-
-            // skip first two header lines
-            reader.readLine();
-            reader.readLine();
-
-            // parse remaining lines
-            String line;
-            while ((line = reader.readLine()) != null) {
-                splitLine(line, values);
-
-                try {
-                    entry.iface = values.get(0);
-                    entry.uid = UID_ALL;
-                    entry.set = SET_DEFAULT;
-                    entry.tag = TAG_NONE;
-                    entry.rxBytes = Long.parseLong(values.get(1));
-                    entry.rxPackets = Long.parseLong(values.get(2));
-                    entry.txBytes = Long.parseLong(values.get(9));
-                    entry.txPackets = Long.parseLong(values.get(10));
-
-                    if (activeIfaces.contains(entry.iface)) {
-                        // combine stats when iface is active
-                        stats.combineValues(entry);
-                    } else if (!knownIfaces.contains(entry.iface)) {
-                        // add stats when iface is unknown
-                        stats.addValues(entry);
-                    }
-                } catch (NumberFormatException e) {
-                    Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
-                }
-            }
-        } catch (NullPointerException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
-        } catch (NumberFormatException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
-        } catch (IOException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
-        } finally {
-            IoUtils.closeQuietly(reader);
-        }
-
-        return stats;
-    }
-
-    private NetworkStats getNetworkStatsSummarySingleFile() {
-        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-
-        // TODO: read directly from proc once headers are added
-        final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
-                KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
-                KEY_RX_PACKETS, KEY_TX_BYTES, KEY_TX_PACKETS);
-        final ArrayList<String> values = Lists.newArrayList();
-        final HashMap<String, String> parsed = Maps.newHashMap();
-
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new FileReader(mStatsXtIfaceAll));
-
-            String line;
-            while ((line = reader.readLine()) != null) {
-                splitLine(line, values);
-                parseLine(keys, values, parsed);
-
-                entry.iface = parsed.get(KEY_IFACE);
-                entry.uid = UID_ALL;
-                entry.set = SET_DEFAULT;
-                entry.tag = TAG_NONE;
-
-                // always include snapshot values
-                entry.rxBytes = getParsedLong(parsed, KEY_SNAP_RX_BYTES);
-                entry.rxPackets = getParsedLong(parsed, KEY_SNAP_RX_PACKETS);
-                entry.txBytes = getParsedLong(parsed, KEY_SNAP_TX_BYTES);
-                entry.txPackets = getParsedLong(parsed, KEY_SNAP_TX_PACKETS);
-
-                // fold in active numbers, but only when active
-                final boolean active = getParsedInt(parsed, KEY_ACTIVE) != 0;
-                if (active) {
-                    entry.rxBytes += getParsedLong(parsed, KEY_RX_BYTES);
-                    entry.rxPackets += getParsedLong(parsed, KEY_RX_PACKETS);
-                    entry.txBytes += getParsedLong(parsed, KEY_TX_BYTES);
-                    entry.txPackets += getParsedLong(parsed, KEY_TX_PACKETS);
-                }
-
-                stats.addValues(entry);
-            }
-        } catch (NullPointerException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
-        } catch (NumberFormatException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
-        } catch (IOException e) {
-            throw new IllegalStateException("problem parsing stats: " + e);
-        } finally {
-            IoUtils.closeQuietly(reader);
-        }
-
-        return stats;
+        return mStatsFactory.readNetworkStatsSummary();
     }
 
     @Override
     public NetworkStats getNetworkStatsDetail() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
-
-        if (mBandwidthControlEnabled) {
-            return getNetworkStatsDetailNetfilter(UID_ALL);
-        } else {
-            return getNetworkStatsDetailUidstat(UID_ALL);
-        }
+        return mStatsFactory.readNetworkStatsDetail(UID_ALL);
     }
 
     @Override
@@ -1422,126 +1220,7 @@
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
         }
-
-        if (mBandwidthControlEnabled) {
-            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);
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-
-        // TODO: remove knownLines check once 5087722 verified
-        final HashSet<String> knownLines = Sets.newHashSet();
-        // TODO: remove lastIdx check once 5270106 verified
-        int lastIdx;
-
-        final ArrayList<String> keys = Lists.newArrayList();
-        final ArrayList<String> values = Lists.newArrayList();
-        final HashMap<String, String> parsed = Maps.newHashMap();
-
-        BufferedReader reader = null;
-        String line = null;
-        try {
-            reader = new BufferedReader(new FileReader(mStatsXtUid));
-
-            // parse first line as header
-            line = reader.readLine();
-            splitLine(line, keys);
-            lastIdx = 1;
-
-            // parse remaining lines
-            while ((line = reader.readLine()) != null) {
-                splitLine(line, values);
-                parseLine(keys, values, parsed);
-
-                if (!knownLines.add(line)) {
-                    throw new IllegalStateException("duplicate proc entry: " + line);
-                }
-
-                final int idx = getParsedInt(parsed, KEY_IDX);
-                if (idx != lastIdx + 1) {
-                    throw new IllegalStateException(
-                            "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
-                }
-                lastIdx = idx;
-
-                entry.iface = parsed.get(KEY_IFACE);
-                entry.uid = getParsedInt(parsed, KEY_UID);
-                entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
-                entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
-                entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
-                entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
-                entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
-                entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
-
-                if (limitUid == UID_ALL || limitUid == entry.uid) {
-                    stats.addValues(entry);
-                }
-            }
-        } catch (NullPointerException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
-        } catch (NumberFormatException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
-        } catch (IOException e) {
-            throw new IllegalStateException("problem parsing line: " + line, e);
-        } finally {
-            IoUtils.closeQuietly(reader);
-        }
-
-        return stats;
-    }
-
-    private static int getParsedInt(HashMap<String, String> parsed, String key) {
-        final String value = parsed.get(key);
-        return value != null ? Integer.parseInt(value) : 0;
-    }
-
-    private static long getParsedLong(HashMap<String, String> parsed, String key) {
-        final String value = parsed.get(key);
-        return value != null ? Long.parseLong(value) : 0;
-    }
-
-    /**
-     * 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 = fileListWithoutNull(mStatsUid);
-        } else {
-            knownUids = new String[] { String.valueOf(limitUid) };
-        }
-
-        final NetworkStats stats = new NetworkStats(
-                SystemClock.elapsedRealtime(), knownUids.length);
-        final NetworkStats.Entry entry = new NetworkStats.Entry();
-        for (String uid : knownUids) {
-            final int uidInt = Integer.parseInt(uid);
-            final File uidPath = new File(mStatsUid, uid);
-
-            entry.iface = IFACE_ALL;
-            entry.uid = uidInt;
-            entry.tag = TAG_NONE;
-            entry.rxBytes = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
-            entry.rxPackets = readSingleLongFromFile(new File(uidPath, "tcp_rcv_pkt"));
-            entry.txBytes = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
-            entry.txPackets = readSingleLongFromFile(new File(uidPath, "tcp_snd_pkt"));
-
-            stats.addValues(entry);
-        }
-
-        return stats;
+        return mStatsFactory.readNetworkStatsDetail(uid);
     }
 
     @Override
@@ -1670,56 +1349,6 @@
         return getInterfaceThrottle(iface, false);
     }
 
-    /**
-     * Split given line into {@link ArrayList}.
-     */
-    private static void splitLine(String line, ArrayList<String> outSplit) {
-        outSplit.clear();
-
-        final StringTokenizer t = new StringTokenizer(line, " \t\n\r\f:");
-        while (t.hasMoreTokens()) {
-            outSplit.add(t.nextToken());
-        }
-    }
-
-    /**
-     * Zip the two given {@link ArrayList} as key and value pairs into
-     * {@link HashMap}.
-     */
-    private static void parseLine(
-            ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
-        outParsed.clear();
-
-        final int size = Math.min(keys.size(), values.size());
-        for (int i = 0; i < size; i++) {
-            outParsed.put(keys.get(i), values.get(i));
-        }
-    }
-
-    /**
-     * Utility method to read a single plain-text {@link Long} from the given
-     * {@link File}, usually from a {@code /proc/} filesystem.
-     */
-    private static long readSingleLongFromFile(File file) {
-        try {
-            final byte[] buffer = IoUtils.readFileAsByteArray(file.toString());
-            return Long.parseLong(new String(buffer).trim());
-        } catch (NumberFormatException e) {
-            return -1;
-        } catch (IOException e) {
-            return -1;
-        }
-    }
-
-    /**
-     * Wrapper for {@link File#list()} that returns empty array instead of
-     * {@code null}.
-     */
-    private static String[] fileListWithoutNull(File file) {
-        final String[] list = file.list();
-        return list != null ? list : new String[0];
-    }
-
     public void setDefaultInterfaceForDns(String iface) throws IllegalStateException {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index b44dc8a2..8f797ec 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -417,6 +417,13 @@
         }
     }
 
+    public void noteNetworkInterfaceType(String iface, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteNetworkInterfaceTypeLocked(iface, type);
+        }
+    }
+
     public boolean isOnBattery() {
         return mStats.isOnBattery();
     }