blob: 2f55562bc060cfeaae9aca2638811d09e217c3c2 [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;
20import static com.android.internal.util.Preconditions.checkArgument;
21
22import android.app.usage.NetworkStatsManager;
23import android.net.DataUsageRequest;
24import android.net.NetworkStats;
25import android.net.NetworkStats.NonMonotonicObserver;
26import android.net.NetworkStatsHistory;
27import android.net.NetworkTemplate;
28import android.os.Binder;
29import android.os.Bundle;
30import android.os.Looper;
31import android.os.Message;
32import android.os.Messenger;
33import android.os.Handler;
34import android.os.HandlerThread;
35import android.os.IBinder;
36import android.os.Process;
37import android.os.RemoteException;
38import android.util.ArrayMap;
39import android.util.IntArray;
40import android.util.SparseArray;
41import android.util.Slog;
42
43import com.android.internal.annotations.VisibleForTesting;
44import com.android.internal.net.VpnInfo;
45
46import java.util.concurrent.atomic.AtomicInteger;
47
48/**
49 * Manages observers of {@link NetworkStats}. Allows observers to be notified when
50 * data usage has been reported in {@link NetworkStatsService}. An observer can set
51 * a threshold of how much data it cares about to be notified.
52 */
53class NetworkStatsObservers {
54 private static final String TAG = "NetworkStatsObservers";
55 private static final boolean LOGV = true;
56
57 private static final long MIN_THRESHOLD_BYTES = 2 * MB_IN_BYTES;
58
59 private static final int MSG_REGISTER = 1;
60 private static final int MSG_UNREGISTER = 2;
61 private static final int MSG_UPDATE_STATS = 3;
62
63 // All access to this map must be done from the handler thread.
64 // indexed by DataUsageRequest#requestId
65 private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
66
67 // Sequence number of DataUsageRequests
68 private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
69
70 // Lazily instantiated when an observer is registered.
71 private Handler mHandler;
72
73 /**
74 * Creates a wrapper that contains the caller context and a normalized request.
75 * The request should be returned to the caller app, and the wrapper should be sent to this
76 * object through #addObserver by the service handler.
77 *
78 * <p>It will register the observer asynchronously, so it is safe to call from any thread.
79 *
80 * @return the normalized request wrapped within {@link RequestInfo}.
81 */
82 public DataUsageRequest register(DataUsageRequest inputRequest, Messenger messenger,
83 IBinder binder, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
84 checkVisibilityUids(callingUid, accessLevel, inputRequest.uids);
85
86 DataUsageRequest request = buildRequest(inputRequest);
87 RequestInfo requestInfo = buildRequestInfo(request, messenger, binder, callingUid,
88 accessLevel);
89
90 if (LOGV) Slog.v(TAG, "Registering observer for " + request);
91 getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
92 return request;
93 }
94
95 /**
96 * Unregister a data usage observer.
97 *
98 * <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
99 */
100 public void unregister(DataUsageRequest request, int callingUid) {
101 getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
102 request));
103 }
104
105 /**
106 * Updates data usage statistics of registered observers and notifies if limits are reached.
107 *
108 * <p>It will update stats asynchronously, so it is safe to call from any thread.
109 */
110 public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
111 ArrayMap<String, NetworkIdentitySet> activeIfaces,
112 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
113 VpnInfo[] vpnArray, long currentTime) {
114 StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
115 activeUidIfaces, vpnArray, currentTime);
116 getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
117 }
118
119 private Handler getHandler() {
120 if (mHandler == null) {
121 synchronized (this) {
122 if (mHandler == null) {
123 if (LOGV) Slog.v(TAG, "Creating handler");
124 mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
125 }
126 }
127 }
128 return mHandler;
129 }
130
131 @VisibleForTesting
132 protected Looper getHandlerLooperLocked() {
133 HandlerThread handlerThread = new HandlerThread(TAG);
134 handlerThread.start();
135 return handlerThread.getLooper();
136 }
137
138 private Handler.Callback mHandlerCallback = new Handler.Callback() {
139 @Override
140 public boolean handleMessage(Message msg) {
141 switch (msg.what) {
142 case MSG_REGISTER: {
143 handleRegister((RequestInfo) msg.obj);
144 return true;
145 }
146 case MSG_UNREGISTER: {
147 handleUnregister((DataUsageRequest) msg.obj, msg.arg1 /* callingUid */);
148 return true;
149 }
150 case MSG_UPDATE_STATS: {
151 handleUpdateStats((StatsContext) msg.obj);
152 return true;
153 }
154 default: {
155 return false;
156 }
157 }
158 }
159 };
160
161 /**
162 * Adds a {@link RequestInfo} as an observer.
163 * Should only be called from the handler thread otherwise there will be a race condition
164 * on mDataUsageRequests.
165 */
166 private void handleRegister(RequestInfo requestInfo) {
167 mDataUsageRequests.put(requestInfo.mRequest.requestId, requestInfo);
168 }
169
170 /**
171 * Removes a {@link DataUsageRequest} if the calling uid is authorized.
172 * Should only be called from the handler thread otherwise there will be a race condition
173 * on mDataUsageRequests.
174 */
175 private void handleUnregister(DataUsageRequest request, int callingUid) {
176 RequestInfo requestInfo;
177 requestInfo = mDataUsageRequests.get(request.requestId);
178 if (requestInfo == null) {
179 if (LOGV) Slog.v(TAG, "Trying to unregister unknown request " + request);
180 return;
181 }
182 if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
183 Slog.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
184 return;
185 }
186
187 if (LOGV) Slog.v(TAG, "Unregistering " + request);
188 mDataUsageRequests.remove(request.requestId);
189 requestInfo.unlinkDeathRecipient();
190 requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
191 }
192
193 private void handleUpdateStats(StatsContext statsContext) {
194 if (mDataUsageRequests.size() == 0) {
195 if (LOGV) Slog.v(TAG, "No registered listeners of data usage");
196 return;
197 }
198
199 if (LOGV) Slog.v(TAG, "Checking if any registered observer needs to be notified");
200 for (int i = 0; i < mDataUsageRequests.size(); i++) {
201 RequestInfo requestInfo = mDataUsageRequests.valueAt(i);
202 requestInfo.updateStats(statsContext);
203 }
204 }
205
206 private DataUsageRequest buildRequest(DataUsageRequest request) {
207 // Cap the minimum threshold to a safe default to avoid too many callbacks
208 long thresholdInBytes = Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes);
209 if (thresholdInBytes < request.thresholdInBytes) {
210 Slog.w(TAG, "Threshold was too low for " + request
211 + ". Overriding to a safer default of " + thresholdInBytes + " bytes");
212 }
213 return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
214 request.templates, request.uids, thresholdInBytes);
215 }
216
217 private RequestInfo buildRequestInfo(DataUsageRequest request,
218 Messenger messenger, IBinder binder, int callingUid,
219 @NetworkStatsAccess.Level int accessLevel) {
220 if (accessLevel <= NetworkStatsAccess.Level.USER
221 || request.uids != null && request.uids.length > 0) {
222 return new UserUsageRequestInfo(this, request, messenger, binder, callingUid,
223 accessLevel);
224 } else {
225 // Safety check in case a new access level is added and we forgot to update this
226 checkArgument(accessLevel >= NetworkStatsAccess.Level.DEVICESUMMARY);
227 return new NetworkUsageRequestInfo(this, request, messenger, binder, callingUid,
228 accessLevel);
229 }
230 }
231
232 private void checkVisibilityUids(int callingUid, @NetworkStatsAccess.Level int accessLevel,
233 int[] uids) {
234 if (uids == null) {
235 return;
236 }
237 for (int i = 0; i < uids.length; i++) {
238 if (!NetworkStatsAccess.isAccessibleToUser(uids[i], callingUid, accessLevel)) {
239 throw new SecurityException("Caller " + callingUid + " cannot monitor network stats"
240 + " for uid " + uids[i] + " with accessLevel " + accessLevel);
241 }
242 }
243 }
244
245 /**
246 * Tracks information relevant to a data usage observer.
247 * It will notice when the calling process dies so we can self-expire.
248 */
249 private abstract static class RequestInfo implements IBinder.DeathRecipient {
250 private final NetworkStatsObservers mStatsObserver;
251 protected final DataUsageRequest mRequest;
252 private final Messenger mMessenger;
253 private final IBinder mBinder;
254 protected final int mCallingUid;
255 protected final @NetworkStatsAccess.Level int mAccessLevel;
256 protected NetworkStatsRecorder mRecorder;
257 protected NetworkStatsCollection mCollection;
258
259 RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
260 Messenger messenger, IBinder binder, int callingUid,
261 @NetworkStatsAccess.Level int accessLevel) {
262 mStatsObserver = statsObserver;
263 mRequest = request;
264 mMessenger = messenger;
265 mBinder = binder;
266 mCallingUid = callingUid;
267 mAccessLevel = accessLevel;
268
269 try {
270 mBinder.linkToDeath(this, 0);
271 } catch (RemoteException e) {
272 binderDied();
273 }
274 }
275
276 @Override
277 public void binderDied() {
278 if (LOGV) Slog.v(TAG, "RequestInfo binderDied("
279 + mRequest + ", " + mBinder + ")");
280 mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
281 callCallback(NetworkStatsManager.CALLBACK_RELEASED);
282 }
283
284 @Override
285 public String toString() {
286 return "RequestInfo from uid:" + mCallingUid
287 + " for " + mRequest + " accessLevel:" + mAccessLevel;
288 }
289
290 private void unlinkDeathRecipient() {
291 if (mBinder != null) {
292 mBinder.unlinkToDeath(this, 0);
293 }
294 }
295
296 /**
297 * Update stats given the samples and interface to identity mappings.
298 */
299 private void updateStats(StatsContext statsContext) {
300 if (mRecorder == null) {
301 // First run; establish baseline stats
302 resetRecorder();
303 recordSample(statsContext);
304 return;
305 }
306 recordSample(statsContext);
307
308 if (checkStats()) {
309 resetRecorder();
310 callCallback(NetworkStatsManager.CALLBACK_LIMIT_REACHED);
311 }
312 }
313
314 private void callCallback(int callbackType) {
315 Bundle bundle = new Bundle();
316 bundle.putParcelable(DataUsageRequest.PARCELABLE_KEY, mRequest);
317 Message msg = Message.obtain();
318 msg.what = callbackType;
319 msg.setData(bundle);
320 try {
321 if (LOGV) {
322 Slog.v(TAG, "sending notification " + callbackTypeToName(callbackType)
323 + " for " + mRequest);
324 }
325 mMessenger.send(msg);
326 } catch (RemoteException e) {
327 // May occur naturally in the race of binder death.
328 Slog.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
329 }
330 }
331
332 private void resetRecorder() {
333 mRecorder = new NetworkStatsRecorder();
334 mCollection = mRecorder.getSinceBoot();
335 }
336
337 protected abstract boolean checkStats();
338
339 protected abstract void recordSample(StatsContext statsContext);
340
341 private String callbackTypeToName(int callbackType) {
342 switch (callbackType) {
343 case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
344 return "LIMIT_REACHED";
345 case NetworkStatsManager.CALLBACK_RELEASED:
346 return "RELEASED";
347 default:
348 return "UNKNOWN";
349 }
350 }
351 }
352
353 private static class NetworkUsageRequestInfo extends RequestInfo {
354 NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
355 Messenger messenger, IBinder binder, int callingUid,
356 @NetworkStatsAccess.Level int accessLevel) {
357 super(statsObserver, request, messenger, binder, callingUid, accessLevel);
358 }
359
360 @Override
361 protected boolean checkStats() {
362 for (int i = 0; i < mRequest.templates.length; i++) {
363 long bytesSoFar = getTotalBytesForNetwork(mRequest.templates[i]);
364 if (LOGV) {
365 Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
366 + mRequest.templates[i]);
367 }
368 if (bytesSoFar > mRequest.thresholdInBytes) {
369 return true;
370 }
371 }
372 return false;
373 }
374
375 @Override
376 protected void recordSample(StatsContext statsContext) {
377 // Recorder does not need to be locked in this context since only the handler
378 // thread will update it
379 mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
380 statsContext.mVpnArray, statsContext.mCurrentTime);
381 }
382
383 /**
384 * Reads stats matching the given template. {@link NetworkStatsCollection} will aggregate
385 * over all buckets, which in this case should be only one since we built it big enough
386 * that it will outlive the caller. If it doesn't, then there will be multiple buckets.
387 */
388 private long getTotalBytesForNetwork(NetworkTemplate template) {
389 NetworkStats stats = mCollection.getSummary(template,
390 Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
391 mAccessLevel, mCallingUid);
392 if (LOGV) {
393 Slog.v(TAG, "Netstats for " + template + ": " + stats);
394 }
395 return stats.getTotalBytes();
396 }
397 }
398
399 private static class UserUsageRequestInfo extends RequestInfo {
400 UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
401 Messenger messenger, IBinder binder, int callingUid,
402 @NetworkStatsAccess.Level int accessLevel) {
403 super(statsObserver, request, messenger, binder, callingUid, accessLevel);
404 }
405
406 @Override
407 protected boolean checkStats() {
408 int[] uidsToMonitor = getUidsToMonitor();
409
410 for (int i = 0; i < mRequest.templates.length; i++) {
411 for (int j = 0; j < uidsToMonitor.length; j++) {
412 long bytesSoFar = getTotalBytesForNetworkUid(mRequest.templates[i],
413 uidsToMonitor[j]);
414
415 if (LOGV) {
416 Slog.v(TAG, bytesSoFar + " bytes so far since notification for "
417 + mRequest.templates[i] + " for uid=" + uidsToMonitor[j]);
418 }
419 if (bytesSoFar > mRequest.thresholdInBytes) {
420 return true;
421 }
422 }
423 }
424 return false;
425 }
426
427 @Override
428 protected void recordSample(StatsContext statsContext) {
429 // Recorder does not need to be locked in this context since only the handler
430 // thread will update it
431 mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
432 statsContext.mVpnArray, statsContext.mCurrentTime);
433 }
434
435 /**
436 * Reads all stats matching the given template and uid. Ther history will likely only
437 * contain one bucket per ident since we build it big enough that it will outlive the
438 * caller lifetime.
439 */
440 private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) {
441 try {
442 NetworkStatsHistory history = mCollection.getHistory(template, uid,
443 NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
444 NetworkStatsHistory.FIELD_ALL,
445 Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
446 mAccessLevel, mCallingUid);
447 return history.getTotalBytes();
448 } catch (SecurityException e) {
449 if (LOGV) {
450 Slog.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
451 + uid);
452 }
453 return 0;
454 }
455 }
456
457 private int[] getUidsToMonitor() {
458 if (mRequest.uids == null || mRequest.uids.length == 0) {
459 return mCollection.getRelevantUids(mAccessLevel, mCallingUid);
460 }
461 // Pick only uids from the request that are currently accessible to the user
462 IntArray accessibleUids = new IntArray(mRequest.uids.length);
463 for (int i = 0; i < mRequest.uids.length; i++) {
464 int uid = mRequest.uids[i];
465 if (NetworkStatsAccess.isAccessibleToUser(uid, mCallingUid, mAccessLevel)) {
466 accessibleUids.add(uid);
467 }
468 }
469 return accessibleUids.toArray();
470 }
471 }
472
473 private static class StatsContext {
474 NetworkStats mXtSnapshot;
475 NetworkStats mUidSnapshot;
476 ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
477 ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
478 VpnInfo[] mVpnArray;
479 long mCurrentTime;
480
481 StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
482 ArrayMap<String, NetworkIdentitySet> activeIfaces,
483 ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
484 VpnInfo[] vpnArray, long currentTime) {
485 mXtSnapshot = xtSnapshot;
486 mUidSnapshot = uidSnapshot;
487 mActiveIfaces = activeIfaces;
488 mActiveUidIfaces = activeUidIfaces;
489 mVpnArray = vpnArray;
490 mCurrentTime = currentTime;
491 }
492 }
493}