1 /**
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 
17 package android.app.usage;
18 
19 import static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.annotation.Nullable;
22 import android.annotation.SystemService;
23 import android.annotation.TestApi;
24 import android.app.usage.NetworkStats.Bucket;
25 import android.content.Context;
26 import android.net.ConnectivityManager;
27 import android.net.DataUsageRequest;
28 import android.net.INetworkStatsService;
29 import android.net.NetworkIdentity;
30 import android.net.NetworkTemplate;
31 import android.os.Binder;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.Messenger;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.os.ServiceManager.ServiceNotFoundException;
39 import android.util.DataUnit;
40 import android.util.Log;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 /**
45  * Provides access to network usage history and statistics. Usage data is collected in
46  * discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details.
47  * <p />
48  * Queries can define a time interval in the form of start and end timestamps (Long.MIN_VALUE and
49  * Long.MAX_VALUE can be used to simulate open ended intervals). By default, apps can only obtain
50  * data about themselves. See the below note for special cases in which apps can obtain data about
51  * other applications.
52  * <h3>
53  * Summary queries
54  * </h3>
55  * {@link #querySummaryForDevice} <p />
56  * {@link #querySummaryForUser} <p />
57  * {@link #querySummary} <p />
58  * These queries aggregate network usage across the whole interval. Therefore there will be only one
59  * bucket for a particular key, state, metered and roaming combination. In case of the user-wide
60  * and device-wide summaries a single bucket containing the totalised network usage is returned.
61  * <h3>
62  * History queries
63  * </h3>
64  * {@link #queryDetailsForUid} <p />
65  * {@link #queryDetails} <p />
66  * These queries do not aggregate over time but do aggregate over state, metered and roaming.
67  * Therefore there can be multiple buckets for a particular key. However, all Buckets will have
68  * {@code state} {@link NetworkStats.Bucket#STATE_ALL},
69  * {@code defaultNetwork} {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
70  * {@code metered } {@link NetworkStats.Bucket#METERED_ALL},
71  * {@code roaming} {@link NetworkStats.Bucket#ROAMING_ALL}.
72  * <p />
73  * <b>NOTE:</b> Calling {@link #querySummaryForDevice} or accessing stats for apps other than the
74  * calling app requires the permission {@link android.Manifest.permission#PACKAGE_USAGE_STATS},
75  * which is a system-level permission and will not be granted to third-party apps. However,
76  * declaring the permission implies intention to use the API and the user of the device can grant
77  * permission through the Settings application.
78  * <p />
79  * Profile owner apps are automatically granted permission to query data on the profile they manage
80  * (that is, for any query except {@link #querySummaryForDevice}). Device owner apps and carrier-
81  * privileged apps likewise get access to usage data for all users on the device.
82  * <p />
83  * In addition to tethering usage, usage by removed users and apps, and usage by the system
84  * is also included in the results for callers with one of these higher levels of access.
85  * <p />
86  * <b>NOTE:</b> Prior to API level {@value android.os.Build.VERSION_CODES#N}, all calls to these APIs required
87  * the above permission, even to access an app's own data usage, and carrier-privileged apps were
88  * not included.
89  */
90 @SystemService(Context.NETWORK_STATS_SERVICE)
91 public class NetworkStatsManager {
92     private static final String TAG = "NetworkStatsManager";
93     private static final boolean DBG = false;
94 
95     /** @hide */
96     public static final int CALLBACK_LIMIT_REACHED = 0;
97     /** @hide */
98     public static final int CALLBACK_RELEASED = 1;
99 
100     /**
101      * Minimum data usage threshold for registering usage callbacks.
102      *
103      * Requests registered with a threshold lower than this will only be triggered once this minimum
104      * is reached.
105      * @hide
106      */
107     public static final long MIN_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(2);
108 
109     private final Context mContext;
110     private final INetworkStatsService mService;
111 
112     /** @hide */
113     public static final int FLAG_POLL_ON_OPEN = 1 << 0;
114     /** @hide */
115     public static final int FLAG_POLL_FORCE = 1 << 1;
116     /** @hide */
117     public static final int FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN = 1 << 2;
118 
119     private int mFlags;
120 
121     /**
122      * {@hide}
123      */
NetworkStatsManager(Context context)124     public NetworkStatsManager(Context context) throws ServiceNotFoundException {
125         this(context, INetworkStatsService.Stub.asInterface(
126                 ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)));
127     }
128 
129     /** @hide */
130     @VisibleForTesting
NetworkStatsManager(Context context, INetworkStatsService service)131     public NetworkStatsManager(Context context, INetworkStatsService service) {
132         mContext = context;
133         mService = service;
134         setPollOnOpen(true);
135     }
136 
137     /** @hide */
setPollOnOpen(boolean pollOnOpen)138     public void setPollOnOpen(boolean pollOnOpen) {
139         if (pollOnOpen) {
140             mFlags |= FLAG_POLL_ON_OPEN;
141         } else {
142             mFlags &= ~FLAG_POLL_ON_OPEN;
143         }
144     }
145 
146     /** @hide */
147     @TestApi
setPollForce(boolean pollForce)148     public void setPollForce(boolean pollForce) {
149         if (pollForce) {
150             mFlags |= FLAG_POLL_FORCE;
151         } else {
152             mFlags &= ~FLAG_POLL_FORCE;
153         }
154     }
155 
156     /** @hide */
setAugmentWithSubscriptionPlan(boolean augmentWithSubscriptionPlan)157     public void setAugmentWithSubscriptionPlan(boolean augmentWithSubscriptionPlan) {
158         if (augmentWithSubscriptionPlan) {
159             mFlags |= FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN;
160         } else {
161             mFlags &= ~FLAG_AUGMENT_WITH_SUBSCRIPTION_PLAN;
162         }
163     }
164 
165     /** @hide */
querySummaryForDevice(NetworkTemplate template, long startTime, long endTime)166     public Bucket querySummaryForDevice(NetworkTemplate template,
167             long startTime, long endTime) throws SecurityException, RemoteException {
168         Bucket bucket = null;
169         NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime,
170                 mService);
171         bucket = stats.getDeviceSummaryForNetwork();
172 
173         stats.close();
174         return bucket;
175     }
176 
177     /**
178      * Query network usage statistics summaries. Result is summarised data usage for the whole
179      * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and
180      * roaming. This means the bucket's start and end timestamp are going to be the same as the
181      * 'startTime' and 'endTime' parameters. State is going to be
182      * {@link NetworkStats.Bucket#STATE_ALL}, uid {@link NetworkStats.Bucket#UID_ALL},
183      * tag {@link NetworkStats.Bucket#TAG_NONE},
184      * default network {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
185      * metered {@link NetworkStats.Bucket#METERED_ALL},
186      * and roaming {@link NetworkStats.Bucket#ROAMING_ALL}.
187      *
188      * @param networkType As defined in {@link ConnectivityManager}, e.g.
189      *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
190      *            etc.
191      * @param subscriberId If applicable, the subscriber id of the network interface.
192      * @param startTime Start of period. Defined in terms of "Unix time", see
193      *            {@link java.lang.System#currentTimeMillis}.
194      * @param endTime End of period. Defined in terms of "Unix time", see
195      *            {@link java.lang.System#currentTimeMillis}.
196      * @return Bucket object or null if permissions are insufficient or error happened during
197      *         statistics collection.
198      */
querySummaryForDevice(int networkType, String subscriberId, long startTime, long endTime)199     public Bucket querySummaryForDevice(int networkType, String subscriberId,
200             long startTime, long endTime) throws SecurityException, RemoteException {
201         NetworkTemplate template;
202         try {
203             template = createTemplate(networkType, subscriberId);
204         } catch (IllegalArgumentException e) {
205             if (DBG) Log.e(TAG, "Cannot create template", e);
206             return null;
207         }
208 
209         return querySummaryForDevice(template, startTime, endTime);
210     }
211 
212     /**
213      * Query network usage statistics summaries. Result is summarised data usage for all uids
214      * belonging to calling user. Result is a single Bucket aggregated over time, state and uid.
215      * This means the bucket's start and end timestamp are going to be the same as the 'startTime'
216      * and 'endTime' parameters. State is going to be {@link NetworkStats.Bucket#STATE_ALL},
217      * uid {@link NetworkStats.Bucket#UID_ALL}, tag {@link NetworkStats.Bucket#TAG_NONE},
218      * metered {@link NetworkStats.Bucket#METERED_ALL}, and roaming
219      * {@link NetworkStats.Bucket#ROAMING_ALL}.
220      *
221      * @param networkType As defined in {@link ConnectivityManager}, e.g.
222      *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
223      *            etc.
224      * @param subscriberId If applicable, the subscriber id of the network interface.
225      * @param startTime Start of period. Defined in terms of "Unix time", see
226      *            {@link java.lang.System#currentTimeMillis}.
227      * @param endTime End of period. Defined in terms of "Unix time", see
228      *            {@link java.lang.System#currentTimeMillis}.
229      * @return Bucket object or null if permissions are insufficient or error happened during
230      *         statistics collection.
231      */
querySummaryForUser(int networkType, String subscriberId, long startTime, long endTime)232     public Bucket querySummaryForUser(int networkType, String subscriberId, long startTime,
233             long endTime) throws SecurityException, RemoteException {
234         NetworkTemplate template;
235         try {
236             template = createTemplate(networkType, subscriberId);
237         } catch (IllegalArgumentException e) {
238             if (DBG) Log.e(TAG, "Cannot create template", e);
239             return null;
240         }
241 
242         NetworkStats stats;
243         stats = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
244         stats.startSummaryEnumeration();
245 
246         stats.close();
247         return stats.getSummaryAggregate();
248     }
249 
250     /**
251      * Query network usage statistics summaries. Result filtered to include only uids belonging to
252      * calling user. Result is aggregated over time, hence all buckets will have the same start and
253      * end timestamps. Not aggregated over state, uid, default network, metered, or roaming. This
254      * means buckets' start and end timestamps are going to be the same as the 'startTime' and
255      * 'endTime' parameters. State, uid, metered, and roaming are going to vary, and tag is going to
256      * be the same.
257      *
258      * @param networkType As defined in {@link ConnectivityManager}, e.g.
259      *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
260      *            etc.
261      * @param subscriberId If applicable, the subscriber id of the network interface.
262      * @param startTime Start of period. Defined in terms of "Unix time", see
263      *            {@link java.lang.System#currentTimeMillis}.
264      * @param endTime End of period. Defined in terms of "Unix time", see
265      *            {@link java.lang.System#currentTimeMillis}.
266      * @return Statistics object or null if permissions are insufficient or error happened during
267      *         statistics collection.
268      */
querySummary(int networkType, String subscriberId, long startTime, long endTime)269     public NetworkStats querySummary(int networkType, String subscriberId, long startTime,
270             long endTime) throws SecurityException, RemoteException {
271         NetworkTemplate template;
272         try {
273             template = createTemplate(networkType, subscriberId);
274         } catch (IllegalArgumentException e) {
275             if (DBG) Log.e(TAG, "Cannot create template", e);
276             return null;
277         }
278 
279         NetworkStats result;
280         result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
281         result.startSummaryEnumeration();
282 
283         return result;
284     }
285 
286     /**
287      * Query network usage statistics details for a given uid.
288      *
289      * #see queryDetailsForUidTagState(int, String, long, long, int, int, int)
290      */
queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid)291     public NetworkStats queryDetailsForUid(int networkType, String subscriberId,
292             long startTime, long endTime, int uid) throws SecurityException {
293         return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
294             NetworkStats.Bucket.TAG_NONE, NetworkStats.Bucket.STATE_ALL);
295     }
296 
297     /**
298      * Query network usage statistics details for a given uid and tag.
299      *
300      * #see queryDetailsForUidTagState(int, String, long, long, int, int, int)
301      */
queryDetailsForUidTag(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag)302     public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId,
303             long startTime, long endTime, int uid, int tag) throws SecurityException {
304         return queryDetailsForUidTagState(networkType, subscriberId, startTime, endTime, uid,
305             tag, NetworkStats.Bucket.STATE_ALL);
306     }
307 
308     /**
309      * Query network usage statistics details for a given uid, tag, and state. Only usable for uids
310      * belonging to calling user. Result is not aggregated over time. This means buckets' start and
311      * end timestamps are going to be between 'startTime' and 'endTime' parameters. The uid is going
312      * to be the same as the 'uid' parameter, the tag the same as the 'tag' parameter, and the state
313      * the same as the 'state' parameter.
314      * defaultNetwork is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
315      * metered is going to be {@link NetworkStats.Bucket#METERED_ALL}, and
316      * roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
317      * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
318      * interpolate across partial buckets. Since bucket length is in the order of hours, this
319      * method cannot be used to measure data usage on a fine grained time scale.
320      *
321      * @param networkType As defined in {@link ConnectivityManager}, e.g.
322      *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
323      *            etc.
324      * @param subscriberId If applicable, the subscriber id of the network interface.
325      * @param startTime Start of period. Defined in terms of "Unix time", see
326      *            {@link java.lang.System#currentTimeMillis}.
327      * @param endTime End of period. Defined in terms of "Unix time", see
328      *            {@link java.lang.System#currentTimeMillis}.
329      * @param uid UID of app
330      * @param tag TAG of interest. Use {@link NetworkStats.Bucket#TAG_NONE} for no tags.
331      * @param state state of interest. Use {@link NetworkStats.Bucket#STATE_ALL} to aggregate
332      *            traffic from all states.
333      * @return Statistics object or null if an error happened during statistics collection.
334      * @throws SecurityException if permissions are insufficient to read network statistics.
335      */
queryDetailsForUidTagState(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag, int state)336     public NetworkStats queryDetailsForUidTagState(int networkType, String subscriberId,
337             long startTime, long endTime, int uid, int tag, int state) throws SecurityException {
338         NetworkTemplate template;
339         template = createTemplate(networkType, subscriberId);
340 
341         NetworkStats result;
342         try {
343             result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
344             result.startHistoryEnumeration(uid, tag, state);
345         } catch (RemoteException e) {
346             Log.e(TAG, "Error while querying stats for uid=" + uid + " tag=" + tag
347                     + " state=" + state, e);
348             return null;
349         }
350 
351         return result;
352     }
353 
354     /**
355      * Query network usage statistics details. Result filtered to include only uids belonging to
356      * calling user. Result is aggregated over state but not aggregated over time, uid, tag,
357      * metered, nor roaming. This means buckets' start and end timestamps are going to be between
358      * 'startTime' and 'endTime' parameters. State is going to be
359      * {@link NetworkStats.Bucket#STATE_ALL}, uid will vary,
360      * tag {@link NetworkStats.Bucket#TAG_NONE},
361      * default network is going to be {@link NetworkStats.Bucket#DEFAULT_NETWORK_ALL},
362      * metered is going to be {@link NetworkStats.Bucket#METERED_ALL},
363      * and roaming is going to be {@link NetworkStats.Bucket#ROAMING_ALL}.
364      * <p>Only includes buckets that atomically occur in the inclusive time range. Doesn't
365      * interpolate across partial buckets. Since bucket length is in the order of hours, this
366      * method cannot be used to measure data usage on a fine grained time scale.
367      *
368      * @param networkType As defined in {@link ConnectivityManager}, e.g.
369      *            {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI}
370      *            etc.
371      * @param subscriberId If applicable, the subscriber id of the network interface.
372      * @param startTime Start of period. Defined in terms of "Unix time", see
373      *            {@link java.lang.System#currentTimeMillis}.
374      * @param endTime End of period. Defined in terms of "Unix time", see
375      *            {@link java.lang.System#currentTimeMillis}.
376      * @return Statistics object or null if permissions are insufficient or error happened during
377      *         statistics collection.
378      */
queryDetails(int networkType, String subscriberId, long startTime, long endTime)379     public NetworkStats queryDetails(int networkType, String subscriberId, long startTime,
380             long endTime) throws SecurityException, RemoteException {
381         NetworkTemplate template;
382         try {
383             template = createTemplate(networkType, subscriberId);
384         } catch (IllegalArgumentException e) {
385             if (DBG) Log.e(TAG, "Cannot create template", e);
386             return null;
387         }
388 
389         NetworkStats result;
390         result = new NetworkStats(mContext, template, mFlags, startTime, endTime, mService);
391         result.startUserUidEnumeration();
392         return result;
393     }
394 
395     /** @hide */
registerUsageCallback(NetworkTemplate template, int networkType, long thresholdBytes, UsageCallback callback, @Nullable Handler handler)396     public void registerUsageCallback(NetworkTemplate template, int networkType,
397             long thresholdBytes, UsageCallback callback, @Nullable Handler handler) {
398         checkNotNull(callback, "UsageCallback cannot be null");
399 
400         final Looper looper;
401         if (handler == null) {
402             looper = Looper.myLooper();
403         } else {
404             looper = handler.getLooper();
405         }
406 
407         DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET,
408                 template, thresholdBytes);
409         try {
410             CallbackHandler callbackHandler = new CallbackHandler(looper, networkType,
411                     template.getSubscriberId(), callback);
412             callback.request = mService.registerUsageCallback(
413                     mContext.getOpPackageName(), request, new Messenger(callbackHandler),
414                     new Binder());
415             if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request);
416 
417             if (callback.request == null) {
418                 Log.e(TAG, "Request from callback is null; should not happen");
419             }
420         } catch (RemoteException e) {
421             if (DBG) Log.d(TAG, "Remote exception when registering callback");
422             throw e.rethrowFromSystemServer();
423         }
424     }
425 
426     /**
427      * Registers to receive notifications about data usage on specified networks.
428      *
429      * #see registerUsageCallback(int, String[], long, UsageCallback, Handler)
430      */
registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, UsageCallback callback)431     public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes,
432             UsageCallback callback) {
433         registerUsageCallback(networkType, subscriberId, thresholdBytes, callback,
434                 null /* handler */);
435     }
436 
437     /**
438      * Registers to receive notifications about data usage on specified networks.
439      *
440      * <p>The callbacks will continue to be called as long as the process is live or
441      * {@link #unregisterUsageCallback} is called.
442      *
443      * @param networkType Type of network to monitor. Either
444                   {@link ConnectivityManager#TYPE_MOBILE} or {@link ConnectivityManager#TYPE_WIFI}.
445      * @param subscriberId If applicable, the subscriber id of the network interface.
446      * @param thresholdBytes Threshold in bytes to be notified on.
447      * @param callback The {@link UsageCallback} that the system will call when data usage
448      *            has exceeded the specified threshold.
449      * @param handler to dispatch callback events through, otherwise if {@code null} it uses
450      *            the calling thread.
451      */
registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, UsageCallback callback, @Nullable Handler handler)452     public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes,
453             UsageCallback callback, @Nullable Handler handler) {
454         NetworkTemplate template = createTemplate(networkType, subscriberId);
455         if (DBG) {
456             Log.d(TAG, "registerUsageCallback called with: {"
457                 + " networkType=" + networkType
458                 + " subscriberId=" + subscriberId
459                 + " thresholdBytes=" + thresholdBytes
460                 + " }");
461         }
462         registerUsageCallback(template, networkType, thresholdBytes, callback, handler);
463     }
464 
465     /**
466      * Unregisters callbacks on data usage.
467      *
468      * @param callback The {@link UsageCallback} used when registering.
469      */
unregisterUsageCallback(UsageCallback callback)470     public void unregisterUsageCallback(UsageCallback callback) {
471         if (callback == null || callback.request == null
472                 || callback.request.requestId == DataUsageRequest.REQUEST_ID_UNSET) {
473             throw new IllegalArgumentException("Invalid UsageCallback");
474         }
475         try {
476             mService.unregisterUsageRequest(callback.request);
477         } catch (RemoteException e) {
478             if (DBG) Log.d(TAG, "Remote exception when unregistering callback");
479             throw e.rethrowFromSystemServer();
480         }
481     }
482 
483     /**
484      * Base class for usage callbacks. Should be extended by applications wanting notifications.
485      */
486     public static abstract class UsageCallback {
487 
488         /**
489          * Called when data usage has reached the given threshold.
490          */
onThresholdReached(int networkType, String subscriberId)491         public abstract void onThresholdReached(int networkType, String subscriberId);
492 
493         /**
494          * @hide used for internal bookkeeping
495          */
496         private DataUsageRequest request;
497     }
498 
createTemplate(int networkType, String subscriberId)499     private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
500         final NetworkTemplate template;
501         switch (networkType) {
502             case ConnectivityManager.TYPE_MOBILE:
503                 template = subscriberId == null
504                         ? NetworkTemplate.buildTemplateMobileWildcard()
505                         : NetworkTemplate.buildTemplateMobileAll(subscriberId);
506                 break;
507             case ConnectivityManager.TYPE_WIFI:
508                 template = NetworkTemplate.buildTemplateWifiWildcard();
509                 break;
510             default:
511                 throw new IllegalArgumentException("Cannot create template for network type "
512                         + networkType + ", subscriberId '"
513                         + NetworkIdentity.scrubSubscriberId(subscriberId) + "'.");
514         }
515         return template;
516     }
517 
518     private static class CallbackHandler extends Handler {
519         private final int mNetworkType;
520         private final String mSubscriberId;
521         private UsageCallback mCallback;
522 
CallbackHandler(Looper looper, int networkType, String subscriberId, UsageCallback callback)523         CallbackHandler(Looper looper, int networkType, String subscriberId,
524                 UsageCallback callback) {
525             super(looper);
526             mNetworkType = networkType;
527             mSubscriberId = subscriberId;
528             mCallback = callback;
529         }
530 
531         @Override
handleMessage(Message message)532         public void handleMessage(Message message) {
533             DataUsageRequest request =
534                     (DataUsageRequest) getObject(message, DataUsageRequest.PARCELABLE_KEY);
535 
536             switch (message.what) {
537                 case CALLBACK_LIMIT_REACHED: {
538                     if (mCallback != null) {
539                         mCallback.onThresholdReached(mNetworkType, mSubscriberId);
540                     } else {
541                         Log.e(TAG, "limit reached with released callback for " + request);
542                     }
543                     break;
544                 }
545                 case CALLBACK_RELEASED: {
546                     if (DBG) Log.d(TAG, "callback released for " + request);
547                     mCallback = null;
548                     break;
549                 }
550             }
551         }
552 
getObject(Message msg, String key)553         private static Object getObject(Message msg, String key) {
554             return msg.getData().getParcelable(key);
555         }
556     }
557 }
558