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 Entry()83 public Entry() { 84 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 85 } 86 Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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 Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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 isNegative()105 public boolean isNegative() { 106 return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; 107 } 108 isEmpty()109 public boolean isEmpty() { 110 return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 111 && operations == 0; 112 } 113 add(Entry another)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 toString()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 NetworkStats(long elapsedRealtime, int initialSize)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 NetworkStats(Parcel parcel)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 writeToParcel(Parcel dest, int flags)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 clone()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 addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)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 addValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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 */ addValues(Entry entry)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 */ getValues(int i, Entry recycle)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 getElapsedRealtime()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 */ getElapsedRealtimeAge()263 public long getElapsedRealtimeAge() { 264 return SystemClock.elapsedRealtime() - elapsedRealtime; 265 } 266 size()267 public int size() { 268 return size; 269 } 270 271 // @VisibleForTesting internalSize()272 public int internalSize() { 273 return iface.length; 274 } 275 276 @Deprecated combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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 combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)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 */ combineValues(Entry entry)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 */ combineAllValues(NetworkStats another)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 */ findIndex(String iface, int uid, int set, int tag)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 findIndexHinted(String iface, int uid, int set, int tag, int hintIndex)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 */ spliceOperationsFrom(NetworkStats stats)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 */ getUniqueIfaces()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 */ getUniqueUids()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 */ getTotalBytes()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 */ getTotal(Entry recycle)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 */ getTotal(Entry recycle, int limitUid)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 */ getTotal(Entry recycle, HashSet<String> limitIface)432 public Entry getTotal(Entry recycle, HashSet<String> limitIface) { 433 return getTotal(recycle, limitIface, UID_ALL, false); 434 } 435 getTotalIncludingTags(Entry recycle)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 */ getTotal( Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags)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 */ subtract(NetworkStats right)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 */ subtract( NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie)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 */ groupedByIface()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 */ groupedByUid()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 */ withoutUid(int uid)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 dump(String prefix, PrintWriter pw)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 */ setToString(int set)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 */ tagToString(int tag)662 public static String tagToString(int tag) { 663 return "0x" + Integer.toHexString(tag); 664 } 665 666 @Override toString()667 public String toString() { 668 final CharArrayWriter writer = new CharArrayWriter(); 669 dump("", new PrintWriter(writer)); 670 return writer.toString(); 671 } 672 673 @Override describeContents()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> { foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie)691 public void foundNonMonotonic( 692 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); 693 } 694 } 695