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        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