/**
* 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.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.os.ServiceManager;
import android.util.Log;
import dalvik.system.CloseGuard;
/**
* 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;
/**
* 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, long startTimestamp,
long endTimestamp) throws RemoteException, SecurityException {
final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
// Open network stats session
mSession = statsService.openSessionForUsageStats(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 {
/**
* Combined usage across all other states.
*/
public static final int STATE_ALL = -1;
/**
* Usage not accounted in any other states.
*/
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;
private int mUid;
private int mState;
private long mBeginTimeStamp;
private long mEndTimeStamp;
private long mRxBytes;
private long mRxPackets;
private long mTxBytes;
private long mTxPackets;
private static 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;
}
public Bucket() {
}
/**
* Key of the bucket. Usually an app uid or one of the following special values:
*
* - {@link #UID_REMOVED}
* - {@link #UID_TETHERING}
* - {@link android.os.Process#SYSTEM_UID}
*
* @return Bucket key.
*/
public int getUid() {
return mUid;
}
/**
* Usage state. One of the following values:
*
* - {@link #STATE_ALL}
* - {@link #STATE_DEFAULT}
* - {@link #STATE_FOREGROUND}
*
* @return Usage state.
*/
public int getState() {
return mState;
}
/**
* 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);
mEnumerationIndex = 0;
}
/**
* Collects history results for uid and resets history enumeration index.
*/
void startHistoryEnumeration(int uid) {
mHistory = null;
try {
mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid,
android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE,
NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
setSingleUid(uid);
} catch (RemoteException e) {
Log.w(TAG, e);
// Leaving mHistory null
}
mEnumerationIndex = 0;
}
/**
* Starts uid enumeration for current user.
* @throws RemoteException
*/
void startUserUidEnumeration() throws RemoteException {
setUidEnumeration(mSession.getRelevantUids());
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.mState = Bucket.convertState(mRecycledSummaryEntry.set);
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.mState = Bucket.STATE_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 setSingleUid(int uid) {
mUidOrUidIndex = uid;
}
private void setUidEnumeration(int[] uids) {
mUids = uids;
mUidOrUidIndex = -1;
}
private void stepUid() {
if (mUids != null) {
++mUidOrUidIndex;
}
}
}