Implementation of data usage callbacks.

NetworkStatsService will register data usage requests
and keep data usage stats scoped to the request.

There are different types of data usage requests
- scoped to a set of NetworkTemplate; these are restrictred to
device owners and carrier apps and allow the caller to monitor
all activity on the specified interfaces.
- scoped to all uids visible to the user, if the user has
android.Manifest.permission#PACKAGE_USAGE_STATS permission.
The set of uids may change over time, so we keep track of that.
- scoped to a set of uids given by the caller, granted that
the caller has access to those uids.
- scoped to the caller's own data usage. This doesn't require
PACKAGE_USAGE_STATS.

Bug: 25812785
Change-Id: Ie11f35fc1f29d0dbe82f7fc924b169bb55c76708
This commit is contained in:
Antonio Cansado
2016-02-17 13:03:38 -08:00
parent 118b392565
commit 7a6536383d
8 changed files with 811 additions and 53 deletions

View File

@@ -25,9 +25,15 @@ import android.net.ConnectivityManager;
import android.net.DataUsageRequest; import android.net.DataUsageRequest;
import android.net.NetworkIdentity; import android.net.NetworkIdentity;
import android.net.NetworkTemplate; import android.net.NetworkTemplate;
import android.net.INetworkStatsService;
import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.Message;
import android.os.Messenger;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log; import android.util.Log;
/** /**
@@ -75,16 +81,26 @@ import android.util.Log;
* not included. * not included.
*/ */
public class NetworkStatsManager { public class NetworkStatsManager {
private final static String TAG = "NetworkStatsManager"; private static final String TAG = "NetworkStatsManager";
private static final boolean DBG = false;
/** @hide */
public static final int CALLBACK_LIMIT_REACHED = 0;
/** @hide */
public static final int CALLBACK_RELEASED = 1;
private final Context mContext; private final Context mContext;
private final INetworkStatsService mService;
/** /**
* {@hide} * {@hide}
*/ */
public NetworkStatsManager(Context context) { public NetworkStatsManager(Context context) {
mContext = context; mContext = context;
mService = INetworkStatsService.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
} }
/** /**
* Query network usage statistics summaries. Result is summarised data usage for the whole * Query network usage statistics summaries. Result is summarised data usage for the whole
* device. Result is a single Bucket aggregated over time, state, uid, tag and roaming. This * device. Result is a single Bucket aggregated over time, state, uid, tag and roaming. This
@@ -322,7 +338,40 @@ public class NetworkStatsManager {
checkNotNull(policy, "DataUsagePolicy cannot be null"); checkNotNull(policy, "DataUsagePolicy cannot be null");
checkNotNull(callback, "DataUsageCallback cannot be null"); checkNotNull(callback, "DataUsageCallback cannot be null");
// TODO: Implement stub. final Looper looper;
if (handler == null) {
looper = Looper.myLooper();
} else {
looper = handler.getLooper();
}
if (DBG) Log.d(TAG, "registerDataUsageCallback called with " + policy);
NetworkTemplate[] templates;
if (policy.subscriberIds == null || policy.subscriberIds.length == 0) {
templates = new NetworkTemplate[1];
templates[0] = createTemplate(policy.networkType, null /* subscriberId */);
} else {
templates = new NetworkTemplate[policy.subscriberIds.length];
for (int i = 0; i < policy.subscriberIds.length; i++) {
templates[i] = createTemplate(policy.networkType, policy.subscriberIds[i]);
}
}
DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
templates, policy.uids, policy.thresholdInBytes);
try {
CallbackHandler callbackHandler = new CallbackHandler(looper, callback);
callback.request = mService.registerDataUsageCallback(
mContext.getOpPackageName(), request, new Messenger(callbackHandler),
new Binder());
if (DBG) Log.d(TAG, "registerDataUsageCallback returned " + callback.request);
if (callback.request == null) {
Log.e(TAG, "Request from callback is null; should not happen");
}
} catch (RemoteException e) {
if (DBG) Log.d(TAG, "Remote exception when registering callback");
}
} }
/** /**
@@ -331,9 +380,15 @@ public class NetworkStatsManager {
* @param callback The {@link DataUsageCallback} used when registering. * @param callback The {@link DataUsageCallback} used when registering.
*/ */
public void unregisterDataUsageCallback(DataUsageCallback callback) { public void unregisterDataUsageCallback(DataUsageCallback callback) {
checkNotNull(callback, "DataUsageCallback cannot be null"); if (callback == null || callback.request == null
|| callback.request.requestId == DataUsageRequest.REQUEST_ID_UNSET) {
// TODO: Implement stub. throw new IllegalArgumentException("Invalid DataUsageCallback");
}
try {
mService.unregisterDataUsageRequest(callback.request);
} catch (RemoteException e) {
if (DBG) Log.d(TAG, "Remote exception when unregistering callback");
}
} }
/** /**
@@ -366,4 +421,38 @@ public class NetworkStatsManager {
} }
return template; return template;
} }
private static class CallbackHandler extends Handler {
private DataUsageCallback mCallback;
CallbackHandler(Looper looper, DataUsageCallback callback) {
super(looper);
mCallback = callback;
}
@Override
public void handleMessage(Message message) {
DataUsageRequest request =
(DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY);
switch (message.what) {
case CALLBACK_LIMIT_REACHED: {
if (mCallback != null) {
mCallback.onLimitReached();
} else {
Log.e(TAG, "limit reached with released callback for " + request);
}
break;
}
case CALLBACK_RELEASED: {
if (DBG) Log.d(TAG, "callback released for " + request);
mCallback = null;
break;
}
}
}
private static Object getObject(Message msg, String key) {
return msg.getData().getParcelable(key);
}
}
} }

View File

@@ -31,6 +31,11 @@ import java.util.Objects;
*/ */
public class DataUsageRequest implements Parcelable { public class DataUsageRequest implements Parcelable {
/**
* @hide
*/
public static final String PARCELABLE_KEY = "DataUsageRequest";
/** /**
* @hide * @hide
*/ */

View File

@@ -16,10 +16,13 @@
package android.net; package android.net;
import android.net.DataUsageRequest;
import android.net.INetworkStatsSession; import android.net.INetworkStatsSession;
import android.net.NetworkStats; import android.net.NetworkStats;
import android.net.NetworkStatsHistory; import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate; import android.net.NetworkTemplate;
import android.os.IBinder;
import android.os.Messenger;
/** {@hide} */ /** {@hide} */
interface INetworkStatsService { interface INetworkStatsService {
@@ -57,4 +60,11 @@ interface INetworkStatsService {
/** Advise persistance threshold; may be overridden internally. */ /** Advise persistance threshold; may be overridden internally. */
void advisePersistThreshold(long thresholdBytes); void advisePersistThreshold(long thresholdBytes);
/** Registers a callback on data usage. */
DataUsageRequest registerDataUsageCallback(String callingPackage,
in DataUsageRequest request, in Messenger messenger, in IBinder binder);
/** Unregisters a callback on data usage. */
void unregisterDataUsageRequest(in DataUsageRequest request);
} }

View File

@@ -17,6 +17,7 @@
package com.android.server.net; package com.android.server.net;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_REMOVED; import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING; import static android.net.TrafficStats.UID_TETHERING;
@@ -48,6 +49,7 @@ public final class NetworkStatsAccess {
@IntDef({ @IntDef({
Level.DEFAULT, Level.DEFAULT,
Level.USER, Level.USER,
Level.DEVICESUMMARY,
Level.DEVICE, Level.DEVICE,
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@@ -147,6 +149,12 @@ public final class NetworkStatsAccess {
// Device-level access - can access usage for any uid. // Device-level access - can access usage for any uid.
return true; return true;
case NetworkStatsAccess.Level.DEVICESUMMARY: case NetworkStatsAccess.Level.DEVICESUMMARY:
// Can access usage for any app running in the same user, along
// with some special uids (system, removed, or tethering) and
// anonymized uids
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|| uid == UID_TETHERING || uid == UID_ALL
|| UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
case NetworkStatsAccess.Level.USER: case NetworkStatsAccess.Level.USER:
// User-level access - can access usage for any app running in the same user, along // User-level access - can access usage for any app running in the same user, along
// with some special uids (system, removed, or tethering). // with some special uids (system, removed, or tethering).

View File

@@ -135,7 +135,11 @@ public class NetworkStatsCollection implements FileRotator.Reader {
} }
public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
final int callerUid = Binder.getCallingUid(); return getRelevantUids(accessLevel, Binder.getCallingUid());
}
public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
final int callerUid) {
IntArray uids = new IntArray(); IntArray uids = new IntArray();
for (int i = 0; i < mStats.size(); i++) { for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i); final Key key = mStats.keyAt(i);
@@ -169,7 +173,17 @@ public class NetworkStatsCollection implements FileRotator.Reader {
public NetworkStatsHistory getHistory( public NetworkStatsHistory getHistory(
NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end, NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
@NetworkStatsAccess.Level int accessLevel) { @NetworkStatsAccess.Level int accessLevel) {
final int callerUid = Binder.getCallingUid(); return getHistory(template, uid, set, tag, fields, start, end, accessLevel,
Binder.getCallingUid());
}
/**
* Combine all {@link NetworkStatsHistory} in this collection which match
* the requested parameters.
*/
public NetworkStatsHistory getHistory(
NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
@NetworkStatsAccess.Level int accessLevel, int callerUid) {
if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) { if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
throw new SecurityException("Network stats history of uid " + uid throw new SecurityException("Network stats history of uid " + uid
+ " is forbidden for caller " + callerUid); + " is forbidden for caller " + callerUid);
@@ -198,6 +212,15 @@ public class NetworkStatsCollection implements FileRotator.Reader {
*/ */
public NetworkStats getSummary(NetworkTemplate template, long start, long end, public NetworkStats getSummary(NetworkTemplate template, long start, long end,
@NetworkStatsAccess.Level int accessLevel) { @NetworkStatsAccess.Level int accessLevel) {
return getSummary(template, start, end, accessLevel, Binder.getCallingUid());
}
/**
* Summarize all {@link NetworkStatsHistory} in this collection which match
* the requested parameters.
*/
public NetworkStats getSummary(NetworkTemplate template, long start, long end,
@NetworkStatsAccess.Level int accessLevel, int callerUid) {
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
final NetworkStats stats = new NetworkStats(end - start, 24); final NetworkStats stats = new NetworkStats(end - start, 24);
@@ -207,7 +230,6 @@ public class NetworkStatsCollection implements FileRotator.Reader {
final NetworkStats.Entry entry = new NetworkStats.Entry(); final NetworkStats.Entry entry = new NetworkStats.Entry();
NetworkStatsHistory.Entry historyEntry = null; NetworkStatsHistory.Entry historyEntry = null;
final int callerUid = Binder.getCallingUid();
for (int i = 0; i < mStats.size(); i++) { for (int i = 0; i < mStats.size(); i++) {
final Key key = mStats.keyAt(i); final Key key = mStats.keyAt(i);
if (templateMatches(template, key.ident) if (templateMatches(template, key.ident)

View File

@@ -0,0 +1,493 @@
/*
* Copyright (C) 2016 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 com.android.server.net;
import static android.net.TrafficStats.MB_IN_BYTES;
import static com.android.internal.util.Preconditions.checkArgument;
import android.app.usage.NetworkStatsManager;
import android.net.DataUsageRequest;
import android.net.NetworkStats;
import android.net.NetworkStats.NonMonotonicObserver;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.os.Binder;
import android.os.Bundle;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.SparseArray;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.VpnInfo;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Manages observers of {@link NetworkStats}. Allows observers to be notified when
* data usage has been reported in {@link NetworkStatsService}. An observer can set
* a threshold of how much data it cares about to be notified.
*/
class NetworkStatsObservers {
private static final String TAG = "NetworkStatsObservers";
private static final boolean LOGV = true;
private static final long MIN_THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final int MSG_REGISTER = 1;
private static final int MSG_UNREGISTER = 2;
private static final int MSG_UPDATE_STATS = 3;
// All access to this map must be done from the handler thread.
// indexed by DataUsageRequest#requestId
private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
// Sequence number of DataUsageRequests
private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
// Lazily instantiated when an observer is registered.
private Handler mHandler;
/**
* Creates a wrapper that contains the caller context and a normalized request.
* The request should be returned to the caller app, and the wrapper should be sent to this
* object through #addObserver by the service handler.
*
* <p>It will register the observer asynchronously, so it is safe to call from any thread.
*
* @return the normalized request wrapped within {@link RequestInfo}.
*/
public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
checkVisibilityUids(callingUid, accessLevel, inputRequest.uids);
DataUsageRequest request = buildRequest(inputRequest);
RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
accessLevel);
if (LOGV) Slog.v(TAG, "Registering observer for " + request);
getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
return request;
}
/**
* Unregister a data usage observer.
*
* <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
*/
public void unregister(DataUsageRequest request, int callingUid) {
getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
request));
}
/**
* Updates data usage statistics of registered observers and notifies if limits are reached.
*
* <p>It will update stats asynchronously, so it is safe to call from any thread.
*/
public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
ArrayMap<String, NetworkIdentitySet> activeIfaces,
ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
VpnInfo[] vpnArray, long currentTime) {
StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
activeUidIfaces, vpnArray, currentTime);
getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
}
private Handler getHandler() {
if (mHandler == null) {
synchronized (this) {
if (mHandler == null) {
if (LOGV) Slog.v(TAG, "Creating handler");
mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
}
}
}
return mHandler;
}
@VisibleForTesting
protected Looper getHandlerLooperLocked() {
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
return handlerThread.getLooper();
}
private Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER: {
handleRegister((RequestInfo) msg.obj);
return true;
}
case MSG_UNREGISTER: {
handleUnregister((DataUsageRequest) msg.obj, msg.arg1 /* callingUid */);
return true;
}
case MSG_UPDATE_STATS: {
handleUpdateStats((StatsContext) msg.obj);
return true;
}
default: {
return false;
}
}
}
};
/**
* Adds a {@link RequestInfo} as an observer.
* Should only be called from the handler thread otherwise there will be a race condition
* on mDataUsageRequests.
*/
private void handleRegister(RequestInfo requestInfo) {
mDataUsageRequests.put(requestInfo.mRequest.requestId, requestInfo);
}
/**
* Removes a {@link DataUsageRequest} if the calling uid is authorized.
* Should only be called from the handler thread otherwise there will be a race condition
* on mDataUsageRequests.
*/
private void handleUnregister(DataUsageRequest request, int callingUid) {
RequestInfo requestInfo;
requestInfo = mDataUsageRequests.get(request.requestId);
if (requestInfo == null) {
if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request);
return;
}
if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
return;
}
if (LOGV) Slog.v(TAG, "Unregistering " + request);
mDataUsageRequests.remove(request.requestId);
requestInfo.unlinkDeathRecipient();
requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
}
private void handleUpdateStats(StatsContext statsContext) {
if (mDataUsageRequests.size() == 0) {
if (LOGV) Slog.v(TAG, "No registered listeners of data usage");
return;
}
if (LOGV) Slog.v(TAG, "Checking if any registered observer needs to be notified");
for (int i = 0; i < mDataUsageRequests.size(); i++) {
RequestInfo requestInfo = mDataUsageRequests.valueAt(i);
requestInfo.updateStats(statsContext);
}
}
private DataUsageRequest buildRequest(DataUsageRequest request) {
// Cap the minimum threshold to a safe default to avoid too many callbacks
long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
if (thresholdInBytes < request.thresholdInBytes) {
Slog.w(TAG, "Threshold was too low for " + request
+ ". Overriding to a safer default of " + thresholdInBytes + " bytes");
}
return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
request.templates, request.uids, thresholdInBytes);
}
private RequestInfo buildRequestInfo(DataUsageRequest request,
Messenger messenger, IBinder binder, int callingUid,
@NetworkStatsAccess.Level int accessLevel) {
if (accessLevel <= NetworkStatsAccess.Level.USER
|| request.uids != null && request.uids.length > 0) {
return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
accessLevel);
} else {
// Safety check in case a new access level is added and we forgot to update this
checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY);
return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
accessLevel);
}
}
private void checkVisibilityUids(int callingUid, @NetworkStatsAccess.Level int accessLevel,
int[] uids) {
if (uids == null) {
return;
}
for (int i = 0; i < uids.length; i++) {
if (!NetworkStatsAccess.isAccessibleToUser(uids[i], callingUid, accessLevel)) {
throw new SecurityException("Caller " + callingUid + " cannot monitor network stats"
+ " for uid " + uids[i] + " with accessLevel " + accessLevel);
}
}
}
/**
* Tracks information relevant to a data usage observer.
* It will notice when the calling process dies so we can self-expire.
*/
private abstract static class RequestInfo implements IBinder.DeathRecipient {
private final NetworkStatsObservers mStatsObserver;
protected final DataUsageRequest mRequest;
private final Messenger mMessenger;
private final IBinder mBinder;
protected final int mCallingUid;
protected final @NetworkStatsAccess.Level int mAccessLevel;
protected NetworkStatsRecorder mRecorder;
protected NetworkStatsCollection mCollection;
RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
Messenger messenger, IBinder binder, int callingUid,
@NetworkStatsAccess.Level int accessLevel) {
mStatsObserver = statsObserver;
mRequest = request;
mMessenger = messenger;
mBinder = binder;
mCallingUid = callingUid;
mAccessLevel = accessLevel;
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
binderDied();
}
}
@Override
public void binderDied() {
if (LOGV) Slog.v(TAG, "RequestInfo binderDied("
+ mRequest + ", " + mBinder + ")");
mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
callCallback(NetworkStatsManager.CALLBACK_RELEASED);
}
@Override
public String toString() {
return "RequestInfo from uid:" + mCallingUid
+ " for " + mRequest + " accessLevel:" + mAccessLevel;
}
private void unlinkDeathRecipient() {
if (mBinder != null) {
mBinder.unlinkToDeath(this, 0);
}
}
/**
* Update stats given the samples and interface to identity mappings.
*/
private void updateStats(StatsContext statsContext) {
if (mRecorder == null) {
// First run; establish baseline stats
resetRecorder();
recordSample(statsContext);
return;
}
recordSample(statsContext);
if (checkStats()) {
resetRecorder();
callCallback(NetworkStatsManager.CALLBACK_LIMIT_REACHED);
}
}
private void callCallback(int callbackType) {
Bundle bundle = new Bundle();
bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
Message msg = Message.obtain();
msg.what = callbackType;
msg.setData(bundle);
try {
if (LOGV) {
Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType)
+ " for " + mRequest);
}
mMessenger.send(msg);
} catch (RemoteException e) {
// May occur naturally in the race of binder death.
Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
}
}
private void resetRecorder() {
mRecorder = new NetworkStatsRecorder();
mCollection = mRecorder.getSinceBoot();
}
protected abstract boolean checkStats();
protected abstract void recordSample(StatsContext statsContext);
private String callbackTypeToName(int callbackType) {
switch (callbackType) {
case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
return "LIMIT_REACHED";
case NetworkStatsManager.CALLBACK_RELEASED:
return "RELEASED";
default:
return "UNKNOWN";
}
}
}
private static class NetworkUsageRequestInfo extends RequestInfo {
NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
Messenger messenger, IBinder binder, int callingUid,
@NetworkStatsAccess.Level int accessLevel) {
super(statsObserver, request, messenger, binder, callingUid, accessLevel);
}
@Override
protected boolean checkStats() {
for (int i = 0; i < mRequest.templates.length; i++) {
long bytesSoFar = getTotalBytesForNetwork(mRequest.templates[i]);
if (LOGV) {
Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
+ mRequest.templates[i]);
}
if (bytesSoFar > mRequest.thresholdInBytes) {
return true;
}
}
return false;
}
@Override
protected void recordSample(StatsContext statsContext) {
// Recorder does not need to be locked in this context since only the handler
// thread will update it
mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
statsContext.mVpnArray, statsContext.mCurrentTime);
}
/**
* Reads stats matching the given template. {@link NetworkStatsCollection} will aggregate
* over all buckets, which in this case should be only one since we built it big enough
* that it will outlive the caller. If it doesn't, then there will be multiple buckets.
*/
private long getTotalBytesForNetwork(NetworkTemplate template) {
NetworkStats stats = mCollection.getSummary(template,
Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
mAccessLevel, mCallingUid);
if (LOGV) {
Slog.v(TAG, "Netstats for " + template + ": " + stats);
}
return stats.getTotalBytes();
}
}
private static class UserUsageRequestInfo extends RequestInfo {
UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
Messenger messenger, IBinder binder, int callingUid,
@NetworkStatsAccess.Level int accessLevel) {
super(statsObserver, request, messenger, binder, callingUid, accessLevel);
}
@Override
protected boolean checkStats() {
int[] uidsToMonitor = getUidsToMonitor();
for (int i = 0; i < mRequest.templates.length; i++) {
for (int j = 0; j < uidsToMonitor.length; j++) {
long bytesSoFar = getTotalBytesForNetworkUid(mRequest.templates[i],
uidsToMonitor[j]);
if (LOGV) {
Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
+ mRequest.templates[i] + " for uid=" + uidsToMonitor[j]);
}
if (bytesSoFar > mRequest.thresholdInBytes) {
return true;
}
}
}
return false;
}
@Override
protected void recordSample(StatsContext statsContext) {
// Recorder does not need to be locked in this context since only the handler
// thread will update it
mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
statsContext.mVpnArray, statsContext.mCurrentTime);
}
/**
* Reads all stats matching the given template and uid. Ther history will likely only
* contain one bucket per ident since we build it big enough that it will outlive the
* caller lifetime.
*/
private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) {
try {
NetworkStatsHistory history = mCollection.getHistory(template, uid,
NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
NetworkStatsHistory.FIELD_ALL,
Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
mAccessLevel, mCallingUid);
return history.getTotalBytes();
} catch (SecurityException e) {
if (LOGV) {
Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
+ uid);
}
return 0;
}
}
private int[] getUidsToMonitor() {
if (mRequest.uids == null || mRequest.uids.length == 0) {
return mCollection.getRelevantUids(mAccessLevel, mCallingUid);
}
// Pick only uids from the request that are currently accessible to the user
IntArray accessibleUids = new IntArray(mRequest.uids.length);
for (int i = 0; i < mRequest.uids.length; i++) {
int uid = mRequest.uids[i];
if (NetworkStatsAccess.isAccessibleToUser(uid, mCallingUid, mAccessLevel)) {
accessibleUids.add(uid);
}
}
return accessibleUids.toArray();
}
}
private static class StatsContext {
NetworkStats mXtSnapshot;
NetworkStats mUidSnapshot;
ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
VpnInfo[] mVpnArray;
long mCurrentTime;
StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
ArrayMap<String, NetworkIdentitySet> activeIfaces,
ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
VpnInfo[] vpnArray, long currentTime) {
mXtSnapshot = xtSnapshot;
mUidSnapshot = uidSnapshot;
mActiveIfaces = activeIfaces;
mActiveUidIfaces = activeUidIfaces;
mVpnArray = vpnArray;
mCurrentTime = currentTime;
}
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.net;
import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.TAG_NONE;
import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.KB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkNotNull;
import android.net.NetworkStats; import android.net.NetworkStats;
@@ -54,7 +55,7 @@ import libcore.io.IoUtils;
* Logic to record deltas between periodic {@link NetworkStats} snapshots into * Logic to record deltas between periodic {@link NetworkStats} snapshots into
* {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
* Keeps pending changes in memory until they pass a specific threshold, in * Keeps pending changes in memory until they pass a specific threshold, in
* bytes. Uses {@link FileRotator} for persistence logic. * bytes. Uses {@link FileRotator} for persistence logic if present.
* <p> * <p>
* Not inherently thread safe. * Not inherently thread safe.
*/ */
@@ -86,6 +87,29 @@ public class NetworkStatsRecorder {
private WeakReference<NetworkStatsCollection> mComplete; private WeakReference<NetworkStatsCollection> mComplete;
/**
* Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}.
*/
public NetworkStatsRecorder() {
mRotator = null;
mObserver = null;
mDropBox = null;
mCookie = null;
// set the bucket big enough to have all data in one bucket, but allow some
// slack to avoid overflow
mBucketDuration = YEAR_IN_MILLIS;
mOnlyTags = false;
mPending = null;
mSinceBoot = new NetworkStatsCollection(mBucketDuration);
mPendingRewriter = null;
}
/**
* Persisted recorder.
*/
public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) { DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
mRotator = checkNotNull(rotator, "missing FileRotator"); mRotator = checkNotNull(rotator, "missing FileRotator");
@@ -110,9 +134,15 @@ public class NetworkStatsRecorder {
public void resetLocked() { public void resetLocked() {
mLastSnapshot = null; mLastSnapshot = null;
mPending.reset(); if (mPending != null) {
mSinceBoot.reset(); mPending.reset();
mComplete.clear(); }
if (mSinceBoot != null) {
mSinceBoot.reset();
}
if (mComplete != null) {
mComplete.clear();
}
} }
public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) { public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
@@ -120,6 +150,10 @@ public class NetworkStatsRecorder {
NetworkStatsAccess.Level.DEVICE).getTotal(null); NetworkStatsAccess.Level.DEVICE).getTotal(null);
} }
public NetworkStatsCollection getSinceBoot() {
return mSinceBoot;
}
/** /**
* Load complete history represented by {@link FileRotator}. Caches * Load complete history represented by {@link FileRotator}. Caches
* internally as a {@link WeakReference}, and updated with future * internally as a {@link WeakReference}, and updated with future
@@ -127,6 +161,7 @@ public class NetworkStatsRecorder {
* as reference is valid. * as reference is valid.
*/ */
public NetworkStatsCollection getOrLoadCompleteLocked() { public NetworkStatsCollection getOrLoadCompleteLocked() {
checkNotNull(mRotator, "missing FileRotator");
NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
if (res == null) { if (res == null) {
res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE); res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE);
@@ -136,6 +171,7 @@ public class NetworkStatsRecorder {
} }
public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) { public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) {
checkNotNull(mRotator, "missing FileRotator");
NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
if (res == null) { if (res == null) {
res = loadLocked(start, end); res = loadLocked(start, end);
@@ -205,7 +241,9 @@ public class NetworkStatsRecorder {
// only record tag data when requested // only record tag data when requested
if ((entry.tag == TAG_NONE) != mOnlyTags) { if ((entry.tag == TAG_NONE) != mOnlyTags) {
mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); if (mPending != null) {
mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
}
// also record against boot stats when present // also record against boot stats when present
if (mSinceBoot != null) { if (mSinceBoot != null) {
@@ -231,6 +269,7 @@ public class NetworkStatsRecorder {
* {@link #mPersistThresholdBytes}. * {@link #mPersistThresholdBytes}.
*/ */
public void maybePersistLocked(long currentTimeMillis) { public void maybePersistLocked(long currentTimeMillis) {
checkNotNull(mRotator, "missing FileRotator");
final long pendingBytes = mPending.getTotalBytes(); final long pendingBytes = mPending.getTotalBytes();
if (pendingBytes >= mPersistThresholdBytes) { if (pendingBytes >= mPersistThresholdBytes) {
forcePersistLocked(currentTimeMillis); forcePersistLocked(currentTimeMillis);
@@ -243,6 +282,7 @@ public class NetworkStatsRecorder {
* Force persisting any pending deltas. * Force persisting any pending deltas.
*/ */
public void forcePersistLocked(long currentTimeMillis) { public void forcePersistLocked(long currentTimeMillis) {
checkNotNull(mRotator, "missing FileRotator");
if (mPending.isDirty()) { if (mPending.isDirty()) {
if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie); if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
try { try {
@@ -264,20 +304,26 @@ public class NetworkStatsRecorder {
* to {@link TrafficStats#UID_REMOVED}. * to {@link TrafficStats#UID_REMOVED}.
*/ */
public void removeUidsLocked(int[] uids) { public void removeUidsLocked(int[] uids) {
try { if (mRotator != null) {
// Rewrite all persisted data to migrate UID stats try {
mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids)); // Rewrite all persisted data to migrate UID stats
} catch (IOException e) { mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); } catch (IOException e) {
recoverFromWtf(); Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
} catch (OutOfMemoryError e) { recoverFromWtf();
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); } catch (OutOfMemoryError e) {
recoverFromWtf(); Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
recoverFromWtf();
}
} }
// Remove any pending stats // Remove any pending stats
mPending.removeUids(uids); if (mPending != null) {
mSinceBoot.removeUids(uids); mPending.removeUids(uids);
}
if (mSinceBoot != null) {
mSinceBoot.removeUids(uids);
}
// Clear UID from current stats snapshot // Clear UID from current stats snapshot
if (mLastSnapshot != null) { if (mLastSnapshot != null) {
@@ -361,6 +407,8 @@ public class NetworkStatsRecorder {
} }
public void importLegacyNetworkLocked(File file) throws IOException { public void importLegacyNetworkLocked(File file) throws IOException {
checkNotNull(mRotator, "missing FileRotator");
// legacy file still exists; start empty to avoid double importing // legacy file still exists; start empty to avoid double importing
mRotator.deleteAll(); mRotator.deleteAll();
@@ -379,6 +427,8 @@ public class NetworkStatsRecorder {
} }
public void importLegacyUidLocked(File file) throws IOException { public void importLegacyUidLocked(File file) throws IOException {
checkNotNull(mRotator, "missing FileRotator");
// legacy file still exists; start empty to avoid double importing // legacy file still exists; start empty to avoid double importing
mRotator.deleteAll(); mRotator.deleteAll();
@@ -397,7 +447,9 @@ public class NetworkStatsRecorder {
} }
public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) { public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes()); if (mPending != null) {
pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
}
if (fullHistory) { if (fullHistory) {
pw.println("Complete history:"); pw.println("Complete history:");
getOrLoadCompleteLocked().dump(pw); getOrLoadCompleteLocked().dump(pw);

View File

@@ -57,6 +57,7 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats; import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
@@ -72,6 +73,7 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.DataUsageRequest;
import android.net.IConnectivityManager; import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver; import android.net.INetworkManagementEventObserver;
import android.net.INetworkStatsService; import android.net.INetworkStatsService;
@@ -90,8 +92,10 @@ import android.os.DropBoxManager;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService; import android.os.INetworkManagementService;
import android.os.Message; import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
@@ -152,6 +156,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
private final TrustedTime mTime; private final TrustedTime mTime;
private final TelephonyManager mTeleManager; private final TelephonyManager mTeleManager;
private final NetworkStatsSettings mSettings; private final NetworkStatsSettings mSettings;
private final NetworkStatsObservers mStatsObservers;
private final File mSystemDir; private final File mSystemDir;
private final File mBaseDir; private final File mBaseDir;
@@ -233,43 +238,65 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
/** Data layer operation counters for splicing into other structures. */ /** Data layer operation counters for splicing into other structures. */
private NetworkStats mUidOperations = new NetworkStats(0L, 10); private NetworkStats mUidOperations = new NetworkStats(0L, 10);
private final Handler mHandler; /** Must be set in factory by calling #setHandler. */
private Handler mHandler;
private Handler.Callback mHandlerCallback;
private boolean mSystemReady; private boolean mSystemReady;
private long mPersistThreshold = 2 * MB_IN_BYTES; private long mPersistThreshold = 2 * MB_IN_BYTES;
private long mGlobalAlertBytes; private long mGlobalAlertBytes;
public NetworkStatsService(
Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
getDefaultSystemDir(), new DefaultNetworkStatsSettings(context));
}
private static File getDefaultSystemDir() { private static File getDefaultSystemDir() {
return new File(Environment.getDataDirectory(), "system"); return new File(Environment.getDataDirectory(), "system");
} }
public NetworkStatsService(Context context, INetworkManagementService networkManager, private static File getDefaultBaseDir() {
IAlarmManager alarmManager, TrustedTime time, File systemDir, File baseDir = new File(getDefaultSystemDir(), "netstats");
NetworkStatsSettings settings) { baseDir.mkdirs();
return baseDir;
}
public static NetworkStatsService create(Context context,
INetworkManagementService networkManager) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
NetworkStatsService service = new NetworkStatsService(context, networkManager, alarmManager,
wakeLock, NtpTrustedTime.getInstance(context), TelephonyManager.getDefault(),
new DefaultNetworkStatsSettings(context), new NetworkStatsObservers(),
getDefaultSystemDir(), getDefaultBaseDir());
HandlerThread handlerThread = new HandlerThread(TAG);
Handler.Callback callback = new HandlerCallback(service);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper(), callback);
service.setHandler(handler, callback);
return service;
}
@VisibleForTesting
NetworkStatsService(Context context, INetworkManagementService networkManager,
AlarmManager alarmManager, PowerManager.WakeLock wakeLock, TrustedTime time,
TelephonyManager teleManager, NetworkStatsSettings settings,
NetworkStatsObservers statsObservers, File systemDir, File baseDir) {
mContext = checkNotNull(context, "missing Context"); mContext = checkNotNull(context, "missing Context");
mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService"); mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
mAlarmManager = checkNotNull(alarmManager, "missing AlarmManager");
mTime = checkNotNull(time, "missing TrustedTime"); mTime = checkNotNull(time, "missing TrustedTime");
mTeleManager = checkNotNull(TelephonyManager.getDefault(), "missing TelephonyManager");
mSettings = checkNotNull(settings, "missing NetworkStatsSettings"); mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mTeleManager = checkNotNull(teleManager, "missing TelephonyManager");
mWakeLock = checkNotNull(wakeLock, "missing WakeLock");
mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
mSystemDir = checkNotNull(systemDir, "missing systemDir");
mBaseDir = checkNotNull(baseDir, "missing baseDir");
}
final PowerManager powerManager = (PowerManager) context.getSystemService( @VisibleForTesting
Context.POWER_SERVICE); void setHandler(Handler handler, Handler.Callback callback) {
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mHandler = handler;
mHandlerCallback = callback;
HandlerThread thread = new HandlerThread(TAG);
thread.start();
mHandler = new Handler(thread.getLooper(), mHandlerCallback);
mSystemDir = checkNotNull(systemDir);
mBaseDir = new File(systemDir, "netstats");
mBaseDir.mkdirs();
} }
public void bindConnectivityManager(IConnectivityManager connManager) { public void bindConnectivityManager(IConnectivityManager connManager) {
@@ -733,6 +760,46 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
registerGlobalAlert(); registerGlobalAlert();
} }
@Override
public DataUsageRequest registerDataUsageCallback(String callingPackage,
DataUsageRequest request, Messenger messenger, IBinder binder) {
checkNotNull(callingPackage, "calling package is null");
checkNotNull(request, "DataUsageRequest is null");
checkNotNull(request.templates, "NetworkTemplate is null");
checkArgument(request.templates.length > 0);
checkNotNull(messenger, "messenger is null");
checkNotNull(binder, "binder is null");
int callingUid = Binder.getCallingUid();
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
DataUsageRequest normalizedRequest;
final long token = Binder.clearCallingIdentity();
try {
normalizedRequest = mStatsObservers.register(request, messenger, binder,
callingUid, accessLevel);
} finally {
Binder.restoreCallingIdentity(token);
}
// Create baseline stats
mHandler.sendMessage(mHandler.obtainMessage(MSG_PERFORM_POLL, FLAG_PERSIST_ALL));
return normalizedRequest;
}
@Override
public void unregisterDataUsageRequest(DataUsageRequest request) {
checkNotNull(request, "DataUsageRequest is null");
int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
mStatsObservers.unregister(request, callingUid);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/** /**
* Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
* reflect current {@link #mPersistThreshold} value. Always defers to * reflect current {@link #mPersistThreshold} value. Always defers to
@@ -945,6 +1012,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, null, currentTime); mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, null, currentTime);
mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime); mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime); mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime);
// We need to make copies of member fields that are sent to the observer to avoid
// a race condition between the service handler thread and the observer's
mStatsObservers.updateStats(xtSnapshot, uidSnapshot, new ArrayMap<>(mActiveIfaces),
new ArrayMap<>(mActiveUidIfaces), vpnArray, currentTime);
} }
/** /**
@@ -1243,21 +1315,28 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} }
} }
private Handler.Callback mHandlerCallback = new Handler.Callback() { @VisibleForTesting
static class HandlerCallback implements Handler.Callback {
private final NetworkStatsService mService;
HandlerCallback(NetworkStatsService service) {
this.mService = service;
}
@Override @Override
public boolean handleMessage(Message msg) { public boolean handleMessage(Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_PERFORM_POLL: { case MSG_PERFORM_POLL: {
final int flags = msg.arg1; final int flags = msg.arg1;
performPoll(flags); mService.performPoll(flags);
return true; return true;
} }
case MSG_UPDATE_IFACES: { case MSG_UPDATE_IFACES: {
updateIfaces(); mService.updateIfaces();
return true; return true;
} }
case MSG_REGISTER_GLOBAL_ALERT: { case MSG_REGISTER_GLOBAL_ALERT: {
registerGlobalAlert(); mService.registerGlobalAlert();
return true; return true;
} }
default: { default: {
@@ -1265,7 +1344,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
} }
} }
} }
}; }
private void assertBandwidthControlEnabled() { private void assertBandwidthControlEnabled() {
if (!isBandwidthControlEnabled()) { if (!isBandwidthControlEnabled()) {