• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.os.Parcel;
20 import android.os.Parcelable;
21 import android.os.SystemClock;
22 import android.util.SparseBooleanArray;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.internal.util.ArrayUtils;
26 import com.android.internal.util.Objects;
27 
28 import java.io.CharArrayWriter;
29 import java.io.PrintWriter;
30 import java.util.Arrays;
31 import java.util.HashSet;
32 
33 /**
34  * Collection of active network statistics. Can contain summary details across
35  * all interfaces, or details with per-UID granularity. Internally stores data
36  * as a large table, closely matching {@code /proc/} data format. This structure
37  * optimizes for rapid in-memory comparison, but consider using
38  * {@link NetworkStatsHistory} when persisting.
39  *
40  * @hide
41  */
42 public class NetworkStats implements Parcelable {
43     /** {@link #iface} value when interface details unavailable. */
44     public static final String IFACE_ALL = null;
45     /** {@link #uid} value when UID details unavailable. */
46     public static final int UID_ALL = -1;
47     /** {@link #set} value when all sets combined. */
48     public static final int SET_ALL = -1;
49     /** {@link #set} value where background data is accounted. */
50     public static final int SET_DEFAULT = 0;
51     /** {@link #set} value where foreground data is accounted. */
52     public static final int SET_FOREGROUND = 1;
53     /** {@link #tag} value for total data across all tags. */
54     public static final int TAG_NONE = 0;
55 
56     // TODO: move fields to "mVariable" notation
57 
58     /**
59      * {@link SystemClock#elapsedRealtime()} timestamp when this data was
60      * generated.
61      */
62     private final long elapsedRealtime;
63     private int size;
64     private String[] iface;
65     private int[] uid;
66     private int[] set;
67     private int[] tag;
68     private long[] rxBytes;
69     private long[] rxPackets;
70     private long[] txBytes;
71     private long[] txPackets;
72     private long[] operations;
73 
74     public static class Entry {
75         public String iface;
76         public int uid;
77         public int set;
78         public int tag;
79         public long rxBytes;
80         public long rxPackets;
81         public long txBytes;
82         public long txPackets;
83         public long operations;
84 
Entry()85         public Entry() {
86             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
87         }
88 
Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)89         public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
90             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
91                     operations);
92         }
93 
Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)94         public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
95                 long txBytes, long txPackets, long operations) {
96             this.iface = iface;
97             this.uid = uid;
98             this.set = set;
99             this.tag = tag;
100             this.rxBytes = rxBytes;
101             this.rxPackets = rxPackets;
102             this.txBytes = txBytes;
103             this.txPackets = txPackets;
104             this.operations = operations;
105         }
106 
isNegative()107         public boolean isNegative() {
108             return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
109         }
110 
isEmpty()111         public boolean isEmpty() {
112             return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
113                     && operations == 0;
114         }
115 
add(Entry another)116         public void add(Entry another) {
117             this.rxBytes += another.rxBytes;
118             this.rxPackets += another.rxPackets;
119             this.txBytes += another.txBytes;
120             this.txPackets += another.txPackets;
121             this.operations += another.operations;
122         }
123 
124         @Override
toString()125         public String toString() {
126             final StringBuilder builder = new StringBuilder();
127             builder.append("iface=").append(iface);
128             builder.append(" uid=").append(uid);
129             builder.append(" set=").append(setToString(set));
130             builder.append(" tag=").append(tagToString(tag));
131             builder.append(" rxBytes=").append(rxBytes);
132             builder.append(" rxPackets=").append(rxPackets);
133             builder.append(" txBytes=").append(txBytes);
134             builder.append(" txPackets=").append(txPackets);
135             builder.append(" operations=").append(operations);
136             return builder.toString();
137         }
138 
139         @Override
equals(Object o)140         public boolean equals(Object o) {
141             if (o instanceof Entry) {
142                 final Entry e = (Entry) o;
143                 return uid == e.uid && set == e.set && tag == e.tag && rxBytes == e.rxBytes
144                         && rxPackets == e.rxPackets && txBytes == e.txBytes
145                         && txPackets == e.txPackets && operations == e.operations
146                         && iface.equals(e.iface);
147             }
148             return false;
149         }
150     }
151 
NetworkStats(long elapsedRealtime, int initialSize)152     public NetworkStats(long elapsedRealtime, int initialSize) {
153         this.elapsedRealtime = elapsedRealtime;
154         this.size = 0;
155         this.iface = new String[initialSize];
156         this.uid = new int[initialSize];
157         this.set = new int[initialSize];
158         this.tag = new int[initialSize];
159         this.rxBytes = new long[initialSize];
160         this.rxPackets = new long[initialSize];
161         this.txBytes = new long[initialSize];
162         this.txPackets = new long[initialSize];
163         this.operations = new long[initialSize];
164     }
165 
NetworkStats(Parcel parcel)166     public NetworkStats(Parcel parcel) {
167         elapsedRealtime = parcel.readLong();
168         size = parcel.readInt();
169         iface = parcel.createStringArray();
170         uid = parcel.createIntArray();
171         set = parcel.createIntArray();
172         tag = parcel.createIntArray();
173         rxBytes = parcel.createLongArray();
174         rxPackets = parcel.createLongArray();
175         txBytes = parcel.createLongArray();
176         txPackets = parcel.createLongArray();
177         operations = parcel.createLongArray();
178     }
179 
180     @Override
writeToParcel(Parcel dest, int flags)181     public void writeToParcel(Parcel dest, int flags) {
182         dest.writeLong(elapsedRealtime);
183         dest.writeInt(size);
184         dest.writeStringArray(iface);
185         dest.writeIntArray(uid);
186         dest.writeIntArray(set);
187         dest.writeIntArray(tag);
188         dest.writeLongArray(rxBytes);
189         dest.writeLongArray(rxPackets);
190         dest.writeLongArray(txBytes);
191         dest.writeLongArray(txPackets);
192         dest.writeLongArray(operations);
193     }
194 
195     @Override
clone()196     public NetworkStats clone() {
197         final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
198         NetworkStats.Entry entry = null;
199         for (int i = 0; i < size; i++) {
200             entry = getValues(i, entry);
201             clone.addValues(entry);
202         }
203         return clone;
204     }
205 
206     @VisibleForTesting
addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)207     public NetworkStats addIfaceValues(
208             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
209         return addValues(
210                 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
211     }
212 
213     @VisibleForTesting
addValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)214     public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
215             long rxPackets, long txBytes, long txPackets, long operations) {
216         return addValues(new Entry(
217                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
218     }
219 
220     /**
221      * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
222      * object can be recycled across multiple calls.
223      */
addValues(Entry entry)224     public NetworkStats addValues(Entry entry) {
225         if (size >= this.iface.length) {
226             final int newLength = Math.max(iface.length, 10) * 3 / 2;
227             iface = Arrays.copyOf(iface, newLength);
228             uid = Arrays.copyOf(uid, newLength);
229             set = Arrays.copyOf(set, newLength);
230             tag = Arrays.copyOf(tag, newLength);
231             rxBytes = Arrays.copyOf(rxBytes, newLength);
232             rxPackets = Arrays.copyOf(rxPackets, newLength);
233             txBytes = Arrays.copyOf(txBytes, newLength);
234             txPackets = Arrays.copyOf(txPackets, newLength);
235             operations = Arrays.copyOf(operations, newLength);
236         }
237 
238         iface[size] = entry.iface;
239         uid[size] = entry.uid;
240         set[size] = entry.set;
241         tag[size] = entry.tag;
242         rxBytes[size] = entry.rxBytes;
243         rxPackets[size] = entry.rxPackets;
244         txBytes[size] = entry.txBytes;
245         txPackets[size] = entry.txPackets;
246         operations[size] = entry.operations;
247         size++;
248 
249         return this;
250     }
251 
252     /**
253      * Return specific stats entry.
254      */
getValues(int i, Entry recycle)255     public Entry getValues(int i, Entry recycle) {
256         final Entry entry = recycle != null ? recycle : new Entry();
257         entry.iface = iface[i];
258         entry.uid = uid[i];
259         entry.set = set[i];
260         entry.tag = tag[i];
261         entry.rxBytes = rxBytes[i];
262         entry.rxPackets = rxPackets[i];
263         entry.txBytes = txBytes[i];
264         entry.txPackets = txPackets[i];
265         entry.operations = operations[i];
266         return entry;
267     }
268 
getElapsedRealtime()269     public long getElapsedRealtime() {
270         return elapsedRealtime;
271     }
272 
273     /**
274      * Return age of this {@link NetworkStats} object with respect to
275      * {@link SystemClock#elapsedRealtime()}.
276      */
getElapsedRealtimeAge()277     public long getElapsedRealtimeAge() {
278         return SystemClock.elapsedRealtime() - elapsedRealtime;
279     }
280 
size()281     public int size() {
282         return size;
283     }
284 
285     @VisibleForTesting
internalSize()286     public int internalSize() {
287         return iface.length;
288     }
289 
290     @Deprecated
combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)291     public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
292             long txBytes, long txPackets, long operations) {
293         return combineValues(
294                 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations);
295     }
296 
combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)297     public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes,
298             long rxPackets, long txBytes, long txPackets, long operations) {
299         return combineValues(new Entry(
300                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
301     }
302 
303     /**
304      * Combine given values with an existing row, or create a new row if
305      * {@link #findIndex(String, int, int, int)} is unable to find match. Can
306      * also be used to subtract values from existing rows.
307      */
combineValues(Entry entry)308     public NetworkStats combineValues(Entry entry) {
309         final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag);
310         if (i == -1) {
311             // only create new entry when positive contribution
312             addValues(entry);
313         } else {
314             rxBytes[i] += entry.rxBytes;
315             rxPackets[i] += entry.rxPackets;
316             txBytes[i] += entry.txBytes;
317             txPackets[i] += entry.txPackets;
318             operations[i] += entry.operations;
319         }
320         return this;
321     }
322 
323     /**
324      * Combine all values from another {@link NetworkStats} into this object.
325      */
combineAllValues(NetworkStats another)326     public void combineAllValues(NetworkStats another) {
327         NetworkStats.Entry entry = null;
328         for (int i = 0; i < another.size; i++) {
329             entry = another.getValues(i, entry);
330             combineValues(entry);
331         }
332     }
333 
334     /**
335      * Find first stats index that matches the requested parameters.
336      */
findIndex(String iface, int uid, int set, int tag)337     public int findIndex(String iface, int uid, int set, int tag) {
338         for (int i = 0; i < size; i++) {
339             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
340                     && Objects.equal(iface, this.iface[i])) {
341                 return i;
342             }
343         }
344         return -1;
345     }
346 
347     /**
348      * Find first stats index that matches the requested parameters, starting
349      * search around the hinted index as an optimization.
350      */
351     @VisibleForTesting
findIndexHinted(String iface, int uid, int set, int tag, int hintIndex)352     public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) {
353         for (int offset = 0; offset < size; offset++) {
354             final int halfOffset = offset / 2;
355 
356             // search outwards from hint index, alternating forward and backward
357             final int i;
358             if (offset % 2 == 0) {
359                 i = (hintIndex + halfOffset) % size;
360             } else {
361                 i = (size + hintIndex - halfOffset - 1) % size;
362             }
363 
364             if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
365                     && Objects.equal(iface, this.iface[i])) {
366                 return i;
367             }
368         }
369         return -1;
370     }
371 
372     /**
373      * Splice in {@link #operations} from the given {@link NetworkStats} based
374      * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
375      * since operation counts are at data layer.
376      */
spliceOperationsFrom(NetworkStats stats)377     public void spliceOperationsFrom(NetworkStats stats) {
378         for (int i = 0; i < size; i++) {
379             final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i]);
380             if (j == -1) {
381                 operations[i] = 0;
382             } else {
383                 operations[i] = stats.operations[j];
384             }
385         }
386     }
387 
388     /**
389      * Return list of unique interfaces known by this data structure.
390      */
getUniqueIfaces()391     public String[] getUniqueIfaces() {
392         final HashSet<String> ifaces = new HashSet<String>();
393         for (String iface : this.iface) {
394             if (iface != IFACE_ALL) {
395                 ifaces.add(iface);
396             }
397         }
398         return ifaces.toArray(new String[ifaces.size()]);
399     }
400 
401     /**
402      * Return list of unique UIDs known by this data structure.
403      */
getUniqueUids()404     public int[] getUniqueUids() {
405         final SparseBooleanArray uids = new SparseBooleanArray();
406         for (int uid : this.uid) {
407             uids.put(uid, true);
408         }
409 
410         final int size = uids.size();
411         final int[] result = new int[size];
412         for (int i = 0; i < size; i++) {
413             result[i] = uids.keyAt(i);
414         }
415         return result;
416     }
417 
418     /**
419      * Return total bytes represented by this snapshot object, usually used when
420      * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
421      */
getTotalBytes()422     public long getTotalBytes() {
423         final Entry entry = getTotal(null);
424         return entry.rxBytes + entry.txBytes;
425     }
426 
427     /**
428      * Return total of all fields represented by this snapshot object.
429      */
getTotal(Entry recycle)430     public Entry getTotal(Entry recycle) {
431         return getTotal(recycle, null, UID_ALL, false);
432     }
433 
434     /**
435      * Return total of all fields represented by this snapshot object matching
436      * the requested {@link #uid}.
437      */
getTotal(Entry recycle, int limitUid)438     public Entry getTotal(Entry recycle, int limitUid) {
439         return getTotal(recycle, null, limitUid, false);
440     }
441 
442     /**
443      * Return total of all fields represented by this snapshot object matching
444      * the requested {@link #iface}.
445      */
getTotal(Entry recycle, HashSet<String> limitIface)446     public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
447         return getTotal(recycle, limitIface, UID_ALL, false);
448     }
449 
getTotalIncludingTags(Entry recycle)450     public Entry getTotalIncludingTags(Entry recycle) {
451         return getTotal(recycle, null, UID_ALL, true);
452     }
453 
454     /**
455      * Return total of all fields represented by this snapshot object matching
456      * the requested {@link #iface} and {@link #uid}.
457      *
458      * @param limitIface Set of {@link #iface} to include in total; or {@code
459      *            null} to include all ifaces.
460      */
getTotal( Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags)461     private Entry getTotal(
462             Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
463         final Entry entry = recycle != null ? recycle : new Entry();
464 
465         entry.iface = IFACE_ALL;
466         entry.uid = limitUid;
467         entry.set = SET_ALL;
468         entry.tag = TAG_NONE;
469         entry.rxBytes = 0;
470         entry.rxPackets = 0;
471         entry.txBytes = 0;
472         entry.txPackets = 0;
473         entry.operations = 0;
474 
475         for (int i = 0; i < size; i++) {
476             final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
477             final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
478 
479             if (matchesUid && matchesIface) {
480                 // skip specific tags, since already counted in TAG_NONE
481                 if (tag[i] != TAG_NONE && !includeTags) continue;
482 
483                 entry.rxBytes += rxBytes[i];
484                 entry.rxPackets += rxPackets[i];
485                 entry.txBytes += txBytes[i];
486                 entry.txPackets += txPackets[i];
487                 entry.operations += operations[i];
488             }
489         }
490         return entry;
491     }
492 
493     /**
494      * Subtract the given {@link NetworkStats}, effectively leaving the delta
495      * between two snapshots in time. Assumes that statistics rows collect over
496      * time, and that none of them have disappeared.
497      */
subtract(NetworkStats right)498     public NetworkStats subtract(NetworkStats right) {
499         return subtract(this, right, null, null);
500     }
501 
502     /**
503      * Subtract the two given {@link NetworkStats} objects, returning the delta
504      * between two snapshots in time. Assumes that statistics rows collect over
505      * time, and that none of them have disappeared.
506      * <p>
507      * If counters have rolled backwards, they are clamped to {@code 0} and
508      * reported to the given {@link NonMonotonicObserver}.
509      */
subtract( NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie)510     public static <C> NetworkStats subtract(
511             NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) {
512         long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
513         if (deltaRealtime < 0) {
514             if (observer != null) {
515                 observer.foundNonMonotonic(left, -1, right, -1, cookie);
516             }
517             deltaRealtime = 0;
518         }
519 
520         // result will have our rows, and elapsed time between snapshots
521         final Entry entry = new Entry();
522         final NetworkStats result = new NetworkStats(deltaRealtime, left.size);
523         for (int i = 0; i < left.size; i++) {
524             entry.iface = left.iface[i];
525             entry.uid = left.uid[i];
526             entry.set = left.set[i];
527             entry.tag = left.tag[i];
528 
529             // find remote row that matches, and subtract
530             final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
531             if (j == -1) {
532                 // newly appearing row, return entire value
533                 entry.rxBytes = left.rxBytes[i];
534                 entry.rxPackets = left.rxPackets[i];
535                 entry.txBytes = left.txBytes[i];
536                 entry.txPackets = left.txPackets[i];
537                 entry.operations = left.operations[i];
538             } else {
539                 // existing row, subtract remote value
540                 entry.rxBytes = left.rxBytes[i] - right.rxBytes[j];
541                 entry.rxPackets = left.rxPackets[i] - right.rxPackets[j];
542                 entry.txBytes = left.txBytes[i] - right.txBytes[j];
543                 entry.txPackets = left.txPackets[i] - right.txPackets[j];
544                 entry.operations = left.operations[i] - right.operations[j];
545 
546                 if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
547                         || entry.txPackets < 0 || entry.operations < 0) {
548                     if (observer != null) {
549                         observer.foundNonMonotonic(left, i, right, j, cookie);
550                     }
551                     entry.rxBytes = Math.max(entry.rxBytes, 0);
552                     entry.rxPackets = Math.max(entry.rxPackets, 0);
553                     entry.txBytes = Math.max(entry.txBytes, 0);
554                     entry.txPackets = Math.max(entry.txPackets, 0);
555                     entry.operations = Math.max(entry.operations, 0);
556                 }
557             }
558 
559             result.addValues(entry);
560         }
561 
562         return result;
563     }
564 
565     /**
566      * Return total statistics grouped by {@link #iface}; doesn't mutate the
567      * original structure.
568      */
groupedByIface()569     public NetworkStats groupedByIface() {
570         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
571 
572         final Entry entry = new Entry();
573         entry.uid = UID_ALL;
574         entry.set = SET_ALL;
575         entry.tag = TAG_NONE;
576         entry.operations = 0L;
577 
578         for (int i = 0; i < size; i++) {
579             // skip specific tags, since already counted in TAG_NONE
580             if (tag[i] != TAG_NONE) continue;
581 
582             entry.iface = iface[i];
583             entry.rxBytes = rxBytes[i];
584             entry.rxPackets = rxPackets[i];
585             entry.txBytes = txBytes[i];
586             entry.txPackets = txPackets[i];
587             stats.combineValues(entry);
588         }
589 
590         return stats;
591     }
592 
593     /**
594      * Return total statistics grouped by {@link #uid}; doesn't mutate the
595      * original structure.
596      */
groupedByUid()597     public NetworkStats groupedByUid() {
598         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
599 
600         final Entry entry = new Entry();
601         entry.iface = IFACE_ALL;
602         entry.set = SET_ALL;
603         entry.tag = TAG_NONE;
604 
605         for (int i = 0; i < size; i++) {
606             // skip specific tags, since already counted in TAG_NONE
607             if (tag[i] != TAG_NONE) continue;
608 
609             entry.uid = uid[i];
610             entry.rxBytes = rxBytes[i];
611             entry.rxPackets = rxPackets[i];
612             entry.txBytes = txBytes[i];
613             entry.txPackets = txPackets[i];
614             entry.operations = operations[i];
615             stats.combineValues(entry);
616         }
617 
618         return stats;
619     }
620 
621     /**
622      * Return all rows except those attributed to the requested UID; doesn't
623      * mutate the original structure.
624      */
withoutUids(int[] uids)625     public NetworkStats withoutUids(int[] uids) {
626         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
627 
628         Entry entry = new Entry();
629         for (int i = 0; i < size; i++) {
630             entry = getValues(i, entry);
631             if (!ArrayUtils.contains(uids, entry.uid)) {
632                 stats.addValues(entry);
633             }
634         }
635 
636         return stats;
637     }
638 
dump(String prefix, PrintWriter pw)639     public void dump(String prefix, PrintWriter pw) {
640         pw.print(prefix);
641         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
642         for (int i = 0; i < size; i++) {
643             pw.print(prefix);
644             pw.print("  ["); pw.print(i); pw.print("]");
645             pw.print(" iface="); pw.print(iface[i]);
646             pw.print(" uid="); pw.print(uid[i]);
647             pw.print(" set="); pw.print(setToString(set[i]));
648             pw.print(" tag="); pw.print(tagToString(tag[i]));
649             pw.print(" rxBytes="); pw.print(rxBytes[i]);
650             pw.print(" rxPackets="); pw.print(rxPackets[i]);
651             pw.print(" txBytes="); pw.print(txBytes[i]);
652             pw.print(" txPackets="); pw.print(txPackets[i]);
653             pw.print(" operations="); pw.println(operations[i]);
654         }
655     }
656 
657     /**
658      * Return text description of {@link #set} value.
659      */
setToString(int set)660     public static String setToString(int set) {
661         switch (set) {
662             case SET_ALL:
663                 return "ALL";
664             case SET_DEFAULT:
665                 return "DEFAULT";
666             case SET_FOREGROUND:
667                 return "FOREGROUND";
668             default:
669                 return "UNKNOWN";
670         }
671     }
672 
673     /**
674      * Return text description of {@link #tag} value.
675      */
tagToString(int tag)676     public static String tagToString(int tag) {
677         return "0x" + Integer.toHexString(tag);
678     }
679 
680     @Override
toString()681     public String toString() {
682         final CharArrayWriter writer = new CharArrayWriter();
683         dump("", new PrintWriter(writer));
684         return writer.toString();
685     }
686 
687     @Override
describeContents()688     public int describeContents() {
689         return 0;
690     }
691 
692     public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
693         @Override
694         public NetworkStats createFromParcel(Parcel in) {
695             return new NetworkStats(in);
696         }
697 
698         @Override
699         public NetworkStats[] newArray(int size) {
700             return new NetworkStats[size];
701         }
702     };
703 
704     public interface NonMonotonicObserver<C> {
foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie)705         public void foundNonMonotonic(
706                 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
707     }
708 }
709