Network stats with varint, omit parcel fields.

Persist NetworkStatsHistory using variable-length encoding; since
most buckets have small numbers, we can encode them tighter. Initial
test showed 44% space savings. Also persist packet and operation
counters.

Let NetworkStatsHistory consumers request which fields they actually
need to reduce parcel overhead.

Tests for verify varint and history field requests, also verify end-
to-end by persisting history into byte[] and restoring. Expose
bandwidth control enabled state. Extend random generation to create
packet and operation counts. Moved operation counts to long.

Fix bug that miscalculated bytes since last persist, which would
cause partial stats loss when battery pulled.

Bug: 4581977, 5023706, 5023635, 5096903
Change-Id: If61e89f681ffa11fe5711471fd9f7c238d3d37b0
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index c41d182..b65506c 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -24,9 +24,9 @@
 interface INetworkStatsService {
 
     /** Return historical network layer stats for traffic that matches template. */
-    NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template);
+    NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields);
     /** Return historical network layer stats for specific UID traffic that matches template. */
-    NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int tag);
+    NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int tag, int fields);
 
     /** Return network layer usage summary for traffic that matches template. */
     NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 0e8e7fc..f2fcb8f 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -58,7 +58,7 @@
     private long[] rxPackets;
     private long[] txBytes;
     private long[] txPackets;
-    private int[] operations;
+    private long[] operations;
 
     public static class Entry {
         public String iface;
@@ -68,13 +68,18 @@
         public long rxPackets;
         public long txBytes;
         public long txPackets;
-        public int operations;
+        public long operations;
 
         public Entry() {
+            this(IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+        }
+
+        public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+            this(IFACE_ALL, UID_ALL, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, operations);
         }
 
         public Entry(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes,
-                long txPackets, int operations) {
+                long txPackets, long operations) {
             this.iface = iface;
             this.uid = uid;
             this.tag = tag;
@@ -96,7 +101,7 @@
         this.rxPackets = new long[initialSize];
         this.txBytes = new long[initialSize];
         this.txPackets = new long[initialSize];
-        this.operations = new int[initialSize];
+        this.operations = new long[initialSize];
     }
 
     public NetworkStats(Parcel parcel) {
@@ -109,7 +114,7 @@
         rxPackets = parcel.createLongArray();
         txBytes = parcel.createLongArray();
         txPackets = parcel.createLongArray();
-        operations = parcel.createIntArray();
+        operations = parcel.createLongArray();
     }
 
     /** {@inheritDoc} */
@@ -123,16 +128,16 @@
         dest.writeLongArray(rxPackets);
         dest.writeLongArray(txBytes);
         dest.writeLongArray(txPackets);
-        dest.writeIntArray(operations);
+        dest.writeLongArray(operations);
     }
 
     public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
             long txBytes, long txPackets) {
-        return addValues(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, 0);
+        return addValues(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, 0L);
     }
 
     public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
-            long txBytes, long txPackets, int operations) {
+            long txBytes, long txPackets, long operations) {
         return addValues(
                 new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
     }
@@ -197,7 +202,7 @@
     }
 
     public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
-            long txBytes, long txPackets, int operations) {
+            long txBytes, long txPackets, long operations) {
         return combineValues(
                 new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
     }
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 7a4b811..c917af9 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -19,11 +19,11 @@
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
-import static android.net.NetworkStatsHistory.DataStreamUtils.readLongArray;
-import static android.net.NetworkStatsHistory.DataStreamUtils.writeLongArray;
-import static android.net.NetworkStatsHistory.ParcelUtils.readIntArray;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
 import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
-import static android.net.NetworkStatsHistory.ParcelUtils.writeIntArray;
 import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
 
 import android.os.Parcel;
@@ -51,42 +51,53 @@
  */
 public class NetworkStatsHistory implements Parcelable {
     private static final int VERSION_INIT = 1;
+    private static final int VERSION_ADD_PACKETS = 2;
 
-    // TODO: teach about varint encoding to use less disk space
-    // TODO: teach about omitting entire fields to reduce parcel pressure
-    // TODO: persist/restore packet and operation counts
+    public static final int FIELD_RX_BYTES = 0x01;
+    public static final int FIELD_RX_PACKETS = 0x02;
+    public static final int FIELD_TX_BYTES = 0x04;
+    public static final int FIELD_TX_PACKETS = 0x08;
+    public static final int FIELD_OPERATIONS = 0x10;
 
-    private final long bucketDuration;
+    public static final int FIELD_ALL = 0xFFFFFFFF;
+
+    private long bucketDuration;
     private int bucketCount;
     private long[] bucketStart;
     private long[] rxBytes;
     private long[] rxPackets;
     private long[] txBytes;
     private long[] txPackets;
-    private int[] operations;
+    private long[] operations;
 
     public static class Entry {
+        public static final long UNKNOWN = -1;
+
         public long bucketStart;
         public long bucketDuration;
         public long rxBytes;
         public long rxPackets;
         public long txBytes;
         public long txPackets;
-        public int operations;
+        public long operations;
     }
 
     public NetworkStatsHistory(long bucketDuration) {
-        this(bucketDuration, 10);
+        this(bucketDuration, 10, FIELD_ALL);
     }
 
     public NetworkStatsHistory(long bucketDuration, int initialSize) {
+        this(bucketDuration, initialSize, FIELD_ALL);
+    }
+
+    public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
         this.bucketDuration = bucketDuration;
         bucketStart = new long[initialSize];
-        rxBytes = new long[initialSize];
-        rxPackets = new long[initialSize];
-        txBytes = new long[initialSize];
-        txPackets = new long[initialSize];
-        operations = new int[initialSize];
+        if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize];
+        if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize];
+        if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize];
+        if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
+        if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
         bucketCount = 0;
     }
 
@@ -97,7 +108,7 @@
         rxPackets = readLongArray(in);
         txBytes = readLongArray(in);
         txPackets = readLongArray(in);
-        operations = readIntArray(in);
+        operations = readLongArray(in);
         bucketCount = bucketStart.length;
     }
 
@@ -109,21 +120,31 @@
         writeLongArray(out, rxPackets, bucketCount);
         writeLongArray(out, txBytes, bucketCount);
         writeLongArray(out, txPackets, bucketCount);
-        writeIntArray(out, operations, bucketCount);
+        writeLongArray(out, operations, bucketCount);
     }
 
     public NetworkStatsHistory(DataInputStream in) throws IOException {
-        // TODO: read packet and operation counts
         final int version = in.readInt();
         switch (version) {
             case VERSION_INIT: {
                 bucketDuration = in.readLong();
-                bucketStart = readLongArray(in);
-                rxBytes = readLongArray(in);
+                bucketStart = readFullLongArray(in);
+                rxBytes = readFullLongArray(in);
                 rxPackets = new long[bucketStart.length];
-                txBytes = readLongArray(in);
+                txBytes = readFullLongArray(in);
                 txPackets = new long[bucketStart.length];
-                operations = new int[bucketStart.length];
+                operations = new long[bucketStart.length];
+                bucketCount = bucketStart.length;
+                break;
+            }
+            case VERSION_ADD_PACKETS: {
+                bucketDuration = in.readLong();
+                bucketStart = readVarLongArray(in);
+                rxBytes = readVarLongArray(in);
+                rxPackets = readVarLongArray(in);
+                txBytes = readVarLongArray(in);
+                txPackets = readVarLongArray(in);
+                operations = readVarLongArray(in);
                 bucketCount = bucketStart.length;
                 break;
             }
@@ -134,12 +155,14 @@
     }
 
     public void writeToStream(DataOutputStream out) throws IOException {
-        // TODO: write packet and operation counts
-        out.writeInt(VERSION_INIT);
+        out.writeInt(VERSION_ADD_PACKETS);
         out.writeLong(bucketDuration);
-        writeLongArray(out, bucketStart, bucketCount);
-        writeLongArray(out, rxBytes, bucketCount);
-        writeLongArray(out, txBytes, bucketCount);
+        writeVarLongArray(out, bucketStart, bucketCount);
+        writeVarLongArray(out, rxBytes, bucketCount);
+        writeVarLongArray(out, rxPackets, bucketCount);
+        writeVarLongArray(out, txBytes, bucketCount);
+        writeVarLongArray(out, txPackets, bucketCount);
+        writeVarLongArray(out, operations, bucketCount);
     }
 
     /** {@inheritDoc} */
@@ -178,11 +201,11 @@
         final Entry entry = recycle != null ? recycle : new Entry();
         entry.bucketStart = bucketStart[i];
         entry.bucketDuration = bucketDuration;
-        entry.rxBytes = rxBytes[i];
-        entry.rxPackets = rxPackets[i];
-        entry.txBytes = txBytes[i];
-        entry.txPackets = txPackets[i];
-        entry.operations = operations[i];
+        entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
+        entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
+        entry.txBytes = getLong(txBytes, i, UNKNOWN);
+        entry.txPackets = getLong(txPackets, i, UNKNOWN);
+        entry.operations = getLong(operations, i, UNKNOWN);
         return entry;
     }
 
@@ -193,7 +216,7 @@
     @Deprecated
     public void recordData(long start, long end, long rxBytes, long txBytes) {
         recordData(start, end,
-                new NetworkStats.Entry(IFACE_ALL, UID_ALL, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0));
+                new NetworkStats.Entry(IFACE_ALL, UID_ALL, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L));
     }
 
     /**
@@ -230,11 +253,11 @@
             final long fracTxPackets = entry.txPackets * overlap / duration;
             final int fracOperations = (int) (entry.operations * overlap / duration);
 
-            rxBytes[i] += fracRxBytes; entry.rxBytes -= fracRxBytes;
-            rxPackets[i] += fracRxPackets; entry.rxPackets -= fracRxPackets;
-            txBytes[i] += fracTxBytes; entry.txBytes -= fracTxBytes;
-            txPackets[i] += fracTxPackets; entry.txPackets -= fracTxPackets;
-            operations[i] += fracOperations; entry.operations -= fracOperations;
+            addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes;
+            addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets;
+            addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes;
+            addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets;
+            addLong(operations, i, fracOperations); entry.operations -= fracOperations;
 
             duration -= overlap;
         }
@@ -246,16 +269,16 @@
      */
     public void recordEntireHistory(NetworkStatsHistory input) {
         final NetworkStats.Entry entry = new NetworkStats.Entry(
-                IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+                IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
         for (int i = 0; i < input.bucketCount; i++) {
             final long start = input.bucketStart[i];
             final long end = start + input.bucketDuration;
 
-            entry.rxBytes = input.rxBytes[i];
-            entry.rxPackets = input.rxPackets[i];
-            entry.txBytes = input.txBytes[i];
-            entry.txPackets = input.txPackets[i];
-            entry.operations = input.operations[i];
+            entry.rxBytes = getLong(input.rxBytes, i, 0L);
+            entry.rxPackets = getLong(input.rxPackets, i, 0L);
+            entry.txBytes = getLong(input.txBytes, i, 0L);
+            entry.txPackets = getLong(input.txPackets, i, 0L);
+            entry.operations = getLong(input.operations, i, 0L);
 
             recordData(start, end, entry);
         }
@@ -287,11 +310,11 @@
         if (bucketCount >= bucketStart.length) {
             final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
             bucketStart = Arrays.copyOf(bucketStart, newLength);
-            rxBytes = Arrays.copyOf(rxBytes, newLength);
-            rxPackets = Arrays.copyOf(rxPackets, newLength);
-            txBytes = Arrays.copyOf(txBytes, newLength);
-            txPackets = Arrays.copyOf(txPackets, newLength);
-            operations = Arrays.copyOf(operations, newLength);
+            if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
+            if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
+            if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
+            if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
+            if (operations != null) operations = Arrays.copyOf(operations, newLength);
         }
 
         // create gap when inserting bucket in middle
@@ -300,19 +323,19 @@
             final int length = bucketCount - index;
 
             System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
-            System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
-            System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
-            System.arraycopy(txBytes, index, txBytes, dstPos, length);
-            System.arraycopy(txPackets, index, txPackets, dstPos, length);
-            System.arraycopy(operations, index, operations, dstPos, length);
+            if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
+            if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
+            if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
+            if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
+            if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
         }
 
         bucketStart[index] = start;
-        rxBytes[index] = 0;
-        rxPackets[index] = 0;
-        txBytes[index] = 0;
-        txPackets[index] = 0;
-        operations[index] = 0;
+        setLong(rxBytes, index, 0L);
+        setLong(rxPackets, index, 0L);
+        setLong(txBytes, index, 0L);
+        setLong(txPackets, index, 0L);
+        setLong(operations, index, 0L);
         bucketCount++;
     }
 
@@ -333,11 +356,11 @@
         if (i > 0) {
             final int length = bucketStart.length;
             bucketStart = Arrays.copyOfRange(bucketStart, i, length);
-            rxBytes = Arrays.copyOfRange(rxBytes, i, length);
-            rxPackets = Arrays.copyOfRange(rxPackets, i, length);
-            txBytes = Arrays.copyOfRange(txBytes, i, length);
-            txPackets = Arrays.copyOfRange(txPackets, i, length);
-            operations = Arrays.copyOfRange(operations, i, length);
+            if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
+            if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
+            if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
+            if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
+            if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
             bucketCount -= i;
         }
     }
@@ -358,11 +381,11 @@
         final Entry entry = recycle != null ? recycle : new Entry();
         entry.bucketStart = start;
         entry.bucketDuration = end - start;
-        entry.rxBytes = 0;
-        entry.rxPackets = 0;
-        entry.txBytes = 0;
-        entry.txPackets = 0;
-        entry.operations = 0;
+        entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
+        entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
+        entry.txBytes = txBytes != null ? 0 : UNKNOWN;
+        entry.txPackets = txPackets != null ? 0 : UNKNOWN;
+        entry.operations = operations != null ? 0 : UNKNOWN;
 
         for (int i = bucketCount - 1; i >= 0; i--) {
             final long curStart = bucketStart[i];
@@ -380,11 +403,11 @@
             if (overlap <= 0) continue;
 
             // integer math each time is faster than floating point
-            entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
-            entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
-            entry.txBytes += txBytes[i] * overlap / bucketDuration;
-            entry.txPackets += txPackets[i] * overlap / bucketDuration;
-            entry.operations += operations[i] * overlap / bucketDuration;
+            if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration;
+            if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration;
+            if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration;
+            if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration;
+            if (operations != null) entry.operations += operations[i] * overlap / bucketDuration;
         }
 
         return entry;
@@ -394,19 +417,29 @@
      * @deprecated only for temporary testing
      */
     @Deprecated
-    public void generateRandom(long start, long end, long rx, long tx) {
+    public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
+            long txPackets, long operations) {
         ensureBuckets(start, end);
 
         final NetworkStats.Entry entry = new NetworkStats.Entry(
-                IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0);
+                IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
         final Random r = new Random();
-        while (rx > 1024 && tx > 1024) {
+        while (rxBytes > 1024 && rxPackets > 128 && txBytes > 1024 && txPackets > 128
+                && operations > 32) {
             final long curStart = randomLong(r, start, end);
             final long curEnd = randomLong(r, curStart, end);
-            entry.rxBytes = randomLong(r, 0, rx);
-            entry.txBytes = randomLong(r, 0, tx);
-            rx -= entry.rxBytes;
-            tx -= entry.txBytes;
+
+            entry.rxBytes = randomLong(r, 0, rxBytes);
+            entry.rxPackets = randomLong(r, 0, rxPackets);
+            entry.txBytes = randomLong(r, 0, txBytes);
+            entry.txPackets = randomLong(r, 0, txPackets);
+            entry.operations = randomLong(r, 0, operations);
+
+            rxBytes -= entry.rxBytes;
+            rxPackets -= entry.rxPackets;
+            txBytes -= entry.txBytes;
+            txPackets -= entry.txPackets;
+            operations -= entry.operations;
 
             recordData(curStart, curEnd, entry);
         }
@@ -429,11 +462,12 @@
         for (int i = start; i < bucketCount; i++) {
             pw.print(prefix);
             pw.print("  bucketStart="); pw.print(bucketStart[i]);
-            pw.print(" rxBytes="); pw.print(rxBytes[i]);
-            pw.print(" rxPackets="); pw.print(rxPackets[i]);
-            pw.print(" txBytes="); pw.print(txBytes[i]);
-            pw.print(" txPackets="); pw.print(txPackets[i]);
-            pw.print(" operations="); pw.println(operations[i]);
+            if (rxBytes != null) pw.print(" rxBytes="); pw.print(rxBytes[i]);
+            if (rxPackets != null) pw.print(" rxPackets="); pw.print(rxPackets[i]);
+            if (txBytes != null) pw.print(" txBytes="); pw.print(txBytes[i]);
+            if (txPackets != null) pw.print(" txPackets="); pw.print(txPackets[i]);
+            if (operations != null) pw.print(" operations="); pw.print(operations[i]);
+            pw.println();
         }
     }
 
@@ -454,12 +488,25 @@
         }
     };
 
+    private static long getLong(long[] array, int i, long value) {
+        return array != null ? array[i] : value;
+    }
+
+    private static void setLong(long[] array, int i, long value) {
+        if (array != null) array[i] = value;
+    }
+
+    private static void addLong(long[] array, int i, long value) {
+        if (array != null) array[i] += value;
+    }
+
     /**
      * Utility methods for interacting with {@link DataInputStream} and
      * {@link DataOutputStream}, mostly dealing with writing partial arrays.
      */
     public static class DataStreamUtils {
-        public static long[] readLongArray(DataInputStream in) throws IOException {
+        @Deprecated
+        public static long[] readFullLongArray(DataInputStream in) throws IOException {
             final int size = in.readInt();
             final long[] values = new long[size];
             for (int i = 0; i < values.length; i++) {
@@ -468,14 +515,59 @@
             return values;
         }
 
-        public static void writeLongArray(DataOutputStream out, long[] values, int size)
+        /**
+         * Read variable-length {@link Long} using protobuf-style approach.
+         */
+        public static long readVarLong(DataInputStream in) throws IOException {
+            int shift = 0;
+            long result = 0;
+            while (shift < 64) {
+                byte b = in.readByte();
+                result |= (long) (b & 0x7F) << shift;
+                if ((b & 0x80) == 0)
+                    return result;
+                shift += 7;
+            }
+            throw new ProtocolException("malformed long");
+        }
+
+        /**
+         * Write variable-length {@link Long} using protobuf-style approach.
+         */
+        public static void writeVarLong(DataOutputStream out, long value) throws IOException {
+            while (true) {
+                if ((value & ~0x7FL) == 0) {
+                    out.writeByte((int) value);
+                    return;
+                } else {
+                    out.writeByte(((int) value & 0x7F) | 0x80);
+                    value >>>= 7;
+                }
+            }
+        }
+
+        public static long[] readVarLongArray(DataInputStream in) throws IOException {
+            final int size = in.readInt();
+            if (size == -1) return null;
+            final long[] values = new long[size];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = readVarLong(in);
+            }
+            return values;
+        }
+
+        public static void writeVarLongArray(DataOutputStream out, long[] values, int size)
                 throws IOException {
+            if (values == null) {
+                out.writeInt(-1);
+                return;
+            }
             if (size > values.length) {
                 throw new IllegalArgumentException("size larger than length");
             }
             out.writeInt(size);
             for (int i = 0; i < size; i++) {
-                out.writeLong(values[i]);
+                writeVarLong(out, values[i]);
             }
         }
     }
@@ -487,6 +579,7 @@
     public static class ParcelUtils {
         public static long[] readLongArray(Parcel in) {
             final int size = in.readInt();
+            if (size == -1) return null;
             final long[] values = new long[size];
             for (int i = 0; i < values.length; i++) {
                 values[i] = in.readLong();
@@ -495,6 +588,10 @@
         }
 
         public static void writeLongArray(Parcel out, long[] values, int size) {
+            if (values == null) {
+                out.writeInt(-1);
+                return;
+            }
             if (size > values.length) {
                 throw new IllegalArgumentException("size larger than length");
             }
@@ -503,25 +600,6 @@
                 out.writeLong(values[i]);
             }
         }
-
-        public static int[] readIntArray(Parcel in) {
-            final int size = in.readInt();
-            final int[] values = new int[size];
-            for (int i = 0; i < values.length; i++) {
-                values[i] = in.readInt();
-            }
-            return values;
-        }
-
-        public static void writeIntArray(Parcel out, int[] values, int size) {
-            if (size > values.length) {
-                throw new IllegalArgumentException("size larger than length");
-            }
-            out.writeInt(size);
-            for (int i = 0; i < size; i++) {
-                out.writeInt(values[i]);
-            }
-        }
     }
 
 }
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 3704248..bc4e00c 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -240,6 +240,11 @@
     void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces);
 
     /**
+     * Return status of bandwidth control module.
+     */
+    boolean isBandwidthControlEnabled();
+
+    /**
      * Configures bandwidth throttling on an interface.
      */
     void setInterfaceThrottle(String iface, int rxKbps, int txKbps);
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
index 242057c..4db4ea5 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -16,6 +16,14 @@
 
 package android.net;
 
+import static android.net.NetworkStatsHistory.FIELD_ALL;
+import static android.net.NetworkStatsHistory.FIELD_OPERATIONS;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_RX_PACKETS;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLong;
+import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLong;
+import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
 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;
@@ -30,7 +38,10 @@
 
 import com.android.frameworks.coretests.R;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.util.Random;
 
 @SmallTest
@@ -39,6 +50,10 @@
 
     private static final long TEST_START = 1194220800000L;
 
+    private static final long KB_IN_BYTES = 1024;
+    private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
+    private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+
     private NetworkStatsHistory stats;
 
     @Override
@@ -80,10 +95,11 @@
         stats = new NetworkStatsHistory(BUCKET_SIZE);
 
         // record data into narrow window to get single bucket
-        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
+        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 2L));
 
         assertEquals(1, stats.size());
-        assertValues(stats, 0, 1024L, 2048L);
+        assertValues(stats, 0, 1024L, 10L, 2048L, 20L, 2L);
     }
 
     public void testRecordEqualBuckets() throws Exception {
@@ -92,11 +108,12 @@
 
         // split equally across two buckets
         final long recordStart = TEST_START + (bucketDuration / 2);
-        stats.recordData(recordStart, recordStart + bucketDuration, 1024L, 128L);
+        stats.recordData(recordStart, recordStart + bucketDuration,
+                new NetworkStats.Entry(1024L, 10L, 128L, 2L, 2L));
 
         assertEquals(2, stats.size());
-        assertValues(stats, 0, 512L, 64L);
-        assertValues(stats, 1, 512L, 64L);
+        assertValues(stats, 0, 512L, 5L, 64L, 1L, 1L);
+        assertValues(stats, 1, 512L, 5L, 64L, 1L, 1L);
     }
 
     public void testRecordTouchingBuckets() throws Exception {
@@ -107,15 +124,16 @@
         // overlap into neighboring buckets. total record is 20 minutes.
         final long recordStart = (TEST_START + BUCKET_SIZE) - MINUTE_IN_MILLIS;
         final long recordEnd = (TEST_START + (BUCKET_SIZE * 2)) + (MINUTE_IN_MILLIS * 4);
-        stats.recordData(recordStart, recordEnd, 1000L, 5000L);
+        stats.recordData(recordStart, recordEnd,
+                new NetworkStats.Entry(1000L, 2000L, 5000L, 10000L, 100L));
 
         assertEquals(3, stats.size());
         // first bucket should have (1/20 of value)
-        assertValues(stats, 0, 50L, 250L);
+        assertValues(stats, 0, 50L, 100L, 250L, 500L, 5L);
         // second bucket should have (15/20 of value)
-        assertValues(stats, 1, 750L, 3750L);
+        assertValues(stats, 1, 750L, 1500L, 3750L, 7500L, 75L);
         // final bucket should have (4/20 of value)
-        assertValues(stats, 2, 200L, 1000L);
+        assertValues(stats, 2, 200L, 400L, 1000L, 2000L, 20L);
     }
 
     public void testRecordGapBuckets() throws Exception {
@@ -125,25 +143,28 @@
         // record some data today and next week with large gap
         final long firstStart = TEST_START;
         final long lastStart = TEST_START + WEEK_IN_MILLIS;
-        stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS, 128L, 256L);
-        stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS, 64L, 512L);
+        stats.recordData(firstStart, firstStart + SECOND_IN_MILLIS,
+                new NetworkStats.Entry(128L, 2L, 256L, 4L, 1L));
+        stats.recordData(lastStart, lastStart + SECOND_IN_MILLIS,
+                new NetworkStats.Entry(64L, 1L, 512L, 8L, 2L));
 
         // we should have two buckets, far apart from each other
         assertEquals(2, stats.size());
-        assertValues(stats, 0, 128L, 256L);
-        assertValues(stats, 1, 64L, 512L);
+        assertValues(stats, 0, 128L, 2L, 256L, 4L, 1L);
+        assertValues(stats, 1, 64L, 1L, 512L, 8L, 2L);
 
         // now record something in middle, spread across two buckets
         final long middleStart = TEST_START + DAY_IN_MILLIS;
         final long middleEnd = middleStart + (HOUR_IN_MILLIS * 2);
-        stats.recordData(middleStart, middleEnd, 2048L, 2048L);
+        stats.recordData(middleStart, middleEnd,
+                new NetworkStats.Entry(2048L, 4L, 2048L, 4L, 2L));
 
         // now should have four buckets, with new record in middle two buckets
         assertEquals(4, stats.size());
-        assertValues(stats, 0, 128L, 256L);
-        assertValues(stats, 1, 1024L, 1024L);
-        assertValues(stats, 2, 1024L, 1024L);
-        assertValues(stats, 3, 64L, 512L);
+        assertValues(stats, 0, 128L, 2L, 256L, 4L, 1L);
+        assertValues(stats, 1, 1024L, 2L, 1024L, 2L, 1L);
+        assertValues(stats, 2, 1024L, 2L, 1024L, 2L, 1L);
+        assertValues(stats, 3, 64L, 1L, 512L, 8L, 2L);
     }
 
     public void testRecordOverlapBuckets() throws Exception {
@@ -151,14 +172,16 @@
         stats = new NetworkStatsHistory(BUCKET_SIZE);
 
         // record some data in one bucket, and another overlapping buckets
-        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 256L, 256L);
+        stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS,
+                new NetworkStats.Entry(256L, 2L, 256L, 2L, 1L));
         final long midStart = TEST_START + (HOUR_IN_MILLIS / 2);
-        stats.recordData(midStart, midStart + HOUR_IN_MILLIS, 1024L, 1024L);
+        stats.recordData(midStart, midStart + HOUR_IN_MILLIS,
+                new NetworkStats.Entry(1024L, 10L, 1024L, 10L, 10L));
 
         // should have two buckets, with some data mixed together
         assertEquals(2, stats.size());
-        assertValues(stats, 0, 768L, 768L);
-        assertValues(stats, 1, 512L, 512L);
+        assertValues(stats, 0, 768L, 7L, 768L, 7L, 6L);
+        assertValues(stats, 1, 512L, 5L, 512L, 5L, 5L);
     }
 
     public void testRecordEntireGapIdentical() throws Exception {
@@ -283,6 +306,7 @@
     public void testFuzzing() throws Exception {
         try {
             // fuzzing with random events, looking for crashes
+            final NetworkStats.Entry entry = new NetworkStats.Entry();
             final Random r = new Random();
             for (int i = 0; i < 500; i++) {
                 stats = new NetworkStatsHistory(r.nextLong());
@@ -291,7 +315,12 @@
                         // add range
                         final long start = r.nextLong();
                         final long end = start + r.nextInt();
-                        stats.recordData(start, end, r.nextLong(), r.nextLong());
+                        entry.rxBytes = nextPositiveLong(r);
+                        entry.rxPackets = nextPositiveLong(r);
+                        entry.txBytes = nextPositiveLong(r);
+                        entry.txPackets = nextPositiveLong(r);
+                        entry.operations = nextPositiveLong(r);
+                        stats.recordData(start, end, entry);
                     } else {
                         // trim something
                         stats.removeBucketsBefore(r.nextLong());
@@ -305,6 +334,88 @@
         }
     }
 
+    private static long nextPositiveLong(Random r) {
+        final long value = r.nextLong();
+        return value < 0 ? -value : value;
+    }
+
+    public void testIgnoreFields() throws Exception {
+        final NetworkStatsHistory history = new NetworkStatsHistory(
+                MINUTE_IN_MILLIS, 0, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+        history.recordData(0, MINUTE_IN_MILLIS,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+        history.recordData(0, MINUTE_IN_MILLIS * 2,
+                new NetworkStats.Entry(2L, 2L, 2L, 2L, 2L));
+
+        assertValues(
+                history, Long.MIN_VALUE, Long.MAX_VALUE, 1026L, UNKNOWN, 2050L, UNKNOWN, UNKNOWN);
+    }
+
+    public void testIgnoreFieldsRecordIn() throws Exception {
+        final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+        final NetworkStatsHistory partial = new NetworkStatsHistory(
+                MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+        full.recordData(0, MINUTE_IN_MILLIS,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+        partial.recordEntireHistory(full);
+
+        assertValues(partial, Long.MIN_VALUE, Long.MAX_VALUE, UNKNOWN, 10L, UNKNOWN, UNKNOWN, 4L);
+    }
+
+    public void testIgnoreFieldsRecordOut() throws Exception {
+        final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
+        final NetworkStatsHistory partial = new NetworkStatsHistory(
+                MINUTE_IN_MILLIS, 0, FIELD_RX_PACKETS | FIELD_OPERATIONS);
+
+        partial.recordData(0, MINUTE_IN_MILLIS,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+        full.recordEntireHistory(partial);
+
+        assertValues(full, Long.MIN_VALUE, Long.MAX_VALUE, 0L, 10L, 0L, 0L, 4L);
+    }
+
+    public void testSerialize() throws Exception {
+        final NetworkStatsHistory before = new NetworkStatsHistory(MINUTE_IN_MILLIS, 40, FIELD_ALL);
+        before.recordData(0, MINUTE_IN_MILLIS * 4,
+                new NetworkStats.Entry(1024L, 10L, 2048L, 20L, 4L));
+        before.recordData(DAY_IN_MILLIS, DAY_IN_MILLIS + MINUTE_IN_MILLIS,
+                new NetworkStats.Entry(10L, 20L, 30L, 40L, 50L));
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        before.writeToStream(new DataOutputStream(out));
+        out.close();
+
+        final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        final NetworkStatsHistory after = new NetworkStatsHistory(new DataInputStream(in));
+
+        // must have identical totals before and after
+        assertValues(before, Long.MIN_VALUE, Long.MAX_VALUE, 1034L, 30L, 2078L, 60L, 54L);
+        assertValues(after, Long.MIN_VALUE, Long.MAX_VALUE, 1034L, 30L, 2078L, 60L, 54L);
+    }
+
+    public void testVarLong() throws Exception {
+        assertEquals(0L, performVarLong(0L));
+        assertEquals(-1L, performVarLong(-1L));
+        assertEquals(1024L, performVarLong(1024L));
+        assertEquals(-1024L, performVarLong(-1024L));
+        assertEquals(40 * MB_IN_BYTES, performVarLong(40 * MB_IN_BYTES));
+        assertEquals(512 * GB_IN_BYTES, performVarLong(512 * GB_IN_BYTES));
+        assertEquals(Long.MIN_VALUE, performVarLong(Long.MIN_VALUE));
+        assertEquals(Long.MAX_VALUE, performVarLong(Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE + 40, performVarLong(Long.MIN_VALUE + 40));
+        assertEquals(Long.MAX_VALUE - 40, performVarLong(Long.MAX_VALUE - 40));
+    }
+
+    private static long performVarLong(long before) throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeVarLong(new DataOutputStream(out), before);
+
+        final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+        return readVarLong(new DataInputStream(in));
+    }
+
     private static void assertConsistent(NetworkStatsHistory stats) {
         // verify timestamps are monotonic
         long lastStart = Long.MIN_VALUE;
@@ -330,4 +441,23 @@
         assertEquals("unexpected txBytes", txBytes, entry.txBytes);
     }
 
+    private static void assertValues(NetworkStatsHistory stats, int index, long rxBytes,
+            long rxPackets, long txBytes, long txPackets, long operations) {
+        final NetworkStatsHistory.Entry entry = stats.getValues(index, null);
+        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+        assertEquals("unexpected operations", operations, entry.operations);
+    }
+
+    private static void assertValues(NetworkStatsHistory stats, long start, long end, long rxBytes,
+            long rxPackets, long txBytes, long txPackets, long operations) {
+        final NetworkStatsHistory.Entry entry = stats.getValues(start, end, null);
+        assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+        assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+        assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+        assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+        assertEquals("unexpected operations", operations, entry.operations);
+    }
 }
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index a59b6c0..30de385 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -1231,6 +1231,13 @@
         }
     }
 
+    @Override
+    public boolean isBandwidthControlEnabled() {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        return mBandwidthControlEnabled;
+    }
+
+    @Override
     public NetworkStats getNetworkStatsUidDetail(int uid) {
         if (Binder.getCallingUid() != uid) {
             mContext.enforceCallingOrSelfPermission(
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 24188ca..deca7a9 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -16,10 +16,10 @@
 
 package com.android.server.net;
 
-import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
-import static android.Manifest.permission.MODIFY_NETWORK_ACCOUNTING;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MODIFY_NETWORK_ACCOUNTING;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
@@ -68,7 +68,6 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
-import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.NtpTrustedTime;
 import android.util.Slog;
@@ -282,13 +281,13 @@
     }
 
     @Override
-    public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template) {
+    public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
             // combine all interfaces that match template
             final NetworkStatsHistory combined = new NetworkStatsHistory(
-                    mSettings.getNetworkBucketDuration(), estimateNetworkBuckets());
+                    mSettings.getNetworkBucketDuration(), estimateNetworkBuckets(), fields);
             for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
                 if (templateMatches(template, ident)) {
                     final NetworkStatsHistory history = mNetworkStats.get(ident);
@@ -302,7 +301,8 @@
     }
 
     @Override
-    public NetworkStatsHistory getHistoryForUid(NetworkTemplate template, int uid, int tag) {
+    public NetworkStatsHistory getHistoryForUid(
+            NetworkTemplate template, int uid, int tag, int fields) {
         mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
 
         synchronized (mStatsLock) {
@@ -311,7 +311,7 @@
 
             // combine all interfaces that match template
             final NetworkStatsHistory combined = new NetworkStatsHistory(
-                    mSettings.getUidBucketDuration(), estimateUidBuckets());
+                    mSettings.getUidBucketDuration(), estimateUidBuckets(), fields);
             for (NetworkIdentitySet ident : mUidStats.keySet()) {
                 if (templateMatches(template, ident)) {
                     final NetworkStatsHistory history = mUidStats.get(ident).get(packed);
@@ -596,7 +596,7 @@
 
         // decide if enough has changed to trigger persist
         final NetworkStats persistDelta = computeStatsDelta(
-                mLastPersistNetworkSnapshot, networkSnapshot);
+                mLastPersistNetworkSnapshot, networkSnapshot, true);
         final long persistThreshold = mSettings.getPersistThreshold();
 
         NetworkStats.Entry entry = null;
@@ -626,7 +626,7 @@
     private void performNetworkPollLocked(NetworkStats networkSnapshot, long currentTime) {
         final HashSet<String> unknownIface = Sets.newHashSet();
 
-        final NetworkStats delta = computeStatsDelta(mLastNetworkSnapshot, networkSnapshot);
+        final NetworkStats delta = computeStatsDelta(mLastNetworkSnapshot, networkSnapshot, false);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -661,9 +661,9 @@
     private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
         ensureUidStatsLoadedLocked();
 
-        final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot);
+        final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot, false);
         final NetworkStats operationsDelta = computeStatsDelta(
-                mLastOperationsSnapshot, mOperations);
+                mLastOperationsSnapshot, mOperations, false);
         final long timeStart = currentTime - delta.getElapsedRealtime();
 
         NetworkStats.Entry entry = null;
@@ -932,6 +932,7 @@
             out.flush();
             mNetworkFile.finishWrite(fos);
         } catch (IOException e) {
+            Slog.w(TAG, "problem writing stats: ", e);
             if (fos != null) {
                 mNetworkFile.failWrite(fos);
             }
@@ -978,6 +979,7 @@
             out.flush();
             mUidFile.finishWrite(fos);
         } catch (IOException e) {
+            Slog.w(TAG, "problem writing stats: ", e);
             if (fos != null) {
                 mUidFile.failWrite(fos);
             }
@@ -1052,15 +1054,20 @@
      */
     @Deprecated
     private void generateRandomLocked() {
-        long networkEnd = System.currentTimeMillis();
-        long networkStart = networkEnd - mSettings.getNetworkMaxHistory();
-        long networkRx = 3 * GB_IN_BYTES;
-        long networkTx = 2 * GB_IN_BYTES;
+        final long NET_END = System.currentTimeMillis();
+        final long NET_START = NET_END - mSettings.getNetworkMaxHistory();
+        final long NET_RX_BYTES = 3 * GB_IN_BYTES;
+        final long NET_RX_PACKETS = NET_RX_BYTES / 1024;
+        final long NET_TX_BYTES = 2 * GB_IN_BYTES;
+        final long NET_TX_PACKETS = NET_TX_BYTES / 1024;
 
-        long uidEnd = System.currentTimeMillis();
-        long uidStart = uidEnd - mSettings.getUidMaxHistory();
-        long uidRx = 500 * MB_IN_BYTES;
-        long uidTx = 100 * MB_IN_BYTES;
+        final long UID_END = System.currentTimeMillis();
+        final long UID_START = UID_END - mSettings.getUidMaxHistory();
+        final long UID_RX_BYTES = 500 * MB_IN_BYTES;
+        final long UID_RX_PACKETS = UID_RX_BYTES / 1024;
+        final long UID_TX_BYTES = 100 * MB_IN_BYTES;
+        final long UID_TX_PACKETS = UID_TX_BYTES / 1024;
+        final long UID_OPERATIONS = UID_RX_BYTES / 2048;
 
         final List<ApplicationInfo> installedApps = mContext
                 .getPackageManager().getInstalledApplications(0);
@@ -1068,13 +1075,13 @@
         mNetworkStats.clear();
         mUidStats.clear();
         for (NetworkIdentitySet ident : mActiveIfaces.values()) {
-            findOrCreateNetworkStatsLocked(ident).generateRandom(
-                    networkStart, networkEnd, networkRx, networkTx);
+            findOrCreateNetworkStatsLocked(ident).generateRandom(NET_START, NET_END, NET_RX_BYTES,
+                    NET_RX_PACKETS, NET_TX_BYTES, NET_TX_PACKETS, 0L);
 
             for (ApplicationInfo info : installedApps) {
                 final int uid = info.uid;
-                findOrCreateUidStatsLocked(ident, uid, TAG_NONE).generateRandom(
-                        uidStart, uidEnd, uidRx, uidTx);
+                findOrCreateUidStatsLocked(ident, uid, TAG_NONE).generateRandom(UID_START, UID_END,
+                        UID_RX_BYTES, UID_RX_PACKETS, UID_TX_BYTES, UID_TX_PACKETS, UID_OPERATIONS);
             }
         }
     }
@@ -1083,9 +1090,13 @@
      * Return the delta between two {@link NetworkStats} snapshots, where {@code
      * before} can be {@code null}.
      */
-    private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) {
+    private static NetworkStats computeStatsDelta(
+            NetworkStats before, NetworkStats current, boolean collectStale) {
         if (before != null) {
             return current.subtractClamped(before);
+        } else if (collectStale) {
+            // caller is okay collecting stale stats for first call.
+            return current;
         } else {
             // this is first snapshot; to prevent from double-counting we only
             // observe traffic occuring between known snapshots.
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index cf69fd5..8eb9cc3 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -25,6 +25,7 @@
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStatsHistory.FIELD_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.UID_REMOVED;
@@ -302,7 +303,7 @@
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify service recorded history
-        history = mService.getHistoryForNetwork(sTemplateWifi);
+        history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
         assertEquals(HOUR_IN_MILLIS, history.getBucketDuration());
         assertEquals(2, history.size());
@@ -319,7 +320,7 @@
         mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
 
         // verify identical stats, but spread across 4 buckets now
-        history = mService.getHistoryForNetwork(sTemplateWifi);
+        history = mService.getHistoryForNetwork(sTemplateWifi, FIELD_ALL);
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, 512L, 4L, 512L, 4L, 0);
         assertEquals(30 * MINUTE_IN_MILLIS, history.getBucketDuration());
         assertEquals(4, history.size());
@@ -631,14 +632,15 @@
 
     private void assertNetworkTotal(NetworkTemplate template, long rxBytes, long rxPackets,
             long txBytes, long txPackets, int operations) {
-        final NetworkStatsHistory history = mService.getHistoryForNetwork(template);
+        final NetworkStatsHistory history = mService.getHistoryForNetwork(template, FIELD_ALL);
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
                 txPackets, operations);
     }
 
     private void assertUidTotal(NetworkTemplate template, int uid, long rxBytes, long rxPackets,
             long txBytes, long txPackets, int operations) {
-        final NetworkStatsHistory history = mService.getHistoryForUid(template, uid, TAG_NONE);
+        final NetworkStatsHistory history = mService.getHistoryForUid(
+                template, uid, TAG_NONE, FIELD_ALL);
         assertValues(history, Long.MIN_VALUE, Long.MAX_VALUE, rxBytes, rxPackets, txBytes,
                 txPackets, operations);
     }