Home | History | Annotate | Download | only in usage
      1 /**
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations
     14  * under the License.
     15  */
     16 
     17 package android.app.usage;
     18 
     19 import android.annotation.IntDef;
     20 import android.content.Context;
     21 import android.net.INetworkStatsService;
     22 import android.net.INetworkStatsSession;
     23 import android.net.NetworkStatsHistory;
     24 import android.net.NetworkTemplate;
     25 import android.net.TrafficStats;
     26 import android.os.RemoteException;
     27 import android.util.IntArray;
     28 import android.util.Log;
     29 
     30 import dalvik.system.CloseGuard;
     31 
     32 import java.lang.annotation.Retention;
     33 import java.lang.annotation.RetentionPolicy;
     34 
     35 /**
     36  * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects
     37  * are returned as results to various queries in {@link NetworkStatsManager}.
     38  */
     39 public final class NetworkStats implements AutoCloseable {
     40     private final static String TAG = "NetworkStats";
     41 
     42     private final CloseGuard mCloseGuard = CloseGuard.get();
     43 
     44     /**
     45      * Start timestamp of stats collected
     46      */
     47     private final long mStartTimeStamp;
     48 
     49     /**
     50      * End timestamp of stats collected
     51      */
     52     private final long mEndTimeStamp;
     53 
     54     /**
     55      * Non-null array indicates the query enumerates over uids.
     56      */
     57     private int[] mUids;
     58 
     59     /**
     60      * Index of the current uid in mUids when doing uid enumeration or a single uid value,
     61      * depending on query type.
     62      */
     63     private int mUidOrUidIndex;
     64 
     65     /**
     66      * Tag id in case if was specified in the query.
     67      */
     68     private int mTag = android.net.NetworkStats.TAG_NONE;
     69 
     70     /**
     71      * State in case it was not specified in the query.
     72      */
     73     private int mState = Bucket.STATE_ALL;
     74 
     75     /**
     76      * The session while the query requires it, null if all the stats have been collected or close()
     77      * has been called.
     78      */
     79     private INetworkStatsSession mSession;
     80     private NetworkTemplate mTemplate;
     81 
     82     /**
     83      * Results of a summary query.
     84      */
     85     private android.net.NetworkStats mSummary = null;
     86 
     87     /**
     88      * Results of detail queries.
     89      */
     90     private NetworkStatsHistory mHistory = null;
     91 
     92     /**
     93      * Where we are in enumerating over the current result.
     94      */
     95     private int mEnumerationIndex = 0;
     96 
     97     /**
     98      * Recycling entry objects to prevent heap fragmentation.
     99      */
    100     private android.net.NetworkStats.Entry mRecycledSummaryEntry = null;
    101     private NetworkStatsHistory.Entry mRecycledHistoryEntry = null;
    102 
    103     /** @hide */
    104     NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp,
    105             long endTimestamp, INetworkStatsService statsService)
    106             throws RemoteException, SecurityException {
    107         // Open network stats session
    108         mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName());
    109         mCloseGuard.open("close");
    110         mTemplate = template;
    111         mStartTimeStamp = startTimestamp;
    112         mEndTimeStamp = endTimestamp;
    113     }
    114 
    115     @Override
    116     protected void finalize() throws Throwable {
    117         try {
    118             if (mCloseGuard != null) {
    119                 mCloseGuard.warnIfOpen();
    120             }
    121             close();
    122         } finally {
    123             super.finalize();
    124         }
    125     }
    126 
    127     // -------------------------BEGINNING OF PUBLIC API-----------------------------------
    128 
    129     /**
    130      * Buckets are the smallest elements of a query result. As some dimensions of a result may be
    131      * aggregated (e.g. time or state) some values may be equal across all buckets.
    132      */
    133     public static class Bucket {
    134         /** @hide */
    135         @IntDef(prefix = { "STATE_" }, value = {
    136                 STATE_ALL,
    137                 STATE_DEFAULT,
    138                 STATE_FOREGROUND
    139         })
    140         @Retention(RetentionPolicy.SOURCE)
    141         public @interface State {}
    142 
    143         /**
    144          * Combined usage across all states.
    145          */
    146         public static final int STATE_ALL = -1;
    147 
    148         /**
    149          * Usage not accounted for in any other state.
    150          */
    151         public static final int STATE_DEFAULT = 0x1;
    152 
    153         /**
    154          * Foreground usage.
    155          */
    156         public static final int STATE_FOREGROUND = 0x2;
    157 
    158         /**
    159          * Special UID value for aggregate/unspecified.
    160          */
    161         public static final int UID_ALL = android.net.NetworkStats.UID_ALL;
    162 
    163         /**
    164          * Special UID value for removed apps.
    165          */
    166         public static final int UID_REMOVED = TrafficStats.UID_REMOVED;
    167 
    168         /**
    169          * Special UID value for data usage by tethering.
    170          */
    171         public static final int UID_TETHERING = TrafficStats.UID_TETHERING;
    172 
    173         /** @hide */
    174         @IntDef(prefix = { "METERED_" }, value = {
    175                 METERED_ALL,
    176                 METERED_NO,
    177                 METERED_YES
    178         })
    179         @Retention(RetentionPolicy.SOURCE)
    180         public @interface Metered {}
    181 
    182         /**
    183          * Combined usage across all metered states. Covers metered and unmetered usage.
    184          */
    185         public static final int METERED_ALL = -1;
    186 
    187         /**
    188          * Usage that occurs on an unmetered network.
    189          */
    190         public static final int METERED_NO = 0x1;
    191 
    192         /**
    193          * Usage that occurs on a metered network.
    194          *
    195          * <p>A network is classified as metered when the user is sensitive to heavy data usage on
    196          * that connection.
    197          */
    198         public static final int METERED_YES = 0x2;
    199 
    200         /** @hide */
    201         @IntDef(prefix = { "ROAMING_" }, value = {
    202                 ROAMING_ALL,
    203                 ROAMING_NO,
    204                 ROAMING_YES
    205         })
    206         @Retention(RetentionPolicy.SOURCE)
    207         public @interface Roaming {}
    208 
    209         /**
    210          * Combined usage across all roaming states. Covers both roaming and non-roaming usage.
    211          */
    212         public static final int ROAMING_ALL = -1;
    213 
    214         /**
    215          * Usage that occurs on a home, non-roaming network.
    216          *
    217          * <p>Any cellular usage in this bucket was incurred while the device was connected to a
    218          * tower owned or operated by the user's wireless carrier, or a tower that the user's
    219          * wireless carrier has indicated should be treated as a home network regardless.
    220          *
    221          * <p>This is also the default value for network types that do not support roaming.
    222          */
    223         public static final int ROAMING_NO = 0x1;
    224 
    225         /**
    226          * Usage that occurs on a roaming network.
    227          *
    228          * <p>Any cellular usage in this bucket as incurred while the device was roaming on another
    229          * carrier's network, for which additional charges may apply.
    230          */
    231         public static final int ROAMING_YES = 0x2;
    232 
    233         /** @hide */
    234         @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = {
    235                 DEFAULT_NETWORK_ALL,
    236                 DEFAULT_NETWORK_NO,
    237                 DEFAULT_NETWORK_YES
    238         })
    239         @Retention(RetentionPolicy.SOURCE)
    240         public @interface DefaultNetworkStatus {}
    241 
    242         /**
    243          * Combined usage for this network regardless of default network status.
    244          */
    245         public static final int DEFAULT_NETWORK_ALL = -1;
    246 
    247         /**
    248          * Usage that occurs while this network is not a default network.
    249          *
    250          * <p>This implies that the app responsible for this usage requested that it occur on a
    251          * specific network different from the one(s) the system would have selected for it.
    252          */
    253         public static final int DEFAULT_NETWORK_NO = 0x1;
    254 
    255         /**
    256          * Usage that occurs while this network is a default network.
    257          *
    258          * <p>This implies that the app either did not select a specific network for this usage,
    259          * or it selected a network that the system could have selected for app traffic.
    260          */
    261         public static final int DEFAULT_NETWORK_YES = 0x2;
    262 
    263         /**
    264          * Special TAG value for total data across all tags
    265          */
    266         public static final int TAG_NONE = android.net.NetworkStats.TAG_NONE;
    267 
    268         private int mUid;
    269         private int mTag;
    270         private int mState;
    271         private int mDefaultNetworkStatus;
    272         private int mMetered;
    273         private int mRoaming;
    274         private long mBeginTimeStamp;
    275         private long mEndTimeStamp;
    276         private long mRxBytes;
    277         private long mRxPackets;
    278         private long mTxBytes;
    279         private long mTxPackets;
    280 
    281         private static int convertSet(@State int state) {
    282             switch (state) {
    283                 case STATE_ALL: return android.net.NetworkStats.SET_ALL;
    284                 case STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT;
    285                 case STATE_FOREGROUND: return android.net.NetworkStats.SET_FOREGROUND;
    286             }
    287             return 0;
    288         }
    289 
    290         private static @State int convertState(int networkStatsSet) {
    291             switch (networkStatsSet) {
    292                 case android.net.NetworkStats.SET_ALL : return STATE_ALL;
    293                 case android.net.NetworkStats.SET_DEFAULT : return STATE_DEFAULT;
    294                 case android.net.NetworkStats.SET_FOREGROUND : return STATE_FOREGROUND;
    295             }
    296             return 0;
    297         }
    298 
    299         private static int convertUid(int uid) {
    300             switch (uid) {
    301                 case TrafficStats.UID_REMOVED: return UID_REMOVED;
    302                 case TrafficStats.UID_TETHERING: return UID_TETHERING;
    303             }
    304             return uid;
    305         }
    306 
    307         private static int convertTag(int tag) {
    308             switch (tag) {
    309                 case android.net.NetworkStats.TAG_NONE: return TAG_NONE;
    310             }
    311             return tag;
    312         }
    313 
    314         private static @Metered int convertMetered(int metered) {
    315             switch (metered) {
    316                 case android.net.NetworkStats.METERED_ALL : return METERED_ALL;
    317                 case android.net.NetworkStats.METERED_NO: return METERED_NO;
    318                 case android.net.NetworkStats.METERED_YES: return METERED_YES;
    319             }
    320             return 0;
    321         }
    322 
    323         private static @Roaming int convertRoaming(int roaming) {
    324             switch (roaming) {
    325                 case android.net.NetworkStats.ROAMING_ALL : return ROAMING_ALL;
    326                 case android.net.NetworkStats.ROAMING_NO: return ROAMING_NO;
    327                 case android.net.NetworkStats.ROAMING_YES: return ROAMING_YES;
    328             }
    329             return 0;
    330         }
    331 
    332         private static @DefaultNetworkStatus int convertDefaultNetworkStatus(
    333                 int defaultNetworkStatus) {
    334             switch (defaultNetworkStatus) {
    335                 case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL;
    336                 case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO;
    337                 case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES;
    338             }
    339             return 0;
    340         }
    341 
    342         public Bucket() {
    343         }
    344 
    345         /**
    346          * Key of the bucket. Usually an app uid or one of the following special values:<p />
    347          * <ul>
    348          * <li>{@link #UID_REMOVED}</li>
    349          * <li>{@link #UID_TETHERING}</li>
    350          * <li>{@link android.os.Process#SYSTEM_UID}</li>
    351          * </ul>
    352          * @return Bucket key.
    353          */
    354         public int getUid() {
    355             return mUid;
    356         }
    357 
    358         /**
    359          * Tag of the bucket.<p />
    360          * @return Bucket tag.
    361          */
    362         public int getTag() {
    363             return mTag;
    364         }
    365 
    366         /**
    367          * Usage state. One of the following values:<p/>
    368          * <ul>
    369          * <li>{@link #STATE_ALL}</li>
    370          * <li>{@link #STATE_DEFAULT}</li>
    371          * <li>{@link #STATE_FOREGROUND}</li>
    372          * </ul>
    373          * @return Usage state.
    374          */
    375         public @State int getState() {
    376             return mState;
    377         }
    378 
    379         /**
    380          * Metered state. One of the following values:<p/>
    381          * <ul>
    382          * <li>{@link #METERED_ALL}</li>
    383          * <li>{@link #METERED_NO}</li>
    384          * <li>{@link #METERED_YES}</li>
    385          * </ul>
    386          * <p>A network is classified as metered when the user is sensitive to heavy data usage on
    387          * that connection. Apps may warn before using these networks for large downloads. The
    388          * metered state can be set by the user within data usage network restrictions.
    389          */
    390         public @Metered int getMetered() {
    391             return mMetered;
    392         }
    393 
    394         /**
    395          * Roaming state. One of the following values:<p/>
    396          * <ul>
    397          * <li>{@link #ROAMING_ALL}</li>
    398          * <li>{@link #ROAMING_NO}</li>
    399          * <li>{@link #ROAMING_YES}</li>
    400          * </ul>
    401          */
    402         public @Roaming int getRoaming() {
    403             return mRoaming;
    404         }
    405 
    406         /**
    407          * Default network status. One of the following values:<p/>
    408          * <ul>
    409          * <li>{@link #DEFAULT_NETWORK_ALL}</li>
    410          * <li>{@link #DEFAULT_NETWORK_NO}</li>
    411          * <li>{@link #DEFAULT_NETWORK_YES}</li>
    412          * </ul>
    413          */
    414         public @DefaultNetworkStatus int getDefaultNetworkStatus() {
    415             return mDefaultNetworkStatus;
    416         }
    417 
    418         /**
    419          * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see
    420          * {@link java.lang.System#currentTimeMillis}.
    421          * @return Start of interval.
    422          */
    423         public long getStartTimeStamp() {
    424             return mBeginTimeStamp;
    425         }
    426 
    427         /**
    428          * End timestamp of the bucket's time interval. Defined in terms of "Unix time", see
    429          * {@link java.lang.System#currentTimeMillis}.
    430          * @return End of interval.
    431          */
    432         public long getEndTimeStamp() {
    433             return mEndTimeStamp;
    434         }
    435 
    436         /**
    437          * Number of bytes received during the bucket's time interval. Statistics are measured at
    438          * the network layer, so they include both TCP and UDP usage.
    439          * @return Number of bytes.
    440          */
    441         public long getRxBytes() {
    442             return mRxBytes;
    443         }
    444 
    445         /**
    446          * Number of bytes transmitted during the bucket's time interval. Statistics are measured at
    447          * the network layer, so they include both TCP and UDP usage.
    448          * @return Number of bytes.
    449          */
    450         public long getTxBytes() {
    451             return mTxBytes;
    452         }
    453 
    454         /**
    455          * Number of packets received during the bucket's time interval. Statistics are measured at
    456          * the network layer, so they include both TCP and UDP usage.
    457          * @return Number of packets.
    458          */
    459         public long getRxPackets() {
    460             return mRxPackets;
    461         }
    462 
    463         /**
    464          * Number of packets transmitted during the bucket's time interval. Statistics are measured
    465          * at the network layer, so they include both TCP and UDP usage.
    466          * @return Number of packets.
    467          */
    468         public long getTxPackets() {
    469             return mTxPackets;
    470         }
    471     }
    472 
    473     /**
    474      * Fills the recycled bucket with data of the next bin in the enumeration.
    475      * @param bucketOut Bucket to be filled with data.
    476      * @return true if successfully filled the bucket, false otherwise.
    477      */
    478     public boolean getNextBucket(Bucket bucketOut) {
    479         if (mSummary != null) {
    480             return getNextSummaryBucket(bucketOut);
    481         } else {
    482             return getNextHistoryBucket(bucketOut);
    483         }
    484     }
    485 
    486     /**
    487      * Check if it is possible to ask for a next bucket in the enumeration.
    488      * @return true if there is at least one more bucket.
    489      */
    490     public boolean hasNextBucket() {
    491         if (mSummary != null) {
    492             return mEnumerationIndex < mSummary.size();
    493         } else if (mHistory != null) {
    494             return mEnumerationIndex < mHistory.size()
    495                     || hasNextUid();
    496         }
    497         return false;
    498     }
    499 
    500     /**
    501      * Closes the enumeration. Call this method before this object gets out of scope.
    502      */
    503     @Override
    504     public void close() {
    505         if (mSession != null) {
    506             try {
    507                 mSession.close();
    508             } catch (RemoteException e) {
    509                 Log.w(TAG, e);
    510                 // Otherwise, meh
    511             }
    512         }
    513         mSession = null;
    514         if (mCloseGuard != null) {
    515             mCloseGuard.close();
    516         }
    517     }
    518 
    519     // -------------------------END OF PUBLIC API-----------------------------------
    520 
    521     /**
    522      * Collects device summary results into a Bucket.
    523      * @throws RemoteException
    524      */
    525     Bucket getDeviceSummaryForNetwork() throws RemoteException {
    526         mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, mStartTimeStamp, mEndTimeStamp);
    527 
    528         // Setting enumeration index beyond end to avoid accidental enumeration over data that does
    529         // not belong to the calling user.
    530         mEnumerationIndex = mSummary.size();
    531 
    532         return getSummaryAggregate();
    533     }
    534 
    535     /**
    536      * Collects summary results and sets summary enumeration mode.
    537      * @throws RemoteException
    538      */
    539     void startSummaryEnumeration() throws RemoteException {
    540         mSummary = mSession.getSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp,
    541                 false /* includeTags */);
    542         mEnumerationIndex = 0;
    543     }
    544 
    545     /**
    546      * Collects history results for uid and resets history enumeration index.
    547      */
    548     void startHistoryEnumeration(int uid, int tag, int state) {
    549         mHistory = null;
    550         try {
    551             mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid,
    552                     Bucket.convertSet(state), tag, NetworkStatsHistory.FIELD_ALL,
    553                     mStartTimeStamp, mEndTimeStamp);
    554             setSingleUidTagState(uid, tag, state);
    555         } catch (RemoteException e) {
    556             Log.w(TAG, e);
    557             // Leaving mHistory null
    558         }
    559         mEnumerationIndex = 0;
    560     }
    561 
    562     /**
    563      * Starts uid enumeration for current user.
    564      * @throws RemoteException
    565      */
    566     void startUserUidEnumeration() throws RemoteException {
    567         // TODO: getRelevantUids should be sensitive to time interval. When that's done,
    568         //       the filtering logic below can be removed.
    569         int[] uids = mSession.getRelevantUids();
    570         // Filtering of uids with empty history.
    571         IntArray filteredUids = new IntArray(uids.length);
    572         for (int uid : uids) {
    573             try {
    574                 NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid,
    575                         android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE,
    576                         NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
    577                 if (history != null && history.size() > 0) {
    578                     filteredUids.add(uid);
    579                 }
    580             } catch (RemoteException e) {
    581                 Log.w(TAG, "Error while getting history of uid " + uid, e);
    582             }
    583         }
    584         mUids = filteredUids.toArray();
    585         mUidOrUidIndex = -1;
    586         stepHistory();
    587     }
    588 
    589     /**
    590      * Steps to next uid in enumeration and collects history for that.
    591      */
    592     private void stepHistory(){
    593         if (hasNextUid()) {
    594             stepUid();
    595             mHistory = null;
    596             try {
    597                 mHistory = mSession.getHistoryIntervalForUid(mTemplate, getUid(),
    598                         android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE,
    599                         NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
    600             } catch (RemoteException e) {
    601                 Log.w(TAG, e);
    602                 // Leaving mHistory null
    603             }
    604             mEnumerationIndex = 0;
    605         }
    606     }
    607 
    608     private void fillBucketFromSummaryEntry(Bucket bucketOut) {
    609         bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
    610         bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag);
    611         bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
    612         bucketOut.mDefaultNetworkStatus = Bucket.convertDefaultNetworkStatus(
    613                 mRecycledSummaryEntry.defaultNetwork);
    614         bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered);
    615         bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming);
    616         bucketOut.mBeginTimeStamp = mStartTimeStamp;
    617         bucketOut.mEndTimeStamp = mEndTimeStamp;
    618         bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes;
    619         bucketOut.mRxPackets = mRecycledSummaryEntry.rxPackets;
    620         bucketOut.mTxBytes = mRecycledSummaryEntry.txBytes;
    621         bucketOut.mTxPackets = mRecycledSummaryEntry.txPackets;
    622     }
    623 
    624     /**
    625      * Getting the next item in summary enumeration.
    626      * @param bucketOut Next item will be set here.
    627      * @return true if a next item could be set.
    628      */
    629     private boolean getNextSummaryBucket(Bucket bucketOut) {
    630         if (bucketOut != null && mEnumerationIndex < mSummary.size()) {
    631             mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry);
    632             fillBucketFromSummaryEntry(bucketOut);
    633             return true;
    634         }
    635         return false;
    636     }
    637 
    638     Bucket getSummaryAggregate() {
    639         if (mSummary == null) {
    640             return null;
    641         }
    642         Bucket bucket = new Bucket();
    643         if (mRecycledSummaryEntry == null) {
    644             mRecycledSummaryEntry = new android.net.NetworkStats.Entry();
    645         }
    646         mSummary.getTotal(mRecycledSummaryEntry);
    647         fillBucketFromSummaryEntry(bucket);
    648         return bucket;
    649     }
    650 
    651     /**
    652      * Getting the next item in a history enumeration.
    653      * @param bucketOut Next item will be set here.
    654      * @return true if a next item could be set.
    655      */
    656     private boolean getNextHistoryBucket(Bucket bucketOut) {
    657         if (bucketOut != null && mHistory != null) {
    658             if (mEnumerationIndex < mHistory.size()) {
    659                 mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++,
    660                         mRecycledHistoryEntry);
    661                 bucketOut.mUid = Bucket.convertUid(getUid());
    662                 bucketOut.mTag = Bucket.convertTag(mTag);
    663                 bucketOut.mState = mState;
    664                 bucketOut.mDefaultNetworkStatus = Bucket.DEFAULT_NETWORK_ALL;
    665                 bucketOut.mMetered = Bucket.METERED_ALL;
    666                 bucketOut.mRoaming = Bucket.ROAMING_ALL;
    667                 bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
    668                 bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart +
    669                         mRecycledHistoryEntry.bucketDuration;
    670                 bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes;
    671                 bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets;
    672                 bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes;
    673                 bucketOut.mTxPackets = mRecycledHistoryEntry.txPackets;
    674                 return true;
    675             } else if (hasNextUid()) {
    676                 stepHistory();
    677                 return getNextHistoryBucket(bucketOut);
    678             }
    679         }
    680         return false;
    681     }
    682 
    683     // ------------------ UID LOGIC------------------------
    684 
    685     private boolean isUidEnumeration() {
    686         return mUids != null;
    687     }
    688 
    689     private boolean hasNextUid() {
    690         return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length;
    691     }
    692 
    693     private int getUid() {
    694         // Check if uid enumeration.
    695         if (isUidEnumeration()) {
    696             if (mUidOrUidIndex < 0 || mUidOrUidIndex >= mUids.length) {
    697                 throw new IndexOutOfBoundsException(
    698                         "Index=" + mUidOrUidIndex + " mUids.length=" + mUids.length);
    699             }
    700             return mUids[mUidOrUidIndex];
    701         }
    702         // Single uid mode.
    703         return mUidOrUidIndex;
    704     }
    705 
    706     private void setSingleUidTagState(int uid, int tag, int state) {
    707         mUidOrUidIndex = uid;
    708         mTag = tag;
    709         mState = state;
    710     }
    711 
    712     private void stepUid() {
    713         if (mUids != null) {
    714             ++mUidOrUidIndex;
    715         }
    716     }
    717 }
    718