Data Usage public API
Added new API consisting of android.app.usage.NetworkUsageManager and android.app.usage.NetworkUsageStats. Through them data usage on a network interface can be programmatically queried. Both summary and details are available. Bug: 19208876 Change-Id: I0e0c4b37ae23ad1e589d4b0c955b93f28ba4333e
This commit is contained in:
233
core/java/android/app/usage/NetworkStatsManager.java
Normal file
233
core/java/android/app/usage/NetworkStatsManager.java
Normal file
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* 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.app.usage.NetworkUsageStats.Bucket;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkIdentity;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Provides access to network usage history and statistics. Usage data is collected in
|
||||
* discrete bins of time called 'Buckets'. See {@link NetworkUsageStats.Bucket} for details.
|
||||
* <p />
|
||||
* Queries can define a time interval in the form of start and end timestamps (Long.MIN_VALUE and
|
||||
* Long.MAX_VALUE can be used to simulate open ended intervals). All queries (except
|
||||
* {@link #querySummaryForDevice}) collect only network usage of apps belonging to the same user
|
||||
* as the client. In addition tethering usage, usage by removed users and apps, and usage by system
|
||||
* is also included in the results.
|
||||
* <h3>
|
||||
* Summary queries
|
||||
* </h3>
|
||||
* These queries aggregate network usage across the whole interval. Therefore there will be only one
|
||||
* bucket for a particular key and state combination. In case of the user-wide and device-wide
|
||||
* summaries a single bucket containing the totalised network usage is returned.
|
||||
* <h3>
|
||||
* History queries
|
||||
* </h3>
|
||||
* These queries do not aggregate over time but do aggregate over state. Therefore there can be
|
||||
* multiple buckets for a particular key but all Bucket's state is going to be
|
||||
* {@link NetworkUsageStats.Bucket#STATE_ALL}.
|
||||
* <p />
|
||||
* <b>NOTE:</b> This API requires the permission
|
||||
* {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, which is a system-level permission and
|
||||
* will not be granted to third-party apps. However, declaring the permission implies intention to
|
||||
* use the API and the user of the device can grant permission through the Settings application.
|
||||
* Profile owner apps are automatically granted permission to query data on the profile they manage
|
||||
* (that is, for any query except {@link #querySummaryForDevice}). Device owner apps likewise get
|
||||
* access to usage data of the primary user.
|
||||
*/
|
||||
public class NetworkStatsManager {
|
||||
private final static String TAG = "NetworkStatsManager";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
public NetworkStatsManager(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
/**
|
||||
* Query network usage statistics summaries. Result is summarised data usage for the whole
|
||||
* device. Result is a single Bucket aggregated over time, state and uid.
|
||||
*
|
||||
* @param networkType As defined in {@link ConnectivityManager}, e.g.
|
||||
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
|
||||
* etc.
|
||||
* @param subscriberId If applicable, the subscriber id of the network interface.
|
||||
* @param startTime Start of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @param endTime End of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @return Bucket object or null if permissions are insufficient or error happened during
|
||||
* statistics collection.
|
||||
*/
|
||||
public Bucket querySummaryForDevice(int networkType, String subscriberId,
|
||||
long startTime, long endTime) throws SecurityException, RemoteException {
|
||||
NetworkTemplate template = createTemplate(networkType, subscriberId);
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bucket bucket = null;
|
||||
NetworkUsageStats stats = new NetworkUsageStats(mContext, template, startTime, endTime);
|
||||
bucket = stats.getDeviceSummaryForNetwork(startTime, endTime);
|
||||
|
||||
stats.close();
|
||||
return bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query network usage statistics summaries. Result is summarised data usage for all uids
|
||||
* belonging to calling user. Result is a single Bucket aggregated over time, state and uid.
|
||||
*
|
||||
* @param networkType As defined in {@link ConnectivityManager}, e.g.
|
||||
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
|
||||
* etc.
|
||||
* @param subscriberId If applicable, the subscriber id of the network interface.
|
||||
* @param startTime Start of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @param endTime End of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @return Bucket object or null if permissions are insufficient or error happened during
|
||||
* statistics collection.
|
||||
*/
|
||||
public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime,
|
||||
long endTime) throws SecurityException, RemoteException {
|
||||
NetworkTemplate template = createTemplate(networkType, subscriberId);
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NetworkUsageStats stats;
|
||||
stats = new NetworkUsageStats(mContext, template, startTime, endTime);
|
||||
stats.startSummaryEnumeration(startTime, endTime);
|
||||
|
||||
stats.close();
|
||||
return stats.getSummaryAggregate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query network usage statistics summaries. Result filtered to include only uids belonging to
|
||||
* calling user. Result is aggregated over time, hence all buckets will have the same start and
|
||||
* end timestamps. Not aggregated over state or uid.
|
||||
*
|
||||
* @param networkType As defined in {@link ConnectivityManager}, e.g.
|
||||
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
|
||||
* etc.
|
||||
* @param subscriberId If applicable, the subscriber id of the network interface.
|
||||
* @param startTime Start of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @param endTime End of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @return Statistics object or null if permissions are insufficient or error happened during
|
||||
* statistics collection.
|
||||
*/
|
||||
public NetworkUsageStats querySummary(int networkType, String subscriberId, long startTime,
|
||||
long endTime) throws SecurityException, RemoteException {
|
||||
NetworkTemplate template = createTemplate(networkType, subscriberId);
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NetworkUsageStats result;
|
||||
result = new NetworkUsageStats(mContext, template, startTime, endTime);
|
||||
result.startSummaryEnumeration(startTime, endTime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query network usage statistics details. Only usable for uids belonging to calling user.
|
||||
* Result is aggregated over state but not aggregated over time.
|
||||
*
|
||||
* @param networkType As defined in {@link ConnectivityManager}, e.g.
|
||||
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
|
||||
* etc.
|
||||
* @param subscriberId If applicable, the subscriber id of the network interface.
|
||||
* @param startTime Start of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @param endTime End of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @param uid UID of app
|
||||
* @return Statistics object or null if permissions are insufficient or error happened during
|
||||
* statistics collection.
|
||||
*/
|
||||
public NetworkUsageStats queryDetailsForUid(int networkType, String subscriberId,
|
||||
long startTime, long endTime, int uid) throws SecurityException, RemoteException {
|
||||
NetworkTemplate template = createTemplate(networkType, subscriberId);
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NetworkUsageStats result;
|
||||
result = new NetworkUsageStats(mContext, template, startTime, endTime);
|
||||
result.startHistoryEnumeration(uid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query network usage statistics details. Result filtered to include only uids belonging to
|
||||
* calling user. Result is aggregated over state but not aggregated over time or uid.
|
||||
*
|
||||
* @param networkType As defined in {@link ConnectivityManager}, e.g.
|
||||
* {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
|
||||
* etc.
|
||||
* @param subscriberId If applicable, the subscriber id of the network interface.
|
||||
* @param startTime Start of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @param endTime End of period. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @return Statistics object or null if permissions are insufficient or error happened during
|
||||
* statistics collection.
|
||||
*/
|
||||
public NetworkUsageStats queryDetails(int networkType, String subscriberId, long startTime,
|
||||
long endTime) throws SecurityException, RemoteException {
|
||||
NetworkTemplate template = createTemplate(networkType, subscriberId);
|
||||
if (template == null) {
|
||||
return null;
|
||||
}
|
||||
NetworkUsageStats result;
|
||||
result = new NetworkUsageStats(mContext, template, startTime, endTime);
|
||||
result.startUserUidEnumeration();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
|
||||
NetworkTemplate template = null;
|
||||
switch (networkType) {
|
||||
case ConnectivityManager.TYPE_MOBILE: {
|
||||
template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
|
||||
} break;
|
||||
case ConnectivityManager.TYPE_WIFI: {
|
||||
template = NetworkTemplate.buildTemplateWifiWildcard();
|
||||
} break;
|
||||
default: {
|
||||
Log.w(TAG, "Cannot create template for network type " + networkType
|
||||
+ ", subscriberId '" + NetworkIdentity.scrubSubscriberId(subscriberId) +
|
||||
"'.");
|
||||
}
|
||||
}
|
||||
return template;
|
||||
}
|
||||
}
|
||||
479
core/java/android/app/usage/NetworkUsageStats.java
Normal file
479
core/java/android/app/usage/NetworkUsageStats.java
Normal file
@@ -0,0 +1,479 @@
|
||||
/**
|
||||
* 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.NetworkStats;
|
||||
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. NetworkUsageStats objects
|
||||
* are returned as results to various queries in {@link NetworkStatsManager}.
|
||||
*/
|
||||
public final class NetworkUsageStats implements AutoCloseable {
|
||||
private final static String TAG = "NetworkUsageStats";
|
||||
|
||||
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 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 NetworkStats.Entry mRecycledSummaryEntry = null;
|
||||
private NetworkStatsHistory.Entry mRecycledHistoryEntry = null;
|
||||
|
||||
/** @hide */
|
||||
NetworkUsageStats(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 removed apps.
|
||||
*/
|
||||
public static final int UID_REMOVED = -4;
|
||||
|
||||
/**
|
||||
* Special UID value for data usage by tethering.
|
||||
*/
|
||||
public static final int UID_TETHERING = -5;
|
||||
|
||||
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 NetworkStats.SET_ALL : return STATE_ALL;
|
||||
case NetworkStats.SET_DEFAULT : return STATE_DEFAULT;
|
||||
case 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:<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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
* @param startTime
|
||||
* @param endTime
|
||||
* @throws RemoteException
|
||||
*/
|
||||
Bucket getDeviceSummaryForNetwork(long startTime, long endTime) throws RemoteException {
|
||||
mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, startTime, endTime);
|
||||
|
||||
// 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.
|
||||
* @param startTime
|
||||
* @param endTime
|
||||
* @throws RemoteException
|
||||
*/
|
||||
void startSummaryEnumeration(long startTime, long endTime) throws RemoteException {
|
||||
mSummary = mSession.getSummaryForAllUid(mTemplate, startTime, endTime, false);
|
||||
|
||||
mEnumerationIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects history results for uid and resets history enumeration index.
|
||||
*/
|
||||
void startHistoryEnumeration(int uid) {
|
||||
mHistory = null;
|
||||
try {
|
||||
mHistory = mSession.getHistoryForUid(mTemplate, uid, NetworkStats.SET_ALL,
|
||||
NetworkStats.TAG_NONE, NetworkStatsHistory.FIELD_ALL);
|
||||
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.getHistoryForUid(mTemplate, getUid(), NetworkStats.SET_ALL,
|
||||
NetworkStats.TAG_NONE, NetworkStatsHistory.FIELD_ALL);
|
||||
} 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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,14 @@ interface INetworkStatsService {
|
||||
/** Start a statistics query session. */
|
||||
INetworkStatsSession openSession();
|
||||
|
||||
/** Start a statistics query session. If calling package is profile or device owner then it is
|
||||
* granted automatic access if apiLevel is NetworkStatsManager.API_LEVEL_DPC_ALLOWED. If
|
||||
* apiLevel is at least NetworkStatsManager.API_LEVEL_REQUIRES_PACKAGE_USAGE_STATS then
|
||||
* PACKAGE_USAGE_STATS permission is always checked. If PACKAGE_USAGE_STATS is not granted
|
||||
* READ_NETWORK_USAGE_STATS is checked for.
|
||||
*/
|
||||
INetworkStatsSession openSessionForUsageStats(String callingPackage);
|
||||
|
||||
/** Return network layer usage total for traffic that matches template. */
|
||||
long getNetworkTotalBytes(in NetworkTemplate template, long start, long end);
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ import android.net.NetworkTemplate;
|
||||
/** {@hide} */
|
||||
interface INetworkStatsSession {
|
||||
|
||||
/** Return device aggregated network layer usage summary for traffic that matches template. */
|
||||
NetworkStats getDeviceSummaryForNetwork(in NetworkTemplate template, long start, long end);
|
||||
|
||||
/** Return network layer usage summary for traffic that matches template. */
|
||||
NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
|
||||
/** Return historical network layer stats for traffic that matches template. */
|
||||
@@ -33,6 +36,9 @@ interface INetworkStatsSession {
|
||||
/** Return historical network layer stats for specific UID traffic that matches template. */
|
||||
NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields);
|
||||
|
||||
/** Return array of uids that have stats and are accessible to the calling user */
|
||||
int[] getRelevantUids();
|
||||
|
||||
void close();
|
||||
|
||||
}
|
||||
|
||||
@@ -22,21 +22,28 @@ import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
import static android.net.TrafficStats.UID_REMOVED;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkIdentity;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.TrafficStats;
|
||||
import android.os.Binder;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.IntArray;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.FileRotator;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
import com.google.android.collect.Maps;
|
||||
|
||||
@@ -129,6 +136,23 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public int[] getRelevantUids() {
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
IntArray uids = new IntArray();
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
if (isAccessibleToUser(key.uid, callerUid)) {
|
||||
int j = uids.binarySearch(key.uid);
|
||||
|
||||
if (j < 0) {
|
||||
j = ~j;
|
||||
uids.add(j, key.uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return uids.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine all {@link NetworkStatsHistory} in this collection which match
|
||||
* the requested parameters.
|
||||
@@ -144,8 +168,18 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
*/
|
||||
public NetworkStatsHistory getHistory(
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) {
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
if (!isAccessibleToUser(uid, callerUid)) {
|
||||
throw new SecurityException("Network stats history of uid " + uid
|
||||
+ " is forbidden for caller " + callerUid);
|
||||
}
|
||||
|
||||
final NetworkStatsHistory combined = new NetworkStatsHistory(
|
||||
mBucketDuration, estimateBuckets(), fields);
|
||||
mBucketDuration, start == end ? 1 : estimateBuckets(), fields);
|
||||
|
||||
// shortcut when we know stats will be empty
|
||||
if (start == end) return combined;
|
||||
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
final boolean setMatches = set == SET_ALL || key.set == set;
|
||||
@@ -166,15 +200,16 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
final NetworkStats stats = new NetworkStats(end - start, 24);
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
NetworkStatsHistory.Entry historyEntry = null;
|
||||
|
||||
// shortcut when we know stats will be empty
|
||||
if (start == end) return stats;
|
||||
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
NetworkStatsHistory.Entry historyEntry = null;
|
||||
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
if (templateMatches(template, key.ident)) {
|
||||
if (templateMatches(template, key.ident) && isAccessibleToUser(key.uid, callerUid)) {
|
||||
final NetworkStatsHistory value = mStats.valueAt(i);
|
||||
historyEntry = value.getValues(start, end, now, historyEntry);
|
||||
|
||||
@@ -534,6 +569,12 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAccessibleToUser(int uid, int callerUid) {
|
||||
return callerUid == android.os.Process.SYSTEM_UID ||
|
||||
uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED || uid == UID_TETHERING
|
||||
|| UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
|
||||
* in the given {@link NetworkIdentitySet}.
|
||||
|
||||
@@ -62,9 +62,13 @@ import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
|
||||
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
|
||||
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IAlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.admin.DeviceAdminInfo;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -93,7 +97,9 @@ import android.os.HandlerThread;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
@@ -116,6 +122,7 @@ import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.FileRotator;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.EventLogTags;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.connectivity.Tethering;
|
||||
|
||||
import java.io.File;
|
||||
@@ -429,7 +436,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
|
||||
@Override
|
||||
public INetworkStatsSession openSession() {
|
||||
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
|
||||
return openSessionForUsageStats(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public INetworkStatsSession openSessionForUsageStats(final String callingPackage) {
|
||||
assertBandwidthControlEnabled();
|
||||
|
||||
// return an IBinder which holds strong references to any loaded stats
|
||||
@@ -438,6 +449,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
return new INetworkStatsSession.Stub() {
|
||||
private NetworkStatsCollection mUidComplete;
|
||||
private NetworkStatsCollection mUidTagComplete;
|
||||
private String mCallingPackage = callingPackage;
|
||||
|
||||
private NetworkStatsCollection getUidComplete() {
|
||||
synchronized (mStatsLock) {
|
||||
@@ -457,9 +469,30 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getRelevantUids() {
|
||||
enforcePermissionForManagedAdmin(mCallingPackage);
|
||||
return getUidComplete().getRelevantUids();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkStats getDeviceSummaryForNetwork(NetworkTemplate template, long start,
|
||||
long end) {
|
||||
enforcePermission(mCallingPackage);
|
||||
NetworkStats result = new NetworkStats(end - start, 1);
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
result.combineAllValues(internalGetSummaryForNetwork(template, start, end));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkStats getSummaryForNetwork(
|
||||
NetworkTemplate template, long start, long end) {
|
||||
enforcePermission(mCallingPackage);
|
||||
return internalGetSummaryForNetwork(template, start, end);
|
||||
}
|
||||
|
||||
@@ -471,6 +504,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
@Override
|
||||
public NetworkStats getSummaryForAllUid(
|
||||
NetworkTemplate template, long start, long end, boolean includeTags) {
|
||||
enforcePermissionForManagedAdmin(mCallingPackage);
|
||||
final NetworkStats stats = getUidComplete().getSummary(template, start, end);
|
||||
if (includeTags) {
|
||||
final NetworkStats tagStats = getUidTagComplete()
|
||||
@@ -483,6 +517,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
@Override
|
||||
public NetworkStatsHistory getHistoryForUid(
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields) {
|
||||
enforcePermissionForManagedAdmin(mCallingPackage);
|
||||
if (tag == TAG_NONE) {
|
||||
return getUidComplete().getHistory(template, uid, set, tag, fields);
|
||||
} else {
|
||||
@@ -498,6 +533,53 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean hasAppOpsPermission(String callingPackage) {
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
boolean appOpsAllow = false;
|
||||
if (callingPackage != null) {
|
||||
AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
|
||||
Context.APP_OPS_SERVICE);
|
||||
|
||||
final int mode = appOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
|
||||
callingUid, callingPackage);
|
||||
if (mode == AppOpsManager.MODE_DEFAULT) {
|
||||
// The default behavior here is to check if PackageManager has given the app
|
||||
// permission.
|
||||
final int permissionCheck = mContext.checkCallingPermission(
|
||||
Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
appOpsAllow = permissionCheck == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
appOpsAllow = (mode == AppOpsManager.MODE_ALLOWED);
|
||||
}
|
||||
return appOpsAllow;
|
||||
}
|
||||
|
||||
private void enforcePermissionForManagedAdmin(String callingPackage) {
|
||||
boolean hasPermission = hasAppOpsPermission(callingPackage);
|
||||
if (!hasPermission) {
|
||||
// Profile and device owners are exempt from permission checking.
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final DevicePolicyManagerInternal dpmi = LocalServices.getService(
|
||||
DevicePolicyManagerInternal.class);
|
||||
if (dpmi.isActiveAdminWithPolicy(callingUid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)
|
||||
|| dpmi.isActiveAdminWithPolicy(callingUid,
|
||||
DeviceAdminInfo.USES_POLICY_DEVICE_OWNER)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!hasPermission) {
|
||||
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
|
||||
}
|
||||
}
|
||||
|
||||
private void enforcePermission(String callingPackage) {
|
||||
boolean appOpsAllow = hasAppOpsPermission(callingPackage);
|
||||
if (!appOpsAllow) {
|
||||
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return network summary, splicing between DEV and XT stats when
|
||||
* appropriate.
|
||||
|
||||
Reference in New Issue
Block a user