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 */
NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp, long endTimestamp, INetworkStatsService statsService)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
finalize()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 
convertSet(@tate int state)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 
convertState(int networkStatsSet)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 
convertUid(int uid)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 
convertTag(int tag)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 
convertMetered(int metered)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 
convertRoaming(int roaming)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 
convertDefaultNetworkStatus( int defaultNetworkStatus)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 
Bucket()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          */
getUid()354         public int getUid() {
355             return mUid;
356         }
357 
358         /**
359          * Tag of the bucket.<p />
360          * @return Bucket tag.
361          */
getTag()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          */
getState()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          */
getMetered()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          */
getRoaming()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          */
getDefaultNetworkStatus()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          */
getStartTimeStamp()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          */
getEndTimeStamp()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          */
getRxBytes()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          */
getTxBytes()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          */
getRxPackets()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          */
getTxPackets()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      */
getNextBucket(Bucket bucketOut)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      */
hasNextBucket()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
close()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      */
getDeviceSummaryForNetwork()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      */
startSummaryEnumeration()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      */
startHistoryEnumeration(int uid, int tag, int state)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      */
startUserUidEnumeration()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      */
stepHistory()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 
fillBucketFromSummaryEntry(Bucket bucketOut)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      */
getNextSummaryBucket(Bucket bucketOut)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 
getSummaryAggregate()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      */
getNextHistoryBucket(Bucket bucketOut)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 
isUidEnumeration()685     private boolean isUidEnumeration() {
686         return mUids != null;
687     }
688 
hasNextUid()689     private boolean hasNextUid() {
690         return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length;
691     }
692 
getUid()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 
setSingleUidTagState(int uid, int tag, int state)706     private void setSingleUidTagState(int uid, int tag, int state) {
707         mUidOrUidIndex = uid;
708         mTag = tag;
709         mState = state;
710     }
711 
stepUid()712     private void stepUid() {
713         if (mUids != null) {
714             ++mUidOrUidIndex;
715         }
716     }
717 }
718