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