NetworkStatsHistory.java revision 69b0f63af2e3babc2e9f048c4682032a0c17d9d0
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; 29 30import android.os.Parcel; 31import android.os.Parcelable; 32import android.util.MathUtils; 33 34import java.io.CharArrayWriter; 35import java.io.DataInputStream; 36import java.io.DataOutputStream; 37import java.io.IOException; 38import java.io.PrintWriter; 39import java.net.ProtocolException; 40import java.util.Arrays; 41import 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 */ 54public 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 274 // create any buckets needed by this range 275 ensureBuckets(start, end); 276 277 // distribute data usage into buckets 278 long duration = end - start; 279 final int startIndex = getIndexAfter(end); 280 for (int i = startIndex; i >= 0; i--) { 281 final long curStart = bucketStart[i]; 282 final long curEnd = curStart + bucketDuration; 283 284 // bucket is older than record; we're finished 285 if (curEnd < start) break; 286 // bucket is newer than record; keep looking 287 if (curStart > end) continue; 288 289 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 290 if (overlap <= 0) continue; 291 292 // integer math each time is faster than floating point 293 final long fracRxBytes = entry.rxBytes * overlap / duration; 294 final long fracRxPackets = entry.rxPackets * overlap / duration; 295 final long fracTxBytes = entry.txBytes * overlap / duration; 296 final long fracTxPackets = entry.txPackets * overlap / duration; 297 final long fracOperations = entry.operations * overlap / duration; 298 299 addLong(activeTime, i, overlap); 300 addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes; 301 addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets; 302 addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes; 303 addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets; 304 addLong(operations, i, fracOperations); entry.operations -= fracOperations; 305 306 duration -= overlap; 307 } 308 } 309 310 /** 311 * Record an entire {@link NetworkStatsHistory} into this history. Usually 312 * for combining together stats for external reporting. 313 */ 314 public void recordEntireHistory(NetworkStatsHistory input) { 315 final NetworkStats.Entry entry = new NetworkStats.Entry( 316 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 317 for (int i = 0; i < input.bucketCount; i++) { 318 final long start = input.bucketStart[i]; 319 final long end = start + input.bucketDuration; 320 321 entry.rxBytes = getLong(input.rxBytes, i, 0L); 322 entry.rxPackets = getLong(input.rxPackets, i, 0L); 323 entry.txBytes = getLong(input.txBytes, i, 0L); 324 entry.txPackets = getLong(input.txPackets, i, 0L); 325 entry.operations = getLong(input.operations, i, 0L); 326 327 recordData(start, end, entry); 328 } 329 } 330 331 /** 332 * Ensure that buckets exist for given time range, creating as needed. 333 */ 334 private void ensureBuckets(long start, long end) { 335 // normalize incoming range to bucket boundaries 336 start -= start % bucketDuration; 337 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 338 339 for (long now = start; now < end; now += bucketDuration) { 340 // try finding existing bucket 341 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 342 if (index < 0) { 343 // bucket missing, create and insert 344 insertBucket(~index, now); 345 } 346 } 347 } 348 349 /** 350 * Insert new bucket at requested index and starting time. 351 */ 352 private void insertBucket(int index, long start) { 353 // create more buckets when needed 354 if (bucketCount >= bucketStart.length) { 355 final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; 356 bucketStart = Arrays.copyOf(bucketStart, newLength); 357 if (activeTime != null) activeTime = Arrays.copyOf(activeTime, newLength); 358 if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength); 359 if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength); 360 if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength); 361 if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength); 362 if (operations != null) operations = Arrays.copyOf(operations, newLength); 363 } 364 365 // create gap when inserting bucket in middle 366 if (index < bucketCount) { 367 final int dstPos = index + 1; 368 final int length = bucketCount - index; 369 370 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 371 if (activeTime != null) System.arraycopy(activeTime, index, activeTime, dstPos, length); 372 if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length); 373 if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length); 374 if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length); 375 if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length); 376 if (operations != null) System.arraycopy(operations, index, operations, dstPos, length); 377 } 378 379 bucketStart[index] = start; 380 setLong(activeTime, index, 0L); 381 setLong(rxBytes, index, 0L); 382 setLong(rxPackets, index, 0L); 383 setLong(txBytes, index, 0L); 384 setLong(txPackets, index, 0L); 385 setLong(operations, index, 0L); 386 bucketCount++; 387 } 388 389 /** 390 * Remove buckets older than requested cutoff. 391 */ 392 public void removeBucketsBefore(long cutoff) { 393 int i; 394 for (i = 0; i < bucketCount; i++) { 395 final long curStart = bucketStart[i]; 396 final long curEnd = curStart + bucketDuration; 397 398 // cutoff happens before or during this bucket; everything before 399 // this bucket should be removed. 400 if (curEnd > cutoff) break; 401 } 402 403 if (i > 0) { 404 final int length = bucketStart.length; 405 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 406 if (activeTime != null) activeTime = Arrays.copyOfRange(activeTime, i, length); 407 if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length); 408 if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length); 409 if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length); 410 if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); 411 if (operations != null) operations = Arrays.copyOfRange(operations, i, length); 412 bucketCount -= i; 413 } 414 } 415 416 /** 417 * Return interpolated data usage across the requested range. Interpolates 418 * across buckets, so values may be rounded slightly. 419 */ 420 public Entry getValues(long start, long end, Entry recycle) { 421 return getValues(start, end, Long.MAX_VALUE, recycle); 422 } 423 424 /** 425 * Return interpolated data usage across the requested range. Interpolates 426 * across buckets, so values may be rounded slightly. 427 */ 428 public Entry getValues(long start, long end, long now, Entry recycle) { 429 final Entry entry = recycle != null ? recycle : new Entry(); 430 entry.bucketDuration = end - start; 431 entry.bucketStart = start; 432 entry.activeTime = activeTime != null ? 0 : UNKNOWN; 433 entry.rxBytes = rxBytes != null ? 0 : UNKNOWN; 434 entry.rxPackets = rxPackets != null ? 0 : UNKNOWN; 435 entry.txBytes = txBytes != null ? 0 : UNKNOWN; 436 entry.txPackets = txPackets != null ? 0 : UNKNOWN; 437 entry.operations = operations != null ? 0 : UNKNOWN; 438 439 final int startIndex = getIndexAfter(end); 440 for (int i = startIndex; i >= 0; i--) { 441 final long curStart = bucketStart[i]; 442 final long curEnd = curStart + bucketDuration; 443 444 // bucket is older than record; we're finished 445 if (curEnd < start) break; 446 // bucket is newer than record; keep looking 447 if (curStart > end) continue; 448 449 // include full value for active buckets, otherwise only fractional 450 final boolean activeBucket = curStart < now && curEnd > now; 451 final long overlap; 452 if (activeBucket) { 453 overlap = bucketDuration; 454 } else { 455 final long overlapEnd = curEnd < end ? curEnd : end; 456 final long overlapStart = curStart > start ? curStart : start; 457 overlap = overlapEnd - overlapStart; 458 } 459 if (overlap <= 0) continue; 460 461 // integer math each time is faster than floating point 462 if (activeTime != null) entry.activeTime += activeTime[i] * overlap / bucketDuration; 463 if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration; 464 if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration; 465 if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration; 466 if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration; 467 if (operations != null) entry.operations += operations[i] * overlap / bucketDuration; 468 } 469 470 return entry; 471 } 472 473 /** 474 * @deprecated only for temporary testing 475 */ 476 @Deprecated 477 public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, 478 long txPackets, long operations) { 479 ensureBuckets(start, end); 480 481 final NetworkStats.Entry entry = new NetworkStats.Entry( 482 IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 483 final Random r = new Random(); 484 while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 485 || operations > 32) { 486 final long curStart = randomLong(r, start, end); 487 final long curEnd = randomLong(r, curStart, end); 488 489 entry.rxBytes = randomLong(r, 0, rxBytes); 490 entry.rxPackets = randomLong(r, 0, rxPackets); 491 entry.txBytes = randomLong(r, 0, txBytes); 492 entry.txPackets = randomLong(r, 0, txPackets); 493 entry.operations = randomLong(r, 0, operations); 494 495 rxBytes -= entry.rxBytes; 496 rxPackets -= entry.rxPackets; 497 txBytes -= entry.txBytes; 498 txPackets -= entry.txPackets; 499 operations -= entry.operations; 500 501 recordData(curStart, curEnd, entry); 502 } 503 } 504 505 private static long randomLong(Random r, long start, long end) { 506 return (long) (start + (r.nextFloat() * (end - start))); 507 } 508 509 public void dump(String prefix, PrintWriter pw, boolean fullHistory) { 510 pw.print(prefix); 511 pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); 512 513 final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); 514 if (start > 0) { 515 pw.print(prefix); 516 pw.print(" (omitting "); pw.print(start); pw.println(" buckets)"); 517 } 518 519 for (int i = start; i < bucketCount; i++) { 520 pw.print(prefix); 521 pw.print(" bucketStart="); pw.print(bucketStart[i]); 522 if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); } 523 if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); } 524 if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); } 525 if (txBytes != null) { pw.print(" txBytes="); pw.print(txBytes[i]); } 526 if (txPackets != null) { pw.print(" txPackets="); pw.print(txPackets[i]); } 527 if (operations != null) { pw.print(" operations="); pw.print(operations[i]); } 528 pw.println(); 529 } 530 } 531 532 @Override 533 public String toString() { 534 final CharArrayWriter writer = new CharArrayWriter(); 535 dump("", new PrintWriter(writer), false); 536 return writer.toString(); 537 } 538 539 public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 540 public NetworkStatsHistory createFromParcel(Parcel in) { 541 return new NetworkStatsHistory(in); 542 } 543 544 public NetworkStatsHistory[] newArray(int size) { 545 return new NetworkStatsHistory[size]; 546 } 547 }; 548 549 private static long getLong(long[] array, int i, long value) { 550 return array != null ? array[i] : value; 551 } 552 553 private static void setLong(long[] array, int i, long value) { 554 if (array != null) array[i] = value; 555 } 556 557 private static void addLong(long[] array, int i, long value) { 558 if (array != null) array[i] += value; 559 } 560 561 /** 562 * Utility methods for interacting with {@link DataInputStream} and 563 * {@link DataOutputStream}, mostly dealing with writing partial arrays. 564 */ 565 public static class DataStreamUtils { 566 @Deprecated 567 public static long[] readFullLongArray(DataInputStream in) throws IOException { 568 final int size = in.readInt(); 569 final long[] values = new long[size]; 570 for (int i = 0; i < values.length; i++) { 571 values[i] = in.readLong(); 572 } 573 return values; 574 } 575 576 /** 577 * Read variable-length {@link Long} using protobuf-style approach. 578 */ 579 public static long readVarLong(DataInputStream in) throws IOException { 580 int shift = 0; 581 long result = 0; 582 while (shift < 64) { 583 byte b = in.readByte(); 584 result |= (long) (b & 0x7F) << shift; 585 if ((b & 0x80) == 0) 586 return result; 587 shift += 7; 588 } 589 throw new ProtocolException("malformed long"); 590 } 591 592 /** 593 * Write variable-length {@link Long} using protobuf-style approach. 594 */ 595 public static void writeVarLong(DataOutputStream out, long value) throws IOException { 596 while (true) { 597 if ((value & ~0x7FL) == 0) { 598 out.writeByte((int) value); 599 return; 600 } else { 601 out.writeByte(((int) value & 0x7F) | 0x80); 602 value >>>= 7; 603 } 604 } 605 } 606 607 public static long[] readVarLongArray(DataInputStream in) throws IOException { 608 final int size = in.readInt(); 609 if (size == -1) return null; 610 final long[] values = new long[size]; 611 for (int i = 0; i < values.length; i++) { 612 values[i] = readVarLong(in); 613 } 614 return values; 615 } 616 617 public static void writeVarLongArray(DataOutputStream out, long[] values, int size) 618 throws IOException { 619 if (values == null) { 620 out.writeInt(-1); 621 return; 622 } 623 if (size > values.length) { 624 throw new IllegalArgumentException("size larger than length"); 625 } 626 out.writeInt(size); 627 for (int i = 0; i < size; i++) { 628 writeVarLong(out, values[i]); 629 } 630 } 631 } 632 633 /** 634 * Utility methods for interacting with {@link Parcel} structures, mostly 635 * dealing with writing partial arrays. 636 */ 637 public static class ParcelUtils { 638 public static long[] readLongArray(Parcel in) { 639 final int size = in.readInt(); 640 if (size == -1) return null; 641 final long[] values = new long[size]; 642 for (int i = 0; i < values.length; i++) { 643 values[i] = in.readLong(); 644 } 645 return values; 646 } 647 648 public static void writeLongArray(Parcel out, long[] values, int size) { 649 if (values == null) { 650 out.writeInt(-1); 651 return; 652 } 653 if (size > values.length) { 654 throw new IllegalArgumentException("size larger than length"); 655 } 656 out.writeInt(size); 657 for (int i = 0; i < size; i++) { 658 out.writeLong(values[i]); 659 } 660 } 661 } 662 663} 664