blob: 741c2062bd571ae007c6197546d29471008cf7fd [file] [log] [blame]
Antonio Cansadocd42acd2016-02-17 13:03:38 -08001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.net;
18
19import static android.net.TrafficStats.MB_IN_BYTES;
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060020
Antonio Cansadocd42acd2016-02-17 13:03:38 -080021import static com.android.internal.util.Preconditions.checkArgument;
22
23import android.app.usage.NetworkStatsManager;
24import android.net.DataUsageRequest;
25import android.net.NetworkStats;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080026import android.net.NetworkStatsHistory;
27import android.net.NetworkTemplate;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080028import android.os.Bundle;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080029import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.IBinder;
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060032import android.os.Looper;
33import android.os.Message;
34import android.os.Messenger;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080035import android.os.Process;
36import android.os.RemoteException;
37import android.util.ArrayMap;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080038import android.util.Slog;
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060039import android.util.SparseArray;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080040
41import com.android.internal.annotations.VisibleForTesting;
42import com.android.internal.net.VpnInfo;
43
44import java.util.concurrent.atomic.AtomicInteger;
45
46/**
47 * Manages observers of {@link NetworkStats}. Allows observers to be notified when
48 * data usage has been reported in {@link NetworkStatsService}. An observer can set
49 * a threshold of how much data it cares about to be notified.
50 */
51class NetworkStatsObservers {
52 private static final String TAG = "NetworkStatsObservers";
Joe Onorato13460a62016-03-18 13:34:13 -070053 private static final boolean LOGV = false;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080054
55 private static final long MIN_THRESHOLD_BYTES = 2 * MB_IN_BYTES;
56
57 private static final int MSG_REGISTER = 1;
58 private static final int MSG_UNREGISTER = 2;
59 private static final int MSG_UPDATE_STATS = 3;
60
61 // All access to this map must be done from the handler thread.
62 // indexed by DataUsageRequest#requestId
63 private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
64
65 // Sequence number of DataUsageRequests
66 private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
67
68 // Lazily instantiated when an observer is registered.
Ian Rogers4509b942016-05-18 19:58:56 -070069 private volatile Handler mHandler;
Antonio Cansadocd42acd2016-02-17 13:03:38 -080070
71 /**
72 * Creates a wrapper that contains the caller context and a normalized request.
73 * The request should be returned to the caller app, and the wrapper should be sent to this
74 * object through #addObserver by the service handler.
75 *
76 * <p>It will register the observer asynchronously, so it is safe to call from any thread.
77 *
78 * @return the normalized request wrapped within {@link RequestInfo}.
79 */
80 public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
81 IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
Antonio Cansadocd42acd2016-02-17 13:03:38 -080082 DataUsageRequest request = buildRequest(inputRequest);
83 RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
84 accessLevel);
85
86 if (LOGV) Slog.v(TAG, "Registering observer for " + request);
87 getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
88 return request;
89 }
90
91 /**
92 * Unregister a data usage observer.
93 *
94 * <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
95 */
96 public void unregister(DataUsageRequest request, int callingUid) {
97 getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
98 request));
99 }
100
101 /**
102 * Updates data usage statistics of registered observers and notifies if limits are reached.
103 *
104 * <p>It will update stats asynchronously, so it is safe to call from any thread.
105 */
106 public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
107 ArrayMap<String, NetworkIdentitySet> activeIfaces,
108 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
109 VpnInfo[] vpnArray, long currentTime) {
110 StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
111 activeUidIfaces, vpnArray, currentTime);
112 getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
113 }
114
115 private Handler getHandler() {
116 if (mHandler == null) {
117 synchronized (this) {
118 if (mHandler == null) {
119 if (LOGV) Slog.v(TAG, "Creating handler");
120 mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
121 }
122 }
123 }
124 return mHandler;
125 }
126
127 @VisibleForTesting
128 protected Looper getHandlerLooperLocked() {
129 HandlerThread handlerThread = new HandlerThread(TAG);
130 handlerThread.start();
131 return handlerThread.getLooper();
132 }
133
134 private Handler.Callback mHandlerCallback = new Handler.Callback() {
135 @Override
136 public boolean handleMessage(Message msg) {
137 switch (msg.what) {
138 case MSG_REGISTER: {
139 handleRegister((RequestInfo) msg.obj);
140 return true;
141 }
142 case MSG_UNREGISTER: {
143 handleUnregister((DataUsageRequest) msg.obj, msg.arg1 /* callingUid */);
144 return true;
145 }
146 case MSG_UPDATE_STATS: {
147 handleUpdateStats((StatsContext) msg.obj);
148 return true;
149 }
150 default: {
151 return false;
152 }
153 }
154 }
155 };
156
157 /**
158 * Adds a {@link RequestInfo} as an observer.
159 * Should only be called from the handler thread otherwise there will be a race condition
160 * on mDataUsageRequests.
161 */
162 private void handleRegister(RequestInfo requestInfo) {
163 mDataUsageRequests.put(requestInfo.mRequest.requestId, requestInfo);
164 }
165
166 /**
167 * Removes a {@link DataUsageRequest} if the calling uid is authorized.
168 * Should only be called from the handler thread otherwise there will be a race condition
169 * on mDataUsageRequests.
170 */
171 private void handleUnregister(DataUsageRequest request, int callingUid) {
172 RequestInfo requestInfo;
173 requestInfo = mDataUsageRequests.get(request.requestId);
174 if (requestInfo == null) {
175 if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request);
176 return;
177 }
178 if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
179 Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
180 return;
181 }
182
183 if (LOGV) Slog.v(TAG, "Unregistering " + request);
184 mDataUsageRequests.remove(request.requestId);
185 requestInfo.unlinkDeathRecipient();
186 requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
187 }
188
189 private void handleUpdateStats(StatsContext statsContext) {
190 if (mDataUsageRequests.size() == 0) {
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800191 return;
192 }
193
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800194 for (int i = 0; i < mDataUsageRequests.size(); i++) {
195 RequestInfo requestInfo = mDataUsageRequests.valueAt(i);
196 requestInfo.updateStats(statsContext);
197 }
198 }
199
200 private DataUsageRequest buildRequest(DataUsageRequest request) {
201 // Cap the minimum threshold to a safe default to avoid too many callbacks
202 long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
203 if (thresholdInBytes < request.thresholdInBytes) {
204 Slog.w(TAG, "Threshold was too low for " + request
205 + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
206 }
207 return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
Antonio Cansado6965c182016-03-30 11:37:18 -0700208 request.template, thresholdInBytes);
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800209 }
210
211 private RequestInfo buildRequestInfo(DataUsageRequest request,
212 Messenger messenger, IBinder binder, int callingUid,
213 @NetworkStatsAccess.Level int accessLevel) {
Antonio Cansado6965c182016-03-30 11:37:18 -0700214 if (accessLevel <= NetworkStatsAccess.Level.USER) {
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800215 return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
216 accessLevel);
217 } else {
218 // Safety check in case a new access level is added and we forgot to update this
219 checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY);
220 return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
221 accessLevel);
222 }
223 }
224
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800225 /**
226 * Tracks information relevant to a data usage observer.
227 * It will notice when the calling process dies so we can self-expire.
228 */
229 private abstract static class RequestInfo implements IBinder.DeathRecipient {
230 private final NetworkStatsObservers mStatsObserver;
231 protected final DataUsageRequest mRequest;
232 private final Messenger mMessenger;
233 private final IBinder mBinder;
234 protected final int mCallingUid;
235 protected final @NetworkStatsAccess.Level int mAccessLevel;
236 protected NetworkStatsRecorder mRecorder;
237 protected NetworkStatsCollection mCollection;
238
239 RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
240 Messenger messenger, IBinder binder, int callingUid,
241 @NetworkStatsAccess.Level int accessLevel) {
242 mStatsObserver = statsObserver;
243 mRequest = request;
244 mMessenger = messenger;
245 mBinder = binder;
246 mCallingUid = callingUid;
247 mAccessLevel = accessLevel;
248
249 try {
250 mBinder.linkToDeath(this, 0);
251 } catch (RemoteException e) {
252 binderDied();
253 }
254 }
255
256 @Override
257 public void binderDied() {
258 if (LOGV) Slog.v(TAG, "RequestInfo binderDied("
259 + mRequest + ", " + mBinder + ")");
260 mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
261 callCallback(NetworkStatsManager.CALLBACK_RELEASED);
262 }
263
264 @Override
265 public String toString() {
266 return "RequestInfo from uid:" + mCallingUid
267 + " for " + mRequest + " accessLevel:" + mAccessLevel;
268 }
269
270 private void unlinkDeathRecipient() {
271 if (mBinder != null) {
272 mBinder.unlinkToDeath(this, 0);
273 }
274 }
275
276 /**
277 * Update stats given the samples and interface to identity mappings.
278 */
279 private void updateStats(StatsContext statsContext) {
280 if (mRecorder == null) {
281 // First run; establish baseline stats
282 resetRecorder();
283 recordSample(statsContext);
284 return;
285 }
286 recordSample(statsContext);
287
288 if (checkStats()) {
289 resetRecorder();
290 callCallback(NetworkStatsManager.CALLBACK_LIMIT_REACHED);
291 }
292 }
293
294 private void callCallback(int callbackType) {
295 Bundle bundle = new Bundle();
296 bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
297 Message msg = Message.obtain();
298 msg.what = callbackType;
299 msg.setData(bundle);
300 try {
301 if (LOGV) {
302 Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType)
303 + " for " + mRequest);
304 }
305 mMessenger.send(msg);
306 } catch (RemoteException e) {
307 // May occur naturally in the race of binder death.
308 Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
309 }
310 }
311
312 private void resetRecorder() {
313 mRecorder = new NetworkStatsRecorder();
314 mCollection = mRecorder.getSinceBoot();
315 }
316
317 protected abstract boolean checkStats();
318
319 protected abstract void recordSample(StatsContext statsContext);
320
321 private String callbackTypeToName(int callbackType) {
322 switch (callbackType) {
323 case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
324 return "LIMIT_REACHED";
325 case NetworkStatsManager.CALLBACK_RELEASED:
326 return "RELEASED";
327 default:
328 return "UNKNOWN";
329 }
330 }
331 }
332
333 private static class NetworkUsageRequestInfo extends RequestInfo {
334 NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
335 Messenger messenger, IBinder binder, int callingUid,
336 @NetworkStatsAccess.Level int accessLevel) {
337 super(statsObserver, request, messenger, binder, callingUid, accessLevel);
338 }
339
340 @Override
341 protected boolean checkStats() {
Antonio Cansado6965c182016-03-30 11:37:18 -0700342 long bytesSoFar = getTotalBytesForNetwork(mRequest.template);
343 if (LOGV) {
344 Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
345 + mRequest.template);
346 }
347 if (bytesSoFar > mRequest.thresholdInBytes) {
348 return true;
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800349 }
350 return false;
351 }
352
353 @Override
354 protected void recordSample(StatsContext statsContext) {
355 // Recorder does not need to be locked in this context since only the handler
Jeff Davidson4ff3bcf2016-06-15 13:31:52 -0700356 // thread will update it. We pass a null VPN array because usage is aggregated by uid
357 // for this snapshot, so VPN traffic can't be reattributed to responsible apps.
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800358 mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
Jeff Davidson4ff3bcf2016-06-15 13:31:52 -0700359 null /* vpnArray */, statsContext.mCurrentTime);
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800360 }
361
362 /**
363 * Reads stats matching the given template. {@link NetworkStatsCollection} will aggregate
364 * over all buckets, which in this case should be only one since we built it big enough
365 * that it will outlive the caller. If it doesn't, then there will be multiple buckets.
366 */
367 private long getTotalBytesForNetwork(NetworkTemplate template) {
368 NetworkStats stats = mCollection.getSummary(template,
369 Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
370 mAccessLevel, mCallingUid);
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800371 return stats.getTotalBytes();
372 }
373 }
374
375 private static class UserUsageRequestInfo extends RequestInfo {
376 UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
377 Messenger messenger, IBinder binder, int callingUid,
378 @NetworkStatsAccess.Level int accessLevel) {
379 super(statsObserver, request, messenger, binder, callingUid, accessLevel);
380 }
381
382 @Override
383 protected boolean checkStats() {
Antonio Cansado6965c182016-03-30 11:37:18 -0700384 int[] uidsToMonitor = mCollection.getRelevantUids(mAccessLevel, mCallingUid);
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800385
Antonio Cansado6965c182016-03-30 11:37:18 -0700386 for (int i = 0; i < uidsToMonitor.length; i++) {
387 long bytesSoFar = getTotalBytesForNetworkUid(mRequest.template, uidsToMonitor[i]);
Antonio Cansado6965c182016-03-30 11:37:18 -0700388 if (bytesSoFar > mRequest.thresholdInBytes) {
389 return true;
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800390 }
391 }
392 return false;
393 }
394
395 @Override
396 protected void recordSample(StatsContext statsContext) {
397 // Recorder does not need to be locked in this context since only the handler
Jeff Davidson4ff3bcf2016-06-15 13:31:52 -0700398 // thread will update it. We pass the VPN info so VPN traffic is reattributed to
399 // responsible apps.
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800400 mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
401 statsContext.mVpnArray, statsContext.mCurrentTime);
402 }
403
404 /**
405 * Reads all stats matching the given template and uid. Ther history will likely only
406 * contain one bucket per ident since we build it big enough that it will outlive the
407 * caller lifetime.
408 */
409 private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) {
410 try {
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600411 NetworkStatsHistory history = mCollection.getHistory(template, null, uid,
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800412 NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
413 NetworkStatsHistory.FIELD_ALL,
414 Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
415 mAccessLevel, mCallingUid);
416 return history.getTotalBytes();
417 } catch (SecurityException e) {
418 if (LOGV) {
419 Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
420 + uid);
421 }
422 return 0;
423 }
424 }
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800425 }
426
427 private static class StatsContext {
428 NetworkStats mXtSnapshot;
429 NetworkStats mUidSnapshot;
430 ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
431 ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
432 VpnInfo[] mVpnArray;
433 long mCurrentTime;
434
435 StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
436 ArrayMap<String, NetworkIdentitySet> activeIfaces,
437 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
438 VpnInfo[] vpnArray, long currentTime) {
439 mXtSnapshot = xtSnapshot;
440 mUidSnapshot = uidSnapshot;
441 mActiveIfaces = activeIfaces;
442 mActiveUidIfaces = activeUidIfaces;
443 mVpnArray = vpnArray;
444 mCurrentTime = currentTime;
445 }
446 }
447}