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