NetworkStatsHistory.java revision 63abc37356728c0575d6a62a203102ae6d97953b
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 17package android.net; 18 19import static android.net.NetworkStats.IFACE_ALL; 20import static android.net.NetworkStats.SET_DEFAULT; 21import static android.net.NetworkStats.TAG_NONE; 22import static android.net.NetworkStats.UID_ALL; 23import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray; 24import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray; 25import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; 26import static android.net.NetworkStatsHistory.Entry.UNKNOWN; 27import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; 28import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; 29import static com.android.internal.util.ArrayUtils.total; 30 31import android.os.Parcel; 32import android.os.Parcelable; 33import android.util.MathUtils; 34 35import com.android.internal.util.IndentingPrintWriter; 36 37import java.io.CharArrayWriter; 38import java.io.DataInputStream; 39import java.io.DataOutputStream; 40import java.io.IOException; 41import java.net.ProtocolException; 42import java.util.Arrays; 43import java.util.Random; 44 45/** 46 * Collection of historical network statistics, recorded into equally-sized 47 * "buckets" in time. Internally it stores data in {@code long} series for more 48 * efficient persistence. 49 * <p> 50 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for 51 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is 52 * sorted at all times. 53 * 54 * @hide 55 */ 56public class NetworkStatsHistory implements Parcelable { 57 private static final int VERSION_INIT = 1; 58 private static final int VERSION_ADD_PACKETS = 2; 59 private static final int VERSION_ADD_ACTIVE = 3; 60 61 public static final int FIELD_ACTIVE_TIME = 0x01; 62 public static final int FIELD_RX_BYTES = 0x02; 63 public static final int FIELD_RX_PACKETS = 0x04; 64 public static final int FIELD_TX_BYTES = 0x08; 65 public static final int FIELD_TX_PACKETS = 0x10; 66 public static final int FIELD_OPERATIONS = 0x20; 67 68 public static final int FIELD_ALL = 0xFFFFFFFF; 69 70 private long bucketDuration; 71 private int bucketCount; 72 private long[] bucketStart; 73 private long[] activeTime; 74 private long[] rxBytes; 75 private long[] rxPackets; 76 private long[] txBytes; 77 private long[] txPackets; 78 private long[] operations; 79 private long totalBytes; 80 81 public static class Entry { 82 public static final long UNKNOWN = -1; 83 84 public long bucketDuration; 85 public long bucketStart; 86 public long activeTime; 87 public long rxBytes; 88 public long rxPackets; 89 public long txBytes; 90 public long txPackets; 91 public long operations; 92 } 93 94 public NetworkStatsHistory(long bucketDuration) { 95 this(bucketDuration, 10, FIELD_ALL); 96 } 97 98 public NetworkStatsHistory(long bucketDuration, int initialSize) { 99 this(bucketDuration, initialSize, FIELD_ALL); 100 } 101 102 public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { 103 this.bucketDuration = bucketDuration; 104 bucketStart = new long[initialSize]; 105 if ((fields & FIELD_ACTIVE_TIME) != 0) activeTime = new long[initialSize]; 106 if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize]; 107 if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize]; 108 if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize]; 109 if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize]; 110 if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize]; 111 bucketCount = 0; 112 totalBytes = 0; 113 } 114 115 public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { 116 this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); 117 recordEntireHistory(existing); 118 } 119 120 public NetworkStatsHistory(Parcel in) { 121 bucketDuration = in.readLong(); 122 bucketStart = readLongArray(in); 123 activeTime = readLongArray(in); 124 rxBytes = readLongArray(in); 125 rxPackets = readLongArray(in); 126 txBytes = readLongArray(in); 127 txPackets = readLongArray(in); 128 operations = readLongArray(in); 129 bucketCount = bucketStart.length; 130 totalBytes = in.readLong(); 131 } 132 133 /** {@inheritDoc} */ 134 public void writeToParcel(Parcel out, int flags) { 135 out.writeLong(bucketDuration); 136 writeLongArray(out, bucketStart, bucketCount); 137 writeLongArray(out, activeTime, bucketCount); 138 writeLongArray(out, rxBytes, bucketCount); 139 writeLongArray(out, rxPackets, bucketCount); 140 writeLongArray(out, txBytes, bucketCount); 141 writeLongArray(out, txPackets, bucketCount); 142 writeLongArray(out, operations, bucketCount); 143 out.writeLong(totalBytes); 144 } 145 146 public NetworkStatsHistory(DataInputStream in) throws IOException { 147 final int version = in.readInt(); 148 switch (version) { 149 case VERSION_INIT: { 150 bucketDuration = in.readLong(); 151 bucketStart = readFullLongArray(in); 152 rxBytes = readFullLongArray(in); 153 rxPackets = new long[bucketStart.length]; 154 txBytes = readFullLongArray(in); 155 txPackets = new long[bucketStart.length]; 156 operations = new long[bucketStart.length]; 157 bucketCount = bucketStart.length; 158 totalBytes = total(rxBytes) + total(txBytes); 159 break; 160 } 161 case VERSION_ADD_PACKETS: 162 case VERSION_ADD_ACTIVE: { 163 bucketDuration = in.readLong(); 164 bucketStart = readVarLongArray(in); 165 activeTime = (version >= VERSION_ADD_ACTIVE) ? readVarLongArray(in) 166 : new long[bucketStart.length]; 167 rxBytes = readVarLongArray(in); 168 rxPackets = readVarLongArray(in); 169 txBytes = readVarLongArray(in); 170 txPackets = readVarLongArray(in); 171 operations = readVarLongArray(in); 172 bucketCount = bucketStart.length; 173 totalBytes = total(rxBytes) + total(txBytes); 174 break; 175 } 176 default: { 177 throw new ProtocolException("unexpected version: " + version); 178 } 179 } 180 } 181 182 public void writeToStream(DataOutputStream out) throws IOException { 183 out.writeInt(VERSION_ADD_ACTIVE); 184 out.writeLong(bucketDuration); 185 writeVarLongArray(out, bucketStart, bucketCount); 186 writeVarLongArray(out, activeTime, bucketCount); 187 writeVarLongArray(out, rxBytes, bucketCount); 188 writeVarLongArray(out, rxPackets, bucketCount); 189 writeVarLongArray(out, txBytes, bucketCount); 190 writeVarLongArray(out, txPackets, bucketCount); 191 writeVarLongArray(out, operations, bucketCount); 192 } 193 194 /** {@inheritDoc} */ 195 public int describeContents() { 196 return 0; 197 } 198 199 public int size() { 200 return bucketCount; 201 } 202 203 public long getBucketDuration() { 204 return bucketDuration; 205 } 206 207 public long getStart() { 208 if (bucketCount > 0) { 209 return bucketStart[0]; 210 } else { 211 return Long.MAX_VALUE; 212 } 213 } 214 215 public long getEnd() { 216 if (bucketCount > 0) { 217 return bucketStart[bucketCount - 1] + bucketDuration; 218 } else { 219 return Long.MIN_VALUE; 220 } 221 } 222 223 /** 224 * Return total bytes represented by this history. 225 */ 226 public long getTotalBytes() { 227 return totalBytes; 228 } 229 230 /** 231 * Return index of bucket that contains or is immediately before the 232 * requested time. 233 */ 234 public int getIndexBefore(long time) { 235 int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); 236 if (index < 0) { 237 index = (~index) - 1; 238 } else { 239 index -= 1; 240 } 241 return MathUtils.constrain(index, 0, bucketCount - 1); 242 } 243 244 /** 245 * Return index of bucket that contains or is immediately after the 246 * requested time. 247 */ 248 public int getIndexAfter(long time) { 249 int index = Arrays.binarySearch(bucketStart, 0, bucketCount, time); 250 if (index < 0) { 251 index = ~index; 252 } else { 253 index += 1; 254 } 255 return MathUtils.constrain(index, 0, bucketCount - 1); 256 } 257 258 /** 259 * Return specific stats entry. 260 */ 261 public Entry getValues(int i, Entry recycle) { 262 final Entry entry = recycle != null ? recycle : new Entry(); 263 entry.bucketStart = bucketStart[i]; 264 entry.bucketDuration = bucketDuration; 265 entry.activeTime = getLong(activeTime, i, UNKNOWN); 266 entry.rxBytes = getLong(rxBytes, i, UNKNOWN); 267 entry.rxPackets = getLong(rxPackets, i, UNKNOWN); 268 entry.txBytes = getLong(txBytes, i, UNKNOWN); 269 entry.txPackets = getLong(txPackets, i, UNKNOWN); 270 entry.operations = getLong(operations, i, UNKNOWN); 271 return entry; 272 } 273 274 /** 275 * Record that data traffic occurred in the given time range. Will 276 * distribute across internal buckets, creating new buckets as needed. 277 */ 278 @Deprecated 279 public void recordData(long start, long end, long rxBytes, long txBytes) { 280 recordData(start, end, new NetworkStats.Entry( 281 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); 282 } 283 284 /** 285 * Record that data traffic occurred in the given time range. Will 286 * distribute across internal buckets, creating new buckets as needed. 287 */ 288 public void recordData(long start, long end, NetworkStats.Entry entry) { 289 long rxBytes = entry.rxBytes; 290 long rxPackets = entry.rxPackets; 291 long txBytes = entry.txBytes; 292 long txPackets = entry.txPackets; 293 long operations = entry.operations; 294 295 if (entry.isNegative()) { 296 throw new IllegalArgumentException("tried recording negative data"); 297 } 298 if (entry.isEmpty()) { 299 return; 300 } 301 302 // create any buckets needed by this range 303 ensureBuckets(start, end); 304 305 // distribute data usage into buckets 306 long duration = end - start; 307 final int startIndex = getIndexAfter(end); 308 for (int i = startIndex; i >= 0; i--) { 309 final long curStart = bucketStart[i]; 310 final long curEnd = curStart + bucketDuration; 311 312 // bucket is older than record; we're finished 313 if (curEnd < start) break; 314 // bucket is newer than record; keep looking 315 if (curStart > end) continue; 316 317 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 318 if (overlap <= 0) continue; 319 320 // integer math each time is faster than floating point 321 final long fracRxBytes = rxBytes * overlap / duration; 322 final long fracRxPackets = rxPackets * overlap / duration; 323 final long fracTxBytes = txBytes * overlap / duration; 324 final long fracTxPackets = txPackets * overlap / duration; 325 final long fracOperations = operations * overlap / duration; 326 327 addLong(activeTime, i, overlap); 328 addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes; 329 addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets; 330 addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes; 331 addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets; 332 addLong(this.operations, i, fracOperations); operations -= fracOperations; 333 334 duration -= overlap; 335 } 336 337 totalBytes += entry.rxBytes + entry.txBytes; 338 } 339 340 /** 341 * Record an entire {@link NetworkStatsHistory} into this history. Usually 342 * for combining together stats for external reporting. 343 */ 344 public void recordEntireHistory(NetworkStatsHistory input) { 345 final NetworkStats.Entry entry = new NetworkStats.Entry( 346 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 347 for (int i = 0; i < input.bucketCount; i++) { 348 final long start = input.bucketStart[i]; 349 final long end = start + input.bucketDuration; 350 351 entry.rxBytes = getLong(input.rxBytes, i, 0L); 352 entry.rxPackets = getLong(input.rxPackets, i, 0L); 353 entry.txBytes = getLong(input.txBytes, i, 0L); 354 entry.txPackets = getLong(input.txPackets, i, 0L); 355 entry.operations = getLong(input.operations, i, 0L); 356 357 recordData(start, end, entry); 358 } 359 } 360 361 /** 362 * Ensure that buckets exist for given time range, creating as needed. 363 */ 364 private void ensureBuckets(long start, long end) { 365 // normalize incoming range to bucket boundaries 366 start -= start % bucketDuration; 367 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 368 369 for (long now = start; now < end; now += bucketDuration) { 370 // try finding existing bucket 371 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 372 if (index < 0) { 373 // bucket missing, create and insert 374 insertBucket(~index, now); 375 } 376 } 377 } 378 379 /** 380 * Insert new bucket at requested index and starting time. 381 */ 382 private void insertBucket(int index, long start) { 383 // create more buckets when needed 384 if (bucketCount >= bucketStart.length) { 385 final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; 386 bucketStart = Arrays.copyOf(bucketStart, newLength); 387 if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength); 388 if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength); 389 if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength); 390 if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength); 391 if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength); 392 if (operations != null) operations = Arrays.copyOf(operations, newLength); 393 } 394 395 // create gap when inserting bucket in middle 396 if (index < bucketCount) { 397 final int dstPos = index + 1; 398 final int length = bucketCount - index; 399 400 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 401 if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length); 402 if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length); 403 if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length); 404 if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length); 405 if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length); 406 if (operations != null) System.arraycopy(operations, index, operations, dstPos, length); 407 } 408 409 bucketStart[index] = start; 410 setLong(activeTime, index, 0L); 411 setLong(rxBytes, index, 0L); 412 setLong(rxPackets, index, 0L); 413 setLong(txBytes, index, 0L); 414 setLong(txPackets, index, 0L); 415 setLong(operations, index, 0L); 416 bucketCount++; 417 } 418 419 /** 420 * Remove buckets older than requested cutoff. 421 */ 422 @Deprecated 423 public void removeBucketsBefore(long cutoff) { 424 int i; 425 for (i = 0; i < bucketCount; i++) { 426 final long curStart = bucketStart[i]; 427 final long curEnd = curStart + bucketDuration; 428 429 // cutoff happens before or during this bucket; everything before 430 // this bucket should be removed. 431 if (curEnd > cutoff) break; 432 } 433 434 if (i > 0) { 435 final int length = bucketStart.length; 436 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 437 if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length); 438 if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length); 439 if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length); 440 if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length); 441 if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); 442 if (operations != null) operations = Arrays.copyOfRange(operations, i, length); 443 bucketCount -= i; 444 445 // TODO: subtract removed values from totalBytes 446 } 447 } 448 449 /** 450 * Return interpolated data usage across the requested range. Interpolates 451 * across buckets, so values may be rounded slightly. 452 */ 453 public Entry getValues(long start, long end, Entry recycle) { 454 return getValues(start, end, Long.MAX_VALUE, recycle); 455 } 456 457 /** 458 * Return interpolated data usage across the requested range. Interpolates 459 * across buckets, so values may be rounded slightly. 460 */ 461 public Entry getValues(long start, long end, long now, Entry recycle) { 462 final Entry entry = recycle != null ? recycle : new Entry(); 463 entry.bucketDuration = end - start; 464 entry.bucketStart = start; 465 entry.activeTime = activeTime != null ? 0 : UNKNOWN; 466 entry.rxBytes = rxBytes != null ? 0 : UNKNOWN; 467 entry.rxPackets = rxPackets != null ? 0 : UNKNOWN; 468 entry.txBytes = txBytes != null ? 0 : UNKNOWN; 469 entry.txPackets = txPackets != null ? 0 : UNKNOWN; 470 entry.operations = operations != null ? 0 : UNKNOWN; 471 472 final int startIndex = getIndexAfter(end); 473 for (int i = startIndex; i >= 0; i--) { 474 final long curStart = bucketStart[i]; 475 final long curEnd = curStart + bucketDuration; 476 477 // bucket is older than request; we're finished 478 if (curEnd <= start) break; 479 // bucket is newer than request; keep looking 480 if (curStart >= end) continue; 481 482 // include full value for active buckets, otherwise only fractional 483 final boolean activeBucket = curStart < now && curEnd > now; 484 final long overlap; 485 if (activeBucket) { 486 overlap = bucketDuration; 487 } else { 488 final long overlapEnd = curEnd < end ? curEnd : end; 489 final long overlapStart = curStart > start ? curStart : start; 490 overlap = overlapEnd - overlapStart; 491 } 492 if (overlap <= 0) continue; 493 494 // integer math each time is faster than floating point 495 if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration; 496 if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration; 497 if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration; 498 if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration; 499 if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration; 500 if (operations != null) entry.operations += operations[i] * overlap / bucketDuration; 501 } 502 return entry; 503 } 504 505 /** 506 * @deprecated only for temporary testing 507 */ 508 @Deprecated 509 public void generateRandom(long start, long end, long bytes) { 510 final Random r = new Random(); 511 512 final float fractionRx = r.nextFloat(); 513 final long rxBytes = (long) (bytes * fractionRx); 514 final long txBytes = (long) (bytes * (1 - fractionRx)); 515 516 final long rxPackets = rxBytes / 1024; 517 final long txPackets = txBytes / 1024; 518 final long operations = rxBytes / 2048; 519 520 generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r); 521 } 522 523 /** 524 * @deprecated only for temporary testing 525 */ 526 @Deprecated 527 public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, 528 long txPackets, long operations, Random r) { 529 ensureBuckets(start, end); 530 531 final NetworkStats.Entry entry = new NetworkStats.Entry( 532 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 533 while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 534 || operations > 32) { 535 final long curStart = randomLong(r, start, end); 536 final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2); 537 538 entry.rxBytes = randomLong(r, 0, rxBytes); 539 entry.rxPackets = randomLong(r, 0, rxPackets); 540 entry.txBytes = randomLong(r, 0, txBytes); 541 entry.txPackets = randomLong(r, 0, txPackets); 542 entry.operations = randomLong(r, 0, operations); 543 544 rxBytes -= entry.rxBytes; 545 rxPackets -= entry.rxPackets; 546 txBytes -= entry.txBytes; 547 txPackets -= entry.txPackets; 548 operations -= entry.operations; 549 550 recordData(curStart, curEnd, entry); 551 } 552 } 553 554 public static long randomLong(Random r, long start, long end) { 555 return (long) (start + (r.nextFloat() * (end - start))); 556 } 557 558 public void dump(IndentingPrintWriter pw, boolean fullHistory) { 559 pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); 560 pw.increaseIndent(); 561 562 final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); 563 if (start > 0) { 564 pw.print("(omitting "); pw.print(start); pw.println(" buckets)"); 565 } 566 567 for (int i = start; i < bucketCount; i++) { 568 pw.print("bucketStart="); pw.print(bucketStart[i]); 569 if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); } 570 if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); } 571 if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); } 572 if (txBytes != null) { pw.print(" txBytes="); pw.print(txBytes[i]); } 573 if (txPackets != null) { pw.print(" txPackets="); pw.print(txPackets[i]); } 574 if (operations != null) { pw.print(" operations="); pw.print(operations[i]); } 575 pw.println(); 576 } 577 578 pw.decreaseIndent(); 579 } 580 581 @Override 582 public String toString() { 583 final CharArrayWriter writer = new CharArrayWriter(); 584 dump(new IndentingPrintWriter(writer, " "), false); 585 return writer.toString(); 586 } 587 588 public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 589 public NetworkStatsHistory createFromParcel(Parcel in) { 590 return new NetworkStatsHistory(in); 591 } 592 593 public NetworkStatsHistory[] newArray(int size) { 594 return new NetworkStatsHistory[size]; 595 } 596 }; 597 598 private static long getLong(long[] array, int i, long value) { 599 return array != null ? array[i] : value; 600 } 601 602 private static void setLong(long[] array, int i, long value) { 603 if (array != null) array[i] = value; 604 } 605 606 private static void addLong(long[] array, int i, long value) { 607 if (array != null) array[i] += value; 608 } 609 610 public int estimateResizeBuckets(long newBucketDuration) { 611 return (int) (size() * getBucketDuration() / newBucketDuration); 612 } 613 614 /** 615 * Utility methods for interacting with {@link DataInputStream} and 616 * {@link DataOutputStream}, mostly dealing with writing partial arrays. 617 */ 618 public static class DataStreamUtils { 619 @Deprecated 620 public static long[] readFullLongArray(DataInputStream in) throws IOException { 621 final int size = in.readInt(); 622 final long[] values = new long[size]; 623 for (int i = 0; i < values.length; i++) { 624 values[i] = in.readLong(); 625 } 626 return values; 627 } 628 629 /** 630 * Read variable-length {@link Long} using protobuf-style approach. 631 */ 632 public static long readVarLong(DataInputStream in) throws IOException { 633 int shift = 0; 634 long result = 0; 635 while (shift < 64) { 636 byte b = in.readByte(); 637 result |= (long) (b & 0x7F) << shift; 638 if ((b & 0x80) == 0) 639 return result; 640 shift += 7; 641 } 642 throw new ProtocolException("malformed long"); 643 } 644 645 /** 646 * Write variable-length {@link Long} using protobuf-style approach. 647 */ 648 public static void writeVarLong(DataOutputStream out, long value) throws IOException { 649 while (true) { 650 if ((value & ~0x7FL) == 0) { 651 out.writeByte((int) value); 652 return; 653 } else { 654 out.writeByte(((int) value & 0x7F) | 0x80); 655 value >>>= 7; 656 } 657 } 658 } 659 660 public static long[] readVarLongArray(DataInputStream in) throws IOException { 661 final int size = in.readInt(); 662 if (size == -1) return null; 663 final long[] values = new long[size]; 664 for (int i = 0; i < values.length; i++) { 665 values[i] = readVarLong(in); 666 } 667 return values; 668 } 669 670 public static void writeVarLongArray(DataOutputStream out, long[] values, int size) 671 throws IOException { 672 if (values == null) { 673 out.writeInt(-1); 674 return; 675 } 676 if (size > values.length) { 677 throw new IllegalArgumentException("size larger than length"); 678 } 679 out.writeInt(size); 680 for (int i = 0; i < size; i++) { 681 writeVarLong(out, values[i]); 682 } 683 } 684 } 685 686 /** 687 * Utility methods for interacting with {@link Parcel} structures, mostly 688 * dealing with writing partial arrays. 689 */ 690 public static class ParcelUtils { 691 public static long[] readLongArray(Parcel in) { 692 final int size = in.readInt(); 693 if (size == -1) return null; 694 final long[] values = new long[size]; 695 for (int i = 0; i < values.length; i++) { 696 values[i] = in.readLong(); 697 } 698 return values; 699 } 700 701 public static void writeLongArray(Parcel out, long[] values, int size) { 702 if (values == null) { 703 out.writeInt(-1); 704 return; 705 } 706 if (size > values.length) { 707 throw new IllegalArgumentException("size larger than length"); 708 } 709 out.writeInt(size); 710 for (int i = 0; i < size; i++) { 711 out.writeLong(values[i]); 712 } 713 } 714 } 715 716} 717