1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
20 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
21 import static android.net.NetworkStats.IFACE_ALL;
22 import static android.net.NetworkStats.METERED_NO;
23 import static android.net.NetworkStats.ROAMING_NO;
24 import static android.net.NetworkStats.SET_DEFAULT;
25 import static android.net.NetworkStats.TAG_NONE;
26 import static android.net.NetworkStats.UID_ALL;
27 import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray;
28 import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray;
29 import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
30 import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
31 import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
32 import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
33 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
34 
35 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
36 
37 import android.annotation.NonNull;
38 import android.annotation.Nullable;
39 import android.annotation.SystemApi;
40 import android.compat.annotation.UnsupportedAppUsage;
41 import android.os.Build;
42 import android.os.Parcel;
43 import android.os.Parcelable;
44 import android.service.NetworkStatsHistoryBucketProto;
45 import android.service.NetworkStatsHistoryProto;
46 import android.util.IndentingPrintWriter;
47 import android.util.proto.ProtoOutputStream;
48 
49 import com.android.net.module.util.CollectionUtils;
50 import com.android.net.module.util.NetworkStatsUtils;
51 
52 import libcore.util.EmptyArray;
53 
54 import java.io.CharArrayWriter;
55 import java.io.DataInput;
56 import java.io.DataOutput;
57 import java.io.IOException;
58 import java.io.PrintWriter;
59 import java.net.ProtocolException;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.List;
63 import java.util.Random;
64 import java.util.TreeMap;
65 
66 /**
67  * Collection of historical network statistics, recorded into equally-sized
68  * "buckets" in time. Internally it stores data in {@code long} series for more
69  * efficient persistence.
70  * <p>
71  * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for
72  * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is
73  * sorted at all times.
74  *
75  * @hide
76  */
77 @SystemApi(client = MODULE_LIBRARIES)
78 public final class NetworkStatsHistory implements Parcelable {
79     private static final int VERSION_INIT = 1;
80     private static final int VERSION_ADD_PACKETS = 2;
81     private static final int VERSION_ADD_ACTIVE = 3;
82 
83     /** @hide */
84     public static final int FIELD_ACTIVE_TIME = 0x01;
85     /** @hide */
86     public static final int FIELD_RX_BYTES = 0x02;
87     /** @hide */
88     public static final int FIELD_RX_PACKETS = 0x04;
89     /** @hide */
90     public static final int FIELD_TX_BYTES = 0x08;
91     /** @hide */
92     public static final int FIELD_TX_PACKETS = 0x10;
93     /** @hide */
94     public static final int FIELD_OPERATIONS = 0x20;
95     /** @hide */
96     public static final int FIELD_ALL = 0xFFFFFFFF;
97 
98     private long bucketDuration;
99     private int bucketCount;
100     private long[] bucketStart;
101     private long[] activeTime;
102     private long[] rxBytes;
103     private long[] rxPackets;
104     private long[] txBytes;
105     private long[] txPackets;
106     private long[] operations;
107     private long totalBytes;
108 
109     /** @hide */
NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime, long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets, long[] operations, int bucketCount, long totalBytes)110     public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime,
111             long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets,
112             long[] operations, int bucketCount, long totalBytes) {
113         this.bucketDuration = bucketDuration;
114         this.bucketStart = bucketStart;
115         this.activeTime = activeTime;
116         this.rxBytes = rxBytes;
117         this.rxPackets = rxPackets;
118         this.txBytes = txBytes;
119         this.txPackets = txPackets;
120         this.operations = operations;
121         this.bucketCount = bucketCount;
122         this.totalBytes = totalBytes;
123     }
124 
125     /**
126      * An instance to represent a single record in a {@link NetworkStatsHistory} object.
127      */
128     public static final class Entry {
129         /** @hide */
130         public static final long UNKNOWN = -1;
131 
132         /** @hide */
133         // TODO: Migrate all callers to get duration from the history object and remove this field.
134         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
135         public long bucketDuration;
136         /** @hide */
137         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
138         public long bucketStart;
139         /** @hide */
140         public long activeTime;
141         /** @hide */
142         @UnsupportedAppUsage
143         public long rxBytes;
144         /** @hide */
145         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
146         public long rxPackets;
147         /** @hide */
148         @UnsupportedAppUsage
149         public long txBytes;
150         /** @hide */
151         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
152         public long txPackets;
153         /** @hide */
154         public long operations;
155         /** @hide */
Entry()156         Entry() {}
157 
158         /**
159          * Construct a {@link Entry} instance to represent a single record in a
160          * {@link NetworkStatsHistory} object.
161          *
162          * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the
163          *                    Unix epoch, see {@link java.lang.System#currentTimeMillis}.
164          * @param activeTime Active time for this {@link Entry}, in milliseconds.
165          * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should
166          *                represent the contents of IP packets, including IP headers.
167          * @param rxPackets Number of packets received for this {@link Entry}. Statistics should
168          *                  represent the contents of IP packets, including IP headers.
169          * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should
170          *                represent the contents of IP packets, including IP headers.
171          * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should
172          *                  represent the contents of IP packets, including IP headers.
173          * @param operations count of network operations performed for this {@link Entry}. This can
174          *                   be used to derive bytes-per-operation.
175          */
Entry(long bucketStart, long activeTime, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)176         public Entry(long bucketStart, long activeTime, long rxBytes,
177                 long rxPackets, long txBytes, long txPackets, long operations) {
178             this.bucketStart = bucketStart;
179             this.activeTime = activeTime;
180             this.rxBytes = rxBytes;
181             this.rxPackets = rxPackets;
182             this.txBytes = txBytes;
183             this.txPackets = txPackets;
184             this.operations = operations;
185         }
186 
187         /**
188          * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch.
189          */
getBucketStart()190         public long getBucketStart() {
191             return bucketStart;
192         }
193 
194         /**
195          * Get active time of the bucket's time interval, in milliseconds.
196          */
getActiveTime()197         public long getActiveTime() {
198             return activeTime;
199         }
200 
201         /** Get number of bytes received for this {@link Entry}. */
getRxBytes()202         public long getRxBytes() {
203             return rxBytes;
204         }
205 
206         /** Get number of packets received for this {@link Entry}. */
getRxPackets()207         public long getRxPackets() {
208             return rxPackets;
209         }
210 
211         /** Get number of bytes transmitted for this {@link Entry}. */
getTxBytes()212         public long getTxBytes() {
213             return txBytes;
214         }
215 
216         /** Get number of packets transmitted for this {@link Entry}. */
getTxPackets()217         public long getTxPackets() {
218             return txPackets;
219         }
220 
221         /** Get count of network operations performed for this {@link Entry}. */
getOperations()222         public long getOperations() {
223             return operations;
224         }
225 
226         @Override
equals(Object o)227         public boolean equals(Object o) {
228             if (this == o) return true;
229             if (o.getClass() != getClass()) return false;
230             Entry entry = (Entry) o;
231             return bucketStart == entry.bucketStart
232                     && activeTime == entry.activeTime && rxBytes == entry.rxBytes
233                     && rxPackets == entry.rxPackets && txBytes == entry.txBytes
234                     && txPackets == entry.txPackets && operations == entry.operations;
235         }
236 
237         @Override
hashCode()238         public int hashCode() {
239             return (int) (bucketStart * 2
240                     + activeTime * 3
241                     + rxBytes * 5
242                     + rxPackets * 7
243                     + txBytes * 11
244                     + txPackets * 13
245                     + operations * 17);
246         }
247 
248         @Override
toString()249         public String toString() {
250             return "Entry{"
251                     + "bucketStart=" + bucketStart
252                     + ", activeTime=" + activeTime
253                     + ", rxBytes=" + rxBytes
254                     + ", rxPackets=" + rxPackets
255                     + ", txBytes=" + txBytes
256                     + ", txPackets=" + txPackets
257                     + ", operations=" + operations
258                     + "}";
259         }
260 
261         /**
262          * Add the given {@link Entry} with this instance and return a new {@link Entry}
263          * instance as the result.
264          *
265          * @hide
266          */
267         @NonNull
plus(@onNull Entry another, long bucketDuration)268         public Entry plus(@NonNull Entry another, long bucketDuration) {
269             if (this.bucketStart != another.bucketStart) {
270                 throw new IllegalArgumentException("bucketStart " + this.bucketStart
271                         + " is not equal to " + another.bucketStart);
272             }
273             return new Entry(this.bucketStart,
274                     // Active time should not go over bucket duration.
275                     Math.min(this.activeTime + another.activeTime, bucketDuration),
276                     this.rxBytes + another.rxBytes,
277                     this.rxPackets + another.rxPackets,
278                     this.txBytes + another.txBytes,
279                     this.txPackets + another.txPackets,
280                     this.operations + another.operations);
281         }
282     }
283 
284     /** @hide */
285     @UnsupportedAppUsage
NetworkStatsHistory(long bucketDuration)286     public NetworkStatsHistory(long bucketDuration) {
287         this(bucketDuration, 10, FIELD_ALL);
288     }
289 
290     /** @hide */
NetworkStatsHistory(long bucketDuration, int initialSize)291     public NetworkStatsHistory(long bucketDuration, int initialSize) {
292         this(bucketDuration, initialSize, FIELD_ALL);
293     }
294 
295     /** @hide */
NetworkStatsHistory(long bucketDuration, int initialSize, int fields)296     public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) {
297         this.bucketDuration = bucketDuration;
298         bucketStart = new long[initialSize];
299         if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize];
300         if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize];
301         if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize];
302         if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize];
303         if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
304         if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
305         bucketCount = 0;
306         totalBytes = 0;
307     }
308 
309     /** @hide */
NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration)310     public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
311         this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
312         recordEntireHistory(existing);
313     }
314 
315     /** @hide */
316     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
NetworkStatsHistory(Parcel in)317     public NetworkStatsHistory(Parcel in) {
318         bucketDuration = in.readLong();
319         bucketStart = readLongArray(in);
320         activeTime = readLongArray(in);
321         rxBytes = readLongArray(in);
322         rxPackets = readLongArray(in);
323         txBytes = readLongArray(in);
324         txPackets = readLongArray(in);
325         operations = readLongArray(in);
326         bucketCount = bucketStart.length;
327         totalBytes = in.readLong();
328     }
329 
330     @Override
writeToParcel(@onNull Parcel out, int flags)331     public void writeToParcel(@NonNull Parcel out, int flags) {
332         out.writeLong(bucketDuration);
333         writeLongArray(out, bucketStart, bucketCount);
334         writeLongArray(out, activeTime, bucketCount);
335         writeLongArray(out, rxBytes, bucketCount);
336         writeLongArray(out, rxPackets, bucketCount);
337         writeLongArray(out, txBytes, bucketCount);
338         writeLongArray(out, txPackets, bucketCount);
339         writeLongArray(out, operations, bucketCount);
340         out.writeLong(totalBytes);
341     }
342 
343     /** @hide */
NetworkStatsHistory(DataInput in)344     public NetworkStatsHistory(DataInput in) throws IOException {
345         final int version = in.readInt();
346         switch (version) {
347             case VERSION_INIT: {
348                 bucketDuration = in.readLong();
349                 bucketStart = readFullLongArray(in);
350                 rxBytes = readFullLongArray(in);
351                 rxPackets = new long[bucketStart.length];
352                 txBytes = readFullLongArray(in);
353                 txPackets = new long[bucketStart.length];
354                 operations = new long[bucketStart.length];
355                 bucketCount = bucketStart.length;
356                 totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
357                 break;
358             }
359             case VERSION_ADD_PACKETS:
360             case VERSION_ADD_ACTIVE: {
361                 bucketDuration = in.readLong();
362                 bucketStart = readVarLongArray(in);
363                 activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in)
364                         : new long[bucketStart.length];
365                 rxBytes = readVarLongArray(in);
366                 rxPackets = readVarLongArray(in);
367                 txBytes = readVarLongArray(in);
368                 txPackets = readVarLongArray(in);
369                 operations = readVarLongArray(in);
370                 bucketCount = bucketStart.length;
371                 totalBytes = CollectionUtils.total(rxBytes) + CollectionUtils.total(txBytes);
372                 break;
373             }
374             default: {
375                 throw new ProtocolException("unexpected version: " + version);
376             }
377         }
378 
379         if (bucketStart.length != bucketCount || rxBytes.length != bucketCount
380                 || rxPackets.length != bucketCount || txBytes.length != bucketCount
381                 || txPackets.length != bucketCount || operations.length != bucketCount) {
382             throw new ProtocolException("Mismatched history lengths");
383         }
384     }
385 
386     /** @hide */
writeToStream(DataOutput out)387     public void writeToStream(DataOutput out) throws IOException {
388         out.writeInt(VERSION_ADD_ACTIVE);
389         out.writeLong(bucketDuration);
390         writeVarLongArray(out, bucketStart, bucketCount);
391         writeVarLongArray(out, activeTime, bucketCount);
392         writeVarLongArray(out, rxBytes, bucketCount);
393         writeVarLongArray(out, rxPackets, bucketCount);
394         writeVarLongArray(out, txBytes, bucketCount);
395         writeVarLongArray(out, txPackets, bucketCount);
396         writeVarLongArray(out, operations, bucketCount);
397     }
398 
399     @Override
describeContents()400     public int describeContents() {
401         return 0;
402     }
403 
404     /** @hide */
405     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
size()406     public int size() {
407         return bucketCount;
408     }
409 
410     /** @hide */
getBucketDuration()411     public long getBucketDuration() {
412         return bucketDuration;
413     }
414 
415     /** @hide */
416     @UnsupportedAppUsage
getStart()417     public long getStart() {
418         if (bucketCount > 0) {
419             return bucketStart[0];
420         } else {
421             return Long.MAX_VALUE;
422         }
423     }
424 
425     /** @hide */
426     @UnsupportedAppUsage
getEnd()427     public long getEnd() {
428         if (bucketCount > 0) {
429             return bucketStart[bucketCount - 1] + bucketDuration;
430         } else {
431             return Long.MIN_VALUE;
432         }
433     }
434 
435     /**
436      * Return total bytes represented by this history.
437      * @hide
438      */
getTotalBytes()439     public long getTotalBytes() {
440         return totalBytes;
441     }
442 
443     /**
444      * Return index of bucket that contains or is immediately before the
445      * requested time.
446      * @hide
447      */
448     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIndexBefore(long time)449     public int getIndexBefore(long time) {
450         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
451         if (index < 0) {
452             index = (~index) - 1;
453         } else {
454             index -= 1;
455         }
456         return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
457     }
458 
459     /**
460      * Return index of bucket that contains or is immediately after the
461      * requested time.
462      * @hide
463      */
getIndexAfter(long time)464     public int getIndexAfter(long time) {
465         int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time);
466         if (index < 0) {
467             index = ~index;
468         } else {
469             index += 1;
470         }
471         return NetworkStatsUtils.constrain(index, 0, bucketCount - 1);
472     }
473 
474     /**
475      * Return specific stats entry.
476      * @hide
477      */
478     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getValues(int i, Entry recycle)479     public Entry getValues(int i, Entry recycle) {
480         final Entry entry = recycle != null ? recycle : new Entry();
481         entry.bucketStart = bucketStart[i];
482         entry.bucketDuration = bucketDuration;
483         entry.activeTime = getLong(activeTime, i, UNKNOWN);
484         entry.rxBytes = getLong(rxBytes, i, UNKNOWN);
485         entry.rxPackets = getLong(rxPackets, i, UNKNOWN);
486         entry.txBytes = getLong(txBytes, i, UNKNOWN);
487         entry.txPackets = getLong(txPackets, i, UNKNOWN);
488         entry.operations = getLong(operations, i, UNKNOWN);
489         return entry;
490     }
491 
492     /**
493      * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance.
494      *
495      * @return
496      */
497     @NonNull
getEntries()498     public List<Entry> getEntries() {
499         // TODO: Return a wrapper that uses this list instead, to prevent the returned result
500         //  from being changed.
501         final ArrayList<Entry> ret = new ArrayList<>(size());
502         for (int i = 0; i < size(); i++) {
503             ret.add(getValues(i, null /* recycle */));
504         }
505         return ret;
506     }
507 
508     /** @hide */
setValues(int i, Entry entry)509     public void setValues(int i, Entry entry) {
510         // Unwind old values
511         if (rxBytes != null) totalBytes -= rxBytes[i];
512         if (txBytes != null) totalBytes -= txBytes[i];
513 
514         bucketStart[i] = entry.bucketStart;
515         setLong(activeTime, i, entry.activeTime);
516         setLong(rxBytes, i, entry.rxBytes);
517         setLong(rxPackets, i, entry.rxPackets);
518         setLong(txBytes, i, entry.txBytes);
519         setLong(txPackets, i, entry.txPackets);
520         setLong(operations, i, entry.operations);
521 
522         // Apply new values
523         if (rxBytes != null) totalBytes += rxBytes[i];
524         if (txBytes != null) totalBytes += txBytes[i];
525     }
526 
527     /**
528      * Record that data traffic occurred in the given time range. Will
529      * distribute across internal buckets, creating new buckets as needed.
530      * @hide
531      */
532     @Deprecated
recordData(long start, long end, long rxBytes, long txBytes)533     public void recordData(long start, long end, long rxBytes, long txBytes) {
534         recordData(start, end, new NetworkStats.Entry(
535                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
536                 DEFAULT_NETWORK_NO, rxBytes, 0L, txBytes, 0L, 0L));
537     }
538 
539     /**
540      * Record that data traffic occurred in the given time range. Will
541      * distribute across internal buckets, creating new buckets as needed.
542      * @hide
543      */
recordData(long start, long end, NetworkStats.Entry entry)544     public void recordData(long start, long end, NetworkStats.Entry entry) {
545         long rxBytes = entry.rxBytes;
546         long rxPackets = entry.rxPackets;
547         long txBytes = entry.txBytes;
548         long txPackets = entry.txPackets;
549         long operations = entry.operations;
550 
551         if (entry.isNegative()) {
552             throw new IllegalArgumentException("tried recording negative data");
553         }
554         if (entry.isEmpty()) {
555             return;
556         }
557 
558         // create any buckets needed by this range
559         ensureBuckets(start, end);
560         // Return fast if there is still no entry. This would typically happen when the start,
561         // end or duration are not valid values, e.g. start > end, negative duration value, etc.
562         if (bucketCount == 0) return;
563 
564         // distribute data usage into buckets
565         long duration = end - start;
566         final int startIndex = getIndexAfter(end);
567         for (int i = startIndex; i >= 0; i--) {
568             final long curStart = bucketStart[i];
569             final long curEnd = curStart + bucketDuration;
570 
571             // bucket is older than record; we're finished
572             if (curEnd < start) break;
573             // bucket is newer than record; keep looking
574             if (curStart > end) continue;
575 
576             final long overlap = Math.min(curEnd, end) - Math.max(curStart, start);
577             if (overlap <= 0) continue;
578 
579             // integer math each time is faster than floating point
580             final long fracRxBytes = multiplySafeByRational(rxBytes, overlap, duration);
581             final long fracRxPackets = multiplySafeByRational(rxPackets, overlap, duration);
582             final long fracTxBytes = multiplySafeByRational(txBytes, overlap, duration);
583             final long fracTxPackets = multiplySafeByRational(txPackets, overlap, duration);
584             final long fracOperations = multiplySafeByRational(operations, overlap, duration);
585 
586 
587             addLong(activeTime, i, overlap);
588             addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
589             addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
590             addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
591             addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
592             addLong(this.operations, i, fracOperations); operations -= fracOperations;
593 
594             duration -= overlap;
595         }
596 
597         totalBytes += entry.rxBytes + entry.txBytes;
598     }
599 
600     /**
601      * Record an entire {@link NetworkStatsHistory} into this history. Usually
602      * for combining together stats for external reporting.
603      * @hide
604      */
605     @UnsupportedAppUsage
recordEntireHistory(NetworkStatsHistory input)606     public void recordEntireHistory(NetworkStatsHistory input) {
607         recordHistory(input, Long.MIN_VALUE, Long.MAX_VALUE);
608     }
609 
610     /**
611      * Record given {@link NetworkStatsHistory} into this history, copying only
612      * buckets that atomically occur in the inclusive time range. Doesn't
613      * interpolate across partial buckets.
614      * @hide
615      */
recordHistory(NetworkStatsHistory input, long start, long end)616     public void recordHistory(NetworkStatsHistory input, long start, long end) {
617         final NetworkStats.Entry entry = new NetworkStats.Entry(
618                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
619                 DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
620         for (int i = 0; i < input.bucketCount; i++) {
621             final long bucketStart = input.bucketStart[i];
622             final long bucketEnd = bucketStart + input.bucketDuration;
623 
624             // skip when bucket is outside requested range
625             if (bucketStart < start || bucketEnd > end) continue;
626 
627             entry.rxBytes = getLong(input.rxBytes, i, 0L);
628             entry.rxPackets = getLong(input.rxPackets, i, 0L);
629             entry.txBytes = getLong(input.txBytes, i, 0L);
630             entry.txPackets = getLong(input.txPackets, i, 0L);
631             entry.operations = getLong(input.operations, i, 0L);
632 
633             recordData(bucketStart, bucketEnd, entry);
634         }
635     }
636 
637     /**
638      * Ensure that buckets exist for given time range, creating as needed.
639      */
ensureBuckets(long start, long end)640     private void ensureBuckets(long start, long end) {
641         // normalize incoming range to bucket boundaries
642         start -= start % bucketDuration;
643         end += (bucketDuration - (end % bucketDuration)) % bucketDuration;
644 
645         for (long now = start; now < end; now += bucketDuration) {
646             // try finding existing bucket
647             final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now);
648             if (index < 0) {
649                 // bucket missing, create and insert
650                 insertBucket(~index, now);
651             }
652         }
653     }
654 
655     /**
656      * Insert new bucket at requested index and starting time.
657      */
insertBucket(int index, long start)658     private void insertBucket(int index, long start) {
659         // create more buckets when needed
660         if (bucketCount >= bucketStart.length) {
661             final int newLength = Math.max(bucketStart.length, 10) * 3 / 2;
662             bucketStart = Arrays.copyOf(bucketStart, newLength);
663             if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength);
664             if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength);
665             if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength);
666             if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength);
667             if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength);
668             if (operations != null) operations = Arrays.copyOf(operations, newLength);
669         }
670 
671         // create gap when inserting bucket in middle
672         if (index < bucketCount) {
673             final int dstPos = index + 1;
674             final int length = bucketCount - index;
675 
676             System.arraycopy(bucketStart, index, bucketStart, dstPos, length);
677             if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length);
678             if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length);
679             if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length);
680             if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length);
681             if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length);
682             if (operations != null) System.arraycopy(operations, index, operations, dstPos, length);
683         }
684 
685         bucketStart[index] = start;
686         setLong(activeTime, index, 0L);
687         setLong(rxBytes, index, 0L);
688         setLong(rxPackets, index, 0L);
689         setLong(txBytes, index, 0L);
690         setLong(txPackets, index, 0L);
691         setLong(operations, index, 0L);
692         bucketCount++;
693     }
694 
695     /**
696      * Clear all data stored in this object.
697      * @hide
698      */
clear()699     public void clear() {
700         bucketStart = EmptyArray.LONG;
701         if (activeTime != null) activeTime = EmptyArray.LONG;
702         if (rxBytes != null) rxBytes = EmptyArray.LONG;
703         if (rxPackets != null) rxPackets = EmptyArray.LONG;
704         if (txBytes != null) txBytes = EmptyArray.LONG;
705         if (txPackets != null) txPackets = EmptyArray.LONG;
706         if (operations != null) operations = EmptyArray.LONG;
707         bucketCount = 0;
708         totalBytes = 0;
709     }
710 
711     /**
712      * Remove buckets that start older than requested cutoff.
713      *
714      * This method will remove any bucket that contains any data older than the requested
715      * cutoff, even if that same bucket includes some data from after the cutoff.
716      *
717      * @hide
718      */
removeBucketsStartingBefore(final long cutoff)719     public void removeBucketsStartingBefore(final long cutoff) {
720         // TODO: Consider use getIndexBefore.
721         int i;
722         for (i = 0; i < bucketCount; i++) {
723             final long curStart = bucketStart[i];
724 
725             // This bucket starts after or at the cutoff, so it should be kept.
726             if (curStart >= cutoff) break;
727         }
728 
729         if (i > 0) {
730             final int length = bucketStart.length;
731             bucketStart = Arrays.copyOfRange(bucketStart, i, length);
732             if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length);
733             if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length);
734             if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length);
735             if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length);
736             if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
737             if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
738             bucketCount -= i;
739 
740             totalBytes = 0;
741             if (rxBytes != null) totalBytes += CollectionUtils.total(rxBytes);
742             if (txBytes != null) totalBytes += CollectionUtils.total(txBytes);
743         }
744     }
745 
746     /**
747      * Return interpolated data usage across the requested range. Interpolates
748      * across buckets, so values may be rounded slightly.
749      *
750      * <p>If the active bucket is not completed yet, it returns the proportional value of it
751      * based on its duration and the {@code end} param.
752      *
753      * @param start - start of the range, timestamp in milliseconds since the epoch.
754      * @param end - end of the range, timestamp in milliseconds since the epoch.
755      * @param recycle - entry instance for performance, could be null.
756      * @hide
757      */
758     @UnsupportedAppUsage
getValues(long start, long end, Entry recycle)759     public Entry getValues(long start, long end, Entry recycle) {
760         return getValues(start, end, Long.MAX_VALUE, recycle);
761     }
762 
763     /**
764      * Return interpolated data usage across the requested range. Interpolates
765      * across buckets, so values may be rounded slightly.
766      *
767      * @param start - start of the range, timestamp in milliseconds since the epoch.
768      * @param end - end of the range, timestamp in milliseconds since the epoch.
769      * @param now - current timestamp in milliseconds since the epoch (wall clock).
770      * @param recycle - entry instance for performance, could be null.
771      * @hide
772      */
773     @UnsupportedAppUsage
getValues(long start, long end, long now, Entry recycle)774     public Entry getValues(long start, long end, long now, Entry recycle) {
775         final Entry entry = recycle != null ? recycle : new Entry();
776         entry.bucketDuration = end - start;
777         entry.bucketStart = start;
778         entry.activeTime = activeTime != null ? 0 : UNKNOWN;
779         entry.rxBytes = rxBytes != null ? 0 : UNKNOWN;
780         entry.rxPackets = rxPackets != null ? 0 : UNKNOWN;
781         entry.txBytes = txBytes != null ? 0 : UNKNOWN;
782         entry.txPackets = txPackets != null ? 0 : UNKNOWN;
783         entry.operations = operations != null ? 0 : UNKNOWN;
784 
785         // Return fast if there is no entry.
786         if (bucketCount == 0) return entry;
787 
788         final int startIndex = getIndexAfter(end);
789         for (int i = startIndex; i >= 0; i--) {
790             final long curStart = bucketStart[i];
791             long curEnd = curStart + bucketDuration;
792 
793             // bucket is older than request; we're finished
794             if (curEnd <= start) break;
795             // bucket is newer than request; keep looking
796             if (curStart >= end) continue;
797 
798             // the active bucket is shorter then a normal completed bucket
799             if (curEnd > now) curEnd = now;
800             // usually this is simply bucketDuration
801             final long bucketSpan = curEnd - curStart;
802             // prevent division by zero
803             if (bucketSpan <= 0) continue;
804 
805             final long overlapEnd = curEnd < end ? curEnd : end;
806             final long overlapStart = curStart > start ? curStart : start;
807             final long overlap = overlapEnd - overlapStart;
808             if (overlap <= 0) continue;
809 
810             // integer math each time is faster than floating point
811             if (activeTime != null) {
812                 entry.activeTime += multiplySafeByRational(activeTime[i], overlap, bucketSpan);
813             }
814             if (rxBytes != null) {
815                 entry.rxBytes += multiplySafeByRational(rxBytes[i], overlap, bucketSpan);
816             }
817             if (rxPackets != null) {
818                 entry.rxPackets += multiplySafeByRational(rxPackets[i], overlap, bucketSpan);
819             }
820             if (txBytes != null) {
821                 entry.txBytes += multiplySafeByRational(txBytes[i], overlap, bucketSpan);
822             }
823             if (txPackets != null) {
824                 entry.txPackets += multiplySafeByRational(txPackets[i], overlap, bucketSpan);
825             }
826             if (operations != null) {
827                 entry.operations += multiplySafeByRational(operations[i], overlap, bucketSpan);
828             }
829         }
830         return entry;
831     }
832 
833     /**
834      * @deprecated only for temporary testing
835      * @hide
836      */
837     @Deprecated
generateRandom(long start, long end, long bytes)838     public void generateRandom(long start, long end, long bytes) {
839         final Random r = new Random();
840 
841         final float fractionRx = r.nextFloat();
842         final long rxBytes = (long) (bytes * fractionRx);
843         final long txBytes = (long) (bytes * (1 - fractionRx));
844 
845         final long rxPackets = rxBytes / 1024;
846         final long txPackets = txBytes / 1024;
847         final long operations = rxBytes / 2048;
848 
849         generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
850     }
851 
852     /**
853      * @deprecated only for temporary testing
854      * @hide
855      */
856     @Deprecated
generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations, Random r)857     public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
858             long txPackets, long operations, Random r) {
859         ensureBuckets(start, end);
860 
861         final NetworkStats.Entry entry = new NetworkStats.Entry(
862                 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
863                 DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
864         while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
865                 || operations > 32) {
866             final long curStart = randomLong(r, start, end);
867             final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
868 
869             entry.rxBytes = randomLong(r, 0, rxBytes);
870             entry.rxPackets = randomLong(r, 0, rxPackets);
871             entry.txBytes = randomLong(r, 0, txBytes);
872             entry.txPackets = randomLong(r, 0, txPackets);
873             entry.operations = randomLong(r, 0, operations);
874 
875             rxBytes -= entry.rxBytes;
876             rxPackets -= entry.rxPackets;
877             txBytes -= entry.txBytes;
878             txPackets -= entry.txPackets;
879             operations -= entry.operations;
880 
881             recordData(curStart, curEnd, entry);
882         }
883     }
884 
885     /** @hide */
randomLong(Random r, long start, long end)886     public static long randomLong(Random r, long start, long end) {
887         return (long) (start + (r.nextFloat() * (end - start)));
888     }
889 
890     /**
891      * Quickly determine if this history intersects with given window.
892      * @hide
893      */
intersects(long start, long end)894     public boolean intersects(long start, long end) {
895         final long dataStart = getStart();
896         final long dataEnd = getEnd();
897         if (start >= dataStart && start <= dataEnd) return true;
898         if (end >= dataStart && end <= dataEnd) return true;
899         if (dataStart >= start && dataStart <= end) return true;
900         if (dataEnd >= start && dataEnd <= end) return true;
901         return false;
902     }
903 
904     /** @hide */
dump(IndentingPrintWriter pw, boolean fullHistory)905     public void dump(IndentingPrintWriter pw, boolean fullHistory) {
906         pw.print("NetworkStatsHistory: bucketDuration=");
907         pw.println(bucketDuration / SECOND_IN_MILLIS);
908         pw.increaseIndent();
909 
910         final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
911         if (start > 0) {
912             pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
913         }
914 
915         for (int i = start; i < bucketCount; i++) {
916             pw.print("st="); pw.print(bucketStart[i] / SECOND_IN_MILLIS);
917             if (rxBytes != null) { pw.print(" rb="); pw.print(rxBytes[i]); }
918             if (rxPackets != null) { pw.print(" rp="); pw.print(rxPackets[i]); }
919             if (txBytes != null) { pw.print(" tb="); pw.print(txBytes[i]); }
920             if (txPackets != null) { pw.print(" tp="); pw.print(txPackets[i]); }
921             if (operations != null) { pw.print(" op="); pw.print(operations[i]); }
922             pw.println();
923         }
924 
925         pw.decreaseIndent();
926     }
927 
928     /** @hide */
dumpCheckin(PrintWriter pw)929     public void dumpCheckin(PrintWriter pw) {
930         pw.print("d,");
931         pw.print(bucketDuration / SECOND_IN_MILLIS);
932         pw.println();
933 
934         for (int i = 0; i < bucketCount; i++) {
935             pw.print("b,");
936             pw.print(bucketStart[i] / SECOND_IN_MILLIS); pw.print(',');
937             if (rxBytes != null) { pw.print(rxBytes[i]); } else { pw.print("*"); } pw.print(',');
938             if (rxPackets != null) { pw.print(rxPackets[i]); } else { pw.print("*"); } pw.print(',');
939             if (txBytes != null) { pw.print(txBytes[i]); } else { pw.print("*"); } pw.print(',');
940             if (txPackets != null) { pw.print(txPackets[i]); } else { pw.print("*"); } pw.print(',');
941             if (operations != null) { pw.print(operations[i]); } else { pw.print("*"); }
942             pw.println();
943         }
944     }
945 
946     /** @hide */
dumpDebug(ProtoOutputStream proto, long tag)947     public void dumpDebug(ProtoOutputStream proto, long tag) {
948         final long start = proto.start(tag);
949 
950         proto.write(NetworkStatsHistoryProto.BUCKET_DURATION_MS, bucketDuration);
951 
952         for (int i = 0; i < bucketCount; i++) {
953             final long startBucket = proto.start(NetworkStatsHistoryProto.BUCKETS);
954 
955             proto.write(NetworkStatsHistoryBucketProto.BUCKET_START_MS,
956                     bucketStart[i]);
957             dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_BYTES, rxBytes, i);
958             dumpDebug(proto, NetworkStatsHistoryBucketProto.RX_PACKETS, rxPackets, i);
959             dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_BYTES, txBytes, i);
960             dumpDebug(proto, NetworkStatsHistoryBucketProto.TX_PACKETS, txPackets, i);
961             dumpDebug(proto, NetworkStatsHistoryBucketProto.OPERATIONS, operations, i);
962 
963             proto.end(startBucket);
964         }
965 
966         proto.end(start);
967     }
968 
dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index)969     private static void dumpDebug(ProtoOutputStream proto, long tag, long[] array, int index) {
970         if (array != null) {
971             proto.write(tag, array[index]);
972         }
973     }
974 
975     @Override
toString()976     public String toString() {
977         final CharArrayWriter writer = new CharArrayWriter();
978         dump(new IndentingPrintWriter(writer, "  "), false);
979         return writer.toString();
980     }
981 
982     /**
983      * Same as "equals", but not actually called equals as this would affect public API behavior.
984      * @hide
985      */
986     @Nullable
isSameAs(NetworkStatsHistory other)987     public boolean isSameAs(NetworkStatsHistory other) {
988         return bucketCount == other.bucketCount
989                 && Arrays.equals(bucketStart, other.bucketStart)
990                 // Don't check activeTime since it can change on import due to the importer using
991                 // recordHistory. It's also not exposed by the APIs or present in dumpsys or
992                 // toString().
993                 && Arrays.equals(rxBytes, other.rxBytes)
994                 && Arrays.equals(rxPackets, other.rxPackets)
995                 && Arrays.equals(txBytes, other.txBytes)
996                 && Arrays.equals(txPackets, other.txPackets)
997                 && Arrays.equals(operations, other.operations)
998                 && totalBytes == other.totalBytes;
999     }
1000 
1001     @UnsupportedAppUsage
1002     public static final @android.annotation.NonNull Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
1003         @Override
1004         public NetworkStatsHistory createFromParcel(Parcel in) {
1005             return new NetworkStatsHistory(in);
1006         }
1007 
1008         @Override
1009         public NetworkStatsHistory[] newArray(int size) {
1010             return new NetworkStatsHistory[size];
1011         }
1012     };
1013 
getLong(long[] array, int i, long value)1014     private static long getLong(long[] array, int i, long value) {
1015         return array != null ? array[i] : value;
1016     }
1017 
setLong(long[] array, int i, long value)1018     private static void setLong(long[] array, int i, long value) {
1019         if (array != null) array[i] = value;
1020     }
1021 
addLong(long[] array, int i, long value)1022     private static void addLong(long[] array, int i, long value) {
1023         if (array != null) array[i] += value;
1024     }
1025 
1026     /** @hide */
estimateResizeBuckets(long newBucketDuration)1027     public int estimateResizeBuckets(long newBucketDuration) {
1028         return (int) (size() * getBucketDuration() / newBucketDuration);
1029     }
1030 
1031     /**
1032      * Utility methods for interacting with {@link DataInputStream} and
1033      * {@link DataOutputStream}, mostly dealing with writing partial arrays.
1034      * @hide
1035      */
1036     public static class DataStreamUtils {
1037         @Deprecated
readFullLongArray(DataInput in)1038         public static long[] readFullLongArray(DataInput in) throws IOException {
1039             final int size = in.readInt();
1040             if (size < 0) throw new ProtocolException("negative array size");
1041             final long[] values = new long[size];
1042             for (int i = 0; i < values.length; i++) {
1043                 values[i] = in.readLong();
1044             }
1045             return values;
1046         }
1047 
1048         /**
1049          * Read variable-length {@link Long} using protobuf-style approach.
1050          */
readVarLong(DataInput in)1051         public static long readVarLong(DataInput in) throws IOException {
1052             int shift = 0;
1053             long result = 0;
1054             while (shift < 64) {
1055                 byte b = in.readByte();
1056                 result |= (long) (b & 0x7F) << shift;
1057                 if ((b & 0x80) == 0)
1058                     return result;
1059                 shift += 7;
1060             }
1061             throw new ProtocolException("malformed long");
1062         }
1063 
1064         /**
1065          * Write variable-length {@link Long} using protobuf-style approach.
1066          */
writeVarLong(DataOutput out, long value)1067         public static void writeVarLong(DataOutput out, long value) throws IOException {
1068             while (true) {
1069                 if ((value & ~0x7FL) == 0) {
1070                     out.writeByte((int) value);
1071                     return;
1072                 } else {
1073                     out.writeByte(((int) value & 0x7F) | 0x80);
1074                     value >>>= 7;
1075                 }
1076             }
1077         }
1078 
readVarLongArray(DataInput in)1079         public static long[] readVarLongArray(DataInput in) throws IOException {
1080             final int size = in.readInt();
1081             if (size == -1) return null;
1082             if (size < 0) throw new ProtocolException("negative array size");
1083             final long[] values = new long[size];
1084             for (int i = 0; i < values.length; i++) {
1085                 values[i] = readVarLong(in);
1086             }
1087             return values;
1088         }
1089 
writeVarLongArray(DataOutput out, long[] values, int size)1090         public static void writeVarLongArray(DataOutput out, long[] values, int size)
1091                 throws IOException {
1092             if (values == null) {
1093                 out.writeInt(-1);
1094                 return;
1095             }
1096             if (size > values.length) {
1097                 throw new IllegalArgumentException("size larger than length");
1098             }
1099             out.writeInt(size);
1100             for (int i = 0; i < size; i++) {
1101                 writeVarLong(out, values[i]);
1102             }
1103         }
1104     }
1105 
1106     /**
1107      * Utility methods for interacting with {@link Parcel} structures, mostly
1108      * dealing with writing partial arrays.
1109      * @hide
1110      */
1111     public static class ParcelUtils {
readLongArray(Parcel in)1112         public static long[] readLongArray(Parcel in) {
1113             final int size = in.readInt();
1114             if (size == -1) return null;
1115             final long[] values = new long[size];
1116             for (int i = 0; i < values.length; i++) {
1117                 values[i] = in.readLong();
1118             }
1119             return values;
1120         }
1121 
writeLongArray(Parcel out, long[] values, int size)1122         public static void writeLongArray(Parcel out, long[] values, int size) {
1123             if (values == null) {
1124                 out.writeInt(-1);
1125                 return;
1126             }
1127             if (size > values.length) {
1128                 throw new IllegalArgumentException("size larger than length");
1129             }
1130             out.writeInt(size);
1131             for (int i = 0; i < size; i++) {
1132                 out.writeLong(values[i]);
1133             }
1134         }
1135     }
1136 
1137     /**
1138      * Builder class for {@link NetworkStatsHistory}.
1139      */
1140     public static final class Builder {
1141         private final TreeMap<Long, Entry> mEntries;
1142         private final long mBucketDuration;
1143 
1144         /**
1145          * Creates a new Builder with given bucket duration and initial capacity to construct
1146          * {@link NetworkStatsHistory} objects.
1147          *
1148          * @param bucketDuration Duration of the buckets of the object, in milliseconds.
1149          * @param initialCapacity Estimated number of records.
1150          */
Builder(long bucketDuration, int initialCapacity)1151         public Builder(long bucketDuration, int initialCapacity) {
1152             mBucketDuration = bucketDuration;
1153             // Create a collection that is always sorted and can deduplicate items by the timestamp.
1154             mEntries = new TreeMap<>();
1155         }
1156 
1157         /**
1158          * Add an {@link Entry} into the {@link NetworkStatsHistory} instance. If the timestamp
1159          * already exists, the given {@link Entry} will be combined into existing entry.
1160          *
1161          * @param entry The target {@link Entry} object.
1162          * @return The builder object.
1163          */
1164         @NonNull
addEntry(@onNull Entry entry)1165         public Builder addEntry(@NonNull Entry entry) {
1166             final Entry existing = mEntries.get(entry.bucketStart);
1167             if (existing != null) {
1168                 mEntries.put(entry.bucketStart, existing.plus(entry, mBucketDuration));
1169             } else {
1170                 mEntries.put(entry.bucketStart, entry);
1171             }
1172             return this;
1173         }
1174 
sum(@onNull long[] array)1175         private static long sum(@NonNull long[] array) {
1176             long sum = 0L;
1177             for (long entry : array) {
1178                 sum += entry;
1179             }
1180             return sum;
1181         }
1182 
1183         /**
1184          * Builds the instance of the {@link NetworkStatsHistory}.
1185          *
1186          * @return the built instance of {@link NetworkStatsHistory}.
1187          */
1188         @NonNull
build()1189         public NetworkStatsHistory build() {
1190             int size = mEntries.size();
1191             final long[] bucketStart = new long[size];
1192             final long[] activeTime = new long[size];
1193             final long[] rxBytes = new long[size];
1194             final long[] rxPackets = new long[size];
1195             final long[] txBytes = new long[size];
1196             final long[] txPackets = new long[size];
1197             final long[] operations = new long[size];
1198 
1199             int i = 0;
1200             for (Entry entry : mEntries.values()) {
1201                 bucketStart[i] = entry.bucketStart;
1202                 activeTime[i] = entry.activeTime;
1203                 rxBytes[i] = entry.rxBytes;
1204                 rxPackets[i] = entry.rxPackets;
1205                 txBytes[i] = entry.txBytes;
1206                 txPackets[i] = entry.txPackets;
1207                 operations[i] = entry.operations;
1208                 i++;
1209             }
1210 
1211             return new NetworkStatsHistory(mBucketDuration, bucketStart, activeTime,
1212                     rxBytes, rxPackets, txBytes, txPackets, operations,
1213                     size, sum(rxBytes) + sum(txBytes));
1214         }
1215     }
1216 }
1217