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