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