| /** |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy |
| * of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package android.app.usage; |
| |
| import android.annotation.IntDef; |
| import android.content.Context; |
| import android.net.INetworkStatsService; |
| import android.net.INetworkStatsSession; |
| import android.net.NetworkStatsHistory; |
| import android.net.NetworkTemplate; |
| import android.net.TrafficStats; |
| import android.os.RemoteException; |
| import android.util.IntArray; |
| import android.util.Log; |
| |
| import dalvik.system.CloseGuard; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| |
| /** |
| * Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects |
| * are returned as results to various queries in {@link NetworkStatsManager}. |
| */ |
| public final class NetworkStats implements AutoCloseable { |
| private final static String TAG = "NetworkStats"; |
| |
| private final CloseGuard mCloseGuard = CloseGuard.get(); |
| |
| /** |
| * Start timestamp of stats collected |
| */ |
| private final long mStartTimeStamp; |
| |
| /** |
| * End timestamp of stats collected |
| */ |
| private final long mEndTimeStamp; |
| |
| /** |
| * Non-null array indicates the query enumerates over uids. |
| */ |
| private int[] mUids; |
| |
| /** |
| * Index of the current uid in mUids when doing uid enumeration or a single uid value, |
| * depending on query type. |
| */ |
| private int mUidOrUidIndex; |
| |
| /** |
| * Tag id in case if was specified in the query. |
| */ |
| private int mTag = android.net.NetworkStats.TAG_NONE; |
| |
| /** |
| * State in case it was not specified in the query. |
| */ |
| private int mState = Bucket.STATE_ALL; |
| |
| /** |
| * The session while the query requires it, null if all the stats have been collected or close() |
| * has been called. |
| */ |
| private INetworkStatsSession mSession; |
| private NetworkTemplate mTemplate; |
| |
| /** |
| * Results of a summary query. |
| */ |
| private android.net.NetworkStats mSummary = null; |
| |
| /** |
| * Results of detail queries. |
| */ |
| private NetworkStatsHistory mHistory = null; |
| |
| /** |
| * Where we are in enumerating over the current result. |
| */ |
| private int mEnumerationIndex = 0; |
| |
| /** |
| * Recycling entry objects to prevent heap fragmentation. |
| */ |
| private android.net.NetworkStats.Entry mRecycledSummaryEntry = null; |
| private NetworkStatsHistory.Entry mRecycledHistoryEntry = null; |
| |
| /** @hide */ |
| NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp, |
| long endTimestamp, INetworkStatsService statsService) |
| throws RemoteException, SecurityException { |
| // Open network stats session |
| mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName()); |
| mCloseGuard.open("close"); |
| mTemplate = template; |
| mStartTimeStamp = startTimestamp; |
| mEndTimeStamp = endTimestamp; |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| if (mCloseGuard != null) { |
| mCloseGuard.warnIfOpen(); |
| } |
| close(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| // -------------------------BEGINNING OF PUBLIC API----------------------------------- |
| |
| /** |
| * Buckets are the smallest elements of a query result. As some dimensions of a result may be |
| * aggregated (e.g. time or state) some values may be equal across all buckets. |
| */ |
| public static class Bucket { |
| /** @hide */ |
| @IntDef(prefix = { "STATE_" }, value = { |
| STATE_ALL, |
| STATE_DEFAULT, |
| STATE_FOREGROUND |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface State {} |
| |
| /** |
| * Combined usage across all states. |
| */ |
| public static final int STATE_ALL = -1; |
| |
| /** |
| * Usage not accounted for in any other state. |
| */ |
| public static final int STATE_DEFAULT = 0x1; |
| |
| /** |
| * Foreground usage. |
| */ |
| public static final int STATE_FOREGROUND = 0x2; |
| |
| /** |
| * Special UID value for aggregate/unspecified. |
| */ |
| public static final int UID_ALL = android.net.NetworkStats.UID_ALL; |
| |
| /** |
| * Special UID value for removed apps. |
| */ |
| public static final int UID_REMOVED = TrafficStats.UID_REMOVED; |
| |
| /** |
| * Special UID value for data usage by tethering. |
| */ |
| public static final int UID_TETHERING = TrafficStats.UID_TETHERING; |
| |
| /** @hide */ |
| @IntDef(prefix = { "METERED_" }, value = { |
| METERED_ALL, |
| METERED_NO, |
| METERED_YES |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface Metered {} |
| |
| /** |
| * Combined usage across all metered states. Covers metered and unmetered usage. |
| */ |
| public static final int METERED_ALL = -1; |
| |
| /** |
| * Usage that occurs on an unmetered network. |
| */ |
| public static final int METERED_NO = 0x1; |
| |
| /** |
| * Usage that occurs on a metered network. |
| * |
| * <p>A network is classified as metered when the user is sensitive to heavy data usage on |
| * that connection. |
| */ |
| public static final int METERED_YES = 0x2; |
| |
| /** @hide */ |
| @IntDef(prefix = { "ROAMING_" }, value = { |
| ROAMING_ALL, |
| ROAMING_NO, |
| ROAMING_YES |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface Roaming {} |
| |
| /** |
| * Combined usage across all roaming states. Covers both roaming and non-roaming usage. |
| */ |
| public static final int ROAMING_ALL = -1; |
| |
| /** |
| * Usage that occurs on a home, non-roaming network. |
| * |
| * <p>Any cellular usage in this bucket was incurred while the device was connected to a |
| * tower owned or operated by the user's wireless carrier, or a tower that the user's |
| * wireless carrier has indicated should be treated as a home network regardless. |
| * |
| * <p>This is also the default value for network types that do not support roaming. |
| */ |
| public static final int ROAMING_NO = 0x1; |
| |
| /** |
| * Usage that occurs on a roaming network. |
| * |
| * <p>Any cellular usage in this bucket as incurred while the device was roaming on another |
| * carrier's network, for which additional charges may apply. |
| */ |
| public static final int ROAMING_YES = 0x2; |
| |
| /** @hide */ |
| @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = { |
| DEFAULT_NETWORK_ALL, |
| DEFAULT_NETWORK_NO, |
| DEFAULT_NETWORK_YES |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface DefaultNetworkStatus {} |
| |
| /** |
| * Combined usage for this network regardless of default network status. |
| */ |
| public static final int DEFAULT_NETWORK_ALL = -1; |
| |
| /** |
| * Usage that occurs while this network is not a default network. |
| * |
| * <p>This implies that the app responsible for this usage requested that it occur on a |
| * specific network different from the one(s) the system would have selected for it. |
| */ |
| public static final int DEFAULT_NETWORK_NO = 0x1; |
| |
| /** |
| * Usage that occurs while this network is a default network. |
| * |
| * <p>This implies that the app either did not select a specific network for this usage, |
| * or it selected a network that the system could have selected for app traffic. |
| */ |
| public static final int DEFAULT_NETWORK_YES = 0x2; |
| |
| /** |
| * Special TAG value for total data across all tags |
| */ |
| public static final int TAG_NONE = android.net.NetworkStats.TAG_NONE; |
| |
| private int mUid; |
| private int mTag; |
| private int mState; |
| private int mDefaultNetworkStatus; |
| private int mMetered; |
| private int mRoaming; |
| private long mBeginTimeStamp; |
| private long mEndTimeStamp; |
| private long mRxBytes; |
| private long mRxPackets; |
| private long mTxBytes; |
| private long mTxPackets; |
| |
| private static int convertSet(@State int state) { |
| switch (state) { |
| case STATE_ALL: return android.net.NetworkStats.SET_ALL; |
| case STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT; |
| case STATE_FOREGROUND: return android.net.NetworkStats.SET_FOREGROUND; |
| } |
| return 0; |
| } |
| |
| private static @State int convertState(int networkStatsSet) { |
| switch (networkStatsSet) { |
| case android.net.NetworkStats.SET_ALL : return STATE_ALL; |
| case android.net.NetworkStats.SET_DEFAULT : return STATE_DEFAULT; |
| case android.net.NetworkStats.SET_FOREGROUND : return STATE_FOREGROUND; |
| } |
| return 0; |
| } |
| |
| private static int convertUid(int uid) { |
| switch (uid) { |
| case TrafficStats.UID_REMOVED: return UID_REMOVED; |
| case TrafficStats.UID_TETHERING: return UID_TETHERING; |
| } |
| return uid; |
| } |
| |
| private static int convertTag(int tag) { |
| switch (tag) { |
| case android.net.NetworkStats.TAG_NONE: return TAG_NONE; |
| } |
| return tag; |
| } |
| |
| private static @Metered int convertMetered(int metered) { |
| switch (metered) { |
| case android.net.NetworkStats.METERED_ALL : return METERED_ALL; |
| case android.net.NetworkStats.METERED_NO: return METERED_NO; |
| case android.net.NetworkStats.METERED_YES: return METERED_YES; |
| } |
| return 0; |
| } |
| |
| private static @Roaming int convertRoaming(int roaming) { |
| switch (roaming) { |
| case android.net.NetworkStats.ROAMING_ALL : return ROAMING_ALL; |
| case android.net.NetworkStats.ROAMING_NO: return ROAMING_NO; |
| case android.net.NetworkStats.ROAMING_YES: return ROAMING_YES; |
| } |
| return 0; |
| } |
| |
| private static @DefaultNetworkStatus int convertDefaultNetworkStatus( |
| int defaultNetworkStatus) { |
| switch (defaultNetworkStatus) { |
| case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL; |
| case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO; |
| case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES; |
| } |
| return 0; |
| } |
| |
| public Bucket() { |
| } |
| |
| /** |
| * Key of the bucket. Usually an app uid or one of the following special values:<p /> |
| * <ul> |
| * <li>{@link #UID_REMOVED}</li> |
| * <li>{@link #UID_TETHERING}</li> |
| * <li>{@link android.os.Process#SYSTEM_UID}</li> |
| * </ul> |
| * @return Bucket key. |
| */ |
| public int getUid() { |
| return mUid; |
| } |
| |
| /** |
| * Tag of the bucket.<p /> |
| * @return Bucket tag. |
| */ |
| public int getTag() { |
| return mTag; |
| } |
| |
| /** |
| * Usage state. One of the following values:<p/> |
| * <ul> |
| * <li>{@link #STATE_ALL}</li> |
| * <li>{@link #STATE_DEFAULT}</li> |
| * <li>{@link #STATE_FOREGROUND}</li> |
| * </ul> |
| * @return Usage state. |
| */ |
| public @State int getState() { |
| return mState; |
| } |
| |
| /** |
| * Metered state. One of the following values:<p/> |
| * <ul> |
| * <li>{@link #METERED_ALL}</li> |
| * <li>{@link #METERED_NO}</li> |
| * <li>{@link #METERED_YES}</li> |
| * </ul> |
| * <p>A network is classified as metered when the user is sensitive to heavy data usage on |
| * that connection. Apps may warn before using these networks for large downloads. The |
| * metered state can be set by the user within data usage network restrictions. |
| */ |
| public @Metered int getMetered() { |
| return mMetered; |
| } |
| |
| /** |
| * Roaming state. One of the following values:<p/> |
| * <ul> |
| * <li>{@link #ROAMING_ALL}</li> |
| * <li>{@link #ROAMING_NO}</li> |
| * <li>{@link #ROAMING_YES}</li> |
| * </ul> |
| */ |
| public @Roaming int getRoaming() { |
| return mRoaming; |
| } |
| |
| /** |
| * Default network status. One of the following values:<p/> |
| * <ul> |
| * <li>{@link #DEFAULT_NETWORK_ALL}</li> |
| * <li>{@link #DEFAULT_NETWORK_NO}</li> |
| * <li>{@link #DEFAULT_NETWORK_YES}</li> |
| * </ul> |
| */ |
| public @DefaultNetworkStatus int getDefaultNetworkStatus() { |
| return mDefaultNetworkStatus; |
| } |
| |
| /** |
| * Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see |
| * {@link java.lang.System#currentTimeMillis}. |
| * @return Start of interval. |
| */ |
| public long getStartTimeStamp() { |
| return mBeginTimeStamp; |
| } |
| |
| /** |
| * End timestamp of the bucket's time interval. Defined in terms of "Unix time", see |
| * {@link java.lang.System#currentTimeMillis}. |
| * @return End of interval. |
| */ |
| public long getEndTimeStamp() { |
| return mEndTimeStamp; |
| } |
| |
| /** |
| * Number of bytes received during the bucket's time interval. Statistics are measured at |
| * the network layer, so they include both TCP and UDP usage. |
| * @return Number of bytes. |
| */ |
| public long getRxBytes() { |
| return mRxBytes; |
| } |
| |
| /** |
| * Number of bytes transmitted during the bucket's time interval. Statistics are measured at |
| * the network layer, so they include both TCP and UDP usage. |
| * @return Number of bytes. |
| */ |
| public long getTxBytes() { |
| return mTxBytes; |
| } |
| |
| /** |
| * Number of packets received during the bucket's time interval. Statistics are measured at |
| * the network layer, so they include both TCP and UDP usage. |
| * @return Number of packets. |
| */ |
| public long getRxPackets() { |
| return mRxPackets; |
| } |
| |
| /** |
| * Number of packets transmitted during the bucket's time interval. Statistics are measured |
| * at the network layer, so they include both TCP and UDP usage. |
| * @return Number of packets. |
| */ |
| public long getTxPackets() { |
| return mTxPackets; |
| } |
| } |
| |
| /** |
| * Fills the recycled bucket with data of the next bin in the enumeration. |
| * @param bucketOut Bucket to be filled with data. |
| * @return true if successfully filled the bucket, false otherwise. |
| */ |
| public boolean getNextBucket(Bucket bucketOut) { |
| if (mSummary != null) { |
| return getNextSummaryBucket(bucketOut); |
| } else { |
| return getNextHistoryBucket(bucketOut); |
| } |
| } |
| |
| /** |
| * Check if it is possible to ask for a next bucket in the enumeration. |
| * @return true if there is at least one more bucket. |
| */ |
| public boolean hasNextBucket() { |
| if (mSummary != null) { |
| return mEnumerationIndex < mSummary.size(); |
| } else if (mHistory != null) { |
| return mEnumerationIndex < mHistory.size() |
| || hasNextUid(); |
| } |
| return false; |
| } |
| |
| /** |
| * Closes the enumeration. Call this method before this object gets out of scope. |
| */ |
| @Override |
| public void close() { |
| if (mSession != null) { |
| try { |
| mSession.close(); |
| } catch (RemoteException e) { |
| Log.w(TAG, e); |
| // Otherwise, meh |
| } |
| } |
| mSession = null; |
| if (mCloseGuard != null) { |
| mCloseGuard.close(); |
| } |
| } |
| |
| // -------------------------END OF PUBLIC API----------------------------------- |
| |
| /** |
| * Collects device summary results into a Bucket. |
| * @throws RemoteException |
| */ |
| Bucket getDeviceSummaryForNetwork() throws RemoteException { |
| mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, mStartTimeStamp, mEndTimeStamp); |
| |
| // Setting enumeration index beyond end to avoid accidental enumeration over data that does |
| // not belong to the calling user. |
| mEnumerationIndex = mSummary.size(); |
| |
| return getSummaryAggregate(); |
| } |
| |
| /** |
| * Collects summary results and sets summary enumeration mode. |
| * @throws RemoteException |
| */ |
| void startSummaryEnumeration() throws RemoteException { |
| mSummary = mSession.getSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp, |
| false /* includeTags */); |
| mEnumerationIndex = 0; |
| } |
| |
| /** |
| * Collects history results for uid and resets history enumeration index. |
| */ |
| void startHistoryEnumeration(int uid, int tag, int state) { |
| mHistory = null; |
| try { |
| mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid, |
| Bucket.convertSet(state), tag, NetworkStatsHistory.FIELD_ALL, |
| mStartTimeStamp, mEndTimeStamp); |
| setSingleUidTagState(uid, tag, state); |
| } catch (RemoteException e) { |
| Log.w(TAG, e); |
| // Leaving mHistory null |
| } |
| mEnumerationIndex = 0; |
| } |
| |
| /** |
| * Starts uid enumeration for current user. |
| * @throws RemoteException |
| */ |
| void startUserUidEnumeration() throws RemoteException { |
| // TODO: getRelevantUids should be sensitive to time interval. When that's done, |
| // the filtering logic below can be removed. |
| int[] uids = mSession.getRelevantUids(); |
| // Filtering of uids with empty history. |
| IntArray filteredUids = new IntArray(uids.length); |
| for (int uid : uids) { |
| try { |
| NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid, |
| android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE, |
| NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); |
| if (history != null && history.size() > 0) { |
| filteredUids.add(uid); |
| } |
| } catch (RemoteException e) { |
| Log.w(TAG, "Error while getting history of uid " + uid, e); |
| } |
| } |
| mUids = filteredUids.toArray(); |
| mUidOrUidIndex = -1; |
| stepHistory(); |
| } |
| |
| /** |
| * Steps to next uid in enumeration and collects history for that. |
| */ |
| private void stepHistory(){ |
| if (hasNextUid()) { |
| stepUid(); |
| mHistory = null; |
| try { |
| mHistory = mSession.getHistoryIntervalForUid(mTemplate, getUid(), |
| android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE, |
| NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp); |
| } catch (RemoteException e) { |
| Log.w(TAG, e); |
| // Leaving mHistory null |
| } |
| mEnumerationIndex = 0; |
| } |
| } |
| |
| private void fillBucketFromSummaryEntry(Bucket bucketOut) { |
| bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid); |
| bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag); |
| bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set); |
| bucketOut.mDefaultNetworkStatus = Bucket.convertDefaultNetworkStatus( |
| mRecycledSummaryEntry.defaultNetwork); |
| bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered); |
| bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming); |
| bucketOut.mBeginTimeStamp = mStartTimeStamp; |
| bucketOut.mEndTimeStamp = mEndTimeStamp; |
| bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes; |
| bucketOut.mRxPackets = mRecycledSummaryEntry.rxPackets; |
| bucketOut.mTxBytes = mRecycledSummaryEntry.txBytes; |
| bucketOut.mTxPackets = mRecycledSummaryEntry.txPackets; |
| } |
| |
| /** |
| * Getting the next item in summary enumeration. |
| * @param bucketOut Next item will be set here. |
| * @return true if a next item could be set. |
| */ |
| private boolean getNextSummaryBucket(Bucket bucketOut) { |
| if (bucketOut != null && mEnumerationIndex < mSummary.size()) { |
| mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry); |
| fillBucketFromSummaryEntry(bucketOut); |
| return true; |
| } |
| return false; |
| } |
| |
| Bucket getSummaryAggregate() { |
| if (mSummary == null) { |
| return null; |
| } |
| Bucket bucket = new Bucket(); |
| if (mRecycledSummaryEntry == null) { |
| mRecycledSummaryEntry = new android.net.NetworkStats.Entry(); |
| } |
| mSummary.getTotal(mRecycledSummaryEntry); |
| fillBucketFromSummaryEntry(bucket); |
| return bucket; |
| } |
| |
| /** |
| * Getting the next item in a history enumeration. |
| * @param bucketOut Next item will be set here. |
| * @return true if a next item could be set. |
| */ |
| private boolean getNextHistoryBucket(Bucket bucketOut) { |
| if (bucketOut != null && mHistory != null) { |
| if (mEnumerationIndex < mHistory.size()) { |
| mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++, |
| mRecycledHistoryEntry); |
| bucketOut.mUid = Bucket.convertUid(getUid()); |
| bucketOut.mTag = Bucket.convertTag(mTag); |
| bucketOut.mState = mState; |
| bucketOut.mDefaultNetworkStatus = Bucket.DEFAULT_NETWORK_ALL; |
| bucketOut.mMetered = Bucket.METERED_ALL; |
| bucketOut.mRoaming = Bucket.ROAMING_ALL; |
| bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart; |
| bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart + |
| mRecycledHistoryEntry.bucketDuration; |
| bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes; |
| bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets; |
| bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes; |
| bucketOut.mTxPackets = mRecycledHistoryEntry.txPackets; |
| return true; |
| } else if (hasNextUid()) { |
| stepHistory(); |
| return getNextHistoryBucket(bucketOut); |
| } |
| } |
| return false; |
| } |
| |
| // ------------------ UID LOGIC------------------------ |
| |
| private boolean isUidEnumeration() { |
| return mUids != null; |
| } |
| |
| private boolean hasNextUid() { |
| return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length; |
| } |
| |
| private int getUid() { |
| // Check if uid enumeration. |
| if (isUidEnumeration()) { |
| if (mUidOrUidIndex < 0 || mUidOrUidIndex >= mUids.length) { |
| throw new IndexOutOfBoundsException( |
| "Index=" + mUidOrUidIndex + " mUids.length=" + mUids.length); |
| } |
| return mUids[mUidOrUidIndex]; |
| } |
| // Single uid mode. |
| return mUidOrUidIndex; |
| } |
| |
| private void setSingleUidTagState(int uid, int tag, int state) { |
| mUidOrUidIndex = uid; |
| mTag = tag; |
| mState = state; |
| } |
| |
| private void stepUid() { |
| if (mUids != null) { |
| ++mUidOrUidIndex; |
| } |
| } |
| } |