Home | History | Annotate | Download | only in net
      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