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