• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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 package android.app;
17 
18 import static android.Manifest.permission.DUMP;
19 import static android.Manifest.permission.PACKAGE_USAGE_STATS;
20 
21 import android.annotation.CallbackExecutor;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.content.Context;
27 import android.os.Binder;
28 import android.os.IPullAtomCallback;
29 import android.os.IPullAtomResultReceiver;
30 import android.os.IStatsManagerService;
31 import android.os.RemoteException;
32 import android.os.StatsFrameworkInitializer;
33 import android.util.AndroidException;
34 import android.util.Log;
35 import android.util.StatsEvent;
36 import android.util.StatsEventParcel;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.concurrent.Executor;
44 
45 /**
46  * API for statsd clients to send configurations and retrieve data.
47  *
48  * @hide
49  */
50 @SystemApi
51 public final class StatsManager {
52     private static final String TAG = "StatsManager";
53     private static final boolean DEBUG = false;
54 
55     private static final Object sLock = new Object();
56     private final Context mContext;
57 
58     @GuardedBy("sLock")
59     private IStatsManagerService mStatsManagerService;
60 
61     /**
62      * Long extra of uid that added the relevant stats config.
63      */
64     public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
65     /**
66      * Long extra of the relevant stats config's configKey.
67      */
68     public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
69     /**
70      * Long extra of the relevant statsd_config.proto's Subscription.id.
71      */
72     public static final String EXTRA_STATS_SUBSCRIPTION_ID =
73             "android.app.extra.STATS_SUBSCRIPTION_ID";
74     /**
75      * Long extra of the relevant statsd_config.proto's Subscription.rule_id.
76      */
77     public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
78             "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
79     /**
80      *   List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie.
81      *   Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}.
82      */
83     public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES =
84             "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
85     /**
86      * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
87      * information.
88      */
89     public static final String EXTRA_STATS_DIMENSIONS_VALUE =
90             "android.app.extra.STATS_DIMENSIONS_VALUE";
91     /**
92      * Long array extra of the active configs for the uid that added those configs.
93      */
94     public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS =
95             "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
96 
97     /**
98      * Broadcast Action: Statsd has started.
99      * Configurations and PendingIntents can now be sent to it.
100      */
101     public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
102 
103     // Pull atom callback return codes.
104     /**
105      * Value indicating that this pull was successful and that the result should be used.
106      *
107      **/
108     public static final int PULL_SUCCESS = 0;
109 
110     /**
111      * Value indicating that this pull was unsuccessful and that the result should not be used.
112      **/
113     public static final int PULL_SKIP = 1;
114 
115     /**
116      * @hide
117      **/
118     @VisibleForTesting public static final long DEFAULT_COOL_DOWN_MILLIS = 1_000L; // 1 second.
119 
120     /**
121      * @hide
122      **/
123     @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 2_000L; // 2 seconds.
124 
125     /**
126      * Constructor for StatsManagerClient.
127      *
128      * @hide
129      */
StatsManager(Context context)130     public StatsManager(Context context) {
131         mContext = context;
132     }
133 
134     /**
135      * Adds the given configuration and associates it with the given configKey. If a config with the
136      * given configKey already exists for the caller's uid, it is replaced with the new one.
137      * This call can block on statsd.
138      *
139      * @param configKey An arbitrary integer that allows clients to track the configuration.
140      * @param config    Wire-encoded StatsdConfig proto that specifies metrics (and all
141      *                  dependencies eg, conditions and matchers).
142      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
143      * @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto
144      */
145     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
addConfig(long configKey, byte[] config)146     public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
147         synchronized (sLock) {
148             try {
149                 IStatsManagerService service = getIStatsManagerServiceLocked();
150                 // can throw IllegalArgumentException
151                 service.addConfiguration(configKey, config, mContext.getOpPackageName());
152             } catch (RemoteException e) {
153                 Log.e(TAG, "Failed to connect to statsmanager when adding configuration");
154                 throw new StatsUnavailableException("could not connect", e);
155             } catch (SecurityException e) {
156                 throw new StatsUnavailableException(e.getMessage(), e);
157             } catch (IllegalStateException e) {
158                 Log.e(TAG, "Failed to addConfig in statsmanager");
159                 throw new StatsUnavailableException(e.getMessage(), e);
160             }
161         }
162     }
163 
164     // TODO: Temporary for backwards compatibility. Remove.
165     /**
166      * @deprecated Use {@link #addConfig(long, byte[])}
167      */
168     @Deprecated
169     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
addConfiguration(long configKey, byte[] config)170     public boolean addConfiguration(long configKey, byte[] config) {
171         try {
172             addConfig(configKey, config);
173             return true;
174         } catch (StatsUnavailableException | IllegalArgumentException e) {
175             return false;
176         }
177     }
178 
179     /**
180      * Remove a configuration from logging.
181      *
182      * This call can block on statsd.
183      *
184      * @param configKey Configuration key to remove.
185      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
186      */
187     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
removeConfig(long configKey)188     public void removeConfig(long configKey) throws StatsUnavailableException {
189         synchronized (sLock) {
190             try {
191                 IStatsManagerService service = getIStatsManagerServiceLocked();
192                 service.removeConfiguration(configKey, mContext.getOpPackageName());
193             } catch (RemoteException e) {
194                 Log.e(TAG, "Failed to connect to statsmanager when removing configuration");
195                 throw new StatsUnavailableException("could not connect", e);
196             } catch (SecurityException e) {
197                 throw new StatsUnavailableException(e.getMessage(), e);
198             } catch (IllegalStateException e) {
199                 Log.e(TAG, "Failed to removeConfig in statsmanager");
200                 throw new StatsUnavailableException(e.getMessage(), e);
201             }
202         }
203     }
204 
205     // TODO: Temporary for backwards compatibility. Remove.
206     /**
207      * @deprecated Use {@link #removeConfig(long)}
208      */
209     @Deprecated
210     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
removeConfiguration(long configKey)211     public boolean removeConfiguration(long configKey) {
212         try {
213             removeConfig(configKey);
214             return true;
215         } catch (StatsUnavailableException e) {
216             return false;
217         }
218     }
219 
220     /**
221      * Set the PendingIntent to be used when broadcasting subscriber information to the given
222      * subscriberId within the given config.
223      * <p>
224      * Suppose that the calling uid has added a config with key configKey, and that in this config
225      * it is specified that when a particular anomaly is detected, a broadcast should be sent to
226      * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
227      * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
228      * when the anomaly is detected.
229      * <p>
230      * When statsd sends the broadcast, the PendingIntent will used to send an intent with
231      * information of
232      * {@link #EXTRA_STATS_CONFIG_UID},
233      * {@link #EXTRA_STATS_CONFIG_KEY},
234      * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
235      * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID},
236      * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and
237      * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
238      * <p>
239      * This function can only be called by the owner (uid) of the config. It must be called each
240      * time statsd starts. The config must have been added first (via {@link #addConfig}).
241      * This call can block on statsd.
242      *
243      * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
244      *                      associated with the given subscriberId. May be null, in which case
245      *                      it undoes any previous setting of this subscriberId.
246      * @param configKey     The integer naming the config to which this subscriber is attached.
247      * @param subscriberId  ID of the subscriber, as used in the config.
248      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
249      */
250     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
setBroadcastSubscriber( PendingIntent pendingIntent, long configKey, long subscriberId)251     public void setBroadcastSubscriber(
252             PendingIntent pendingIntent, long configKey, long subscriberId)
253             throws StatsUnavailableException {
254         synchronized (sLock) {
255             try {
256                 IStatsManagerService service = getIStatsManagerServiceLocked();
257                 if (pendingIntent != null) {
258                     service.setBroadcastSubscriber(configKey, subscriberId, pendingIntent,
259                             mContext.getOpPackageName());
260                 } else {
261                     service.unsetBroadcastSubscriber(configKey, subscriberId,
262                             mContext.getOpPackageName());
263                 }
264             } catch (RemoteException e) {
265                 Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
266                         e);
267                 throw new StatsUnavailableException("could not connect", e);
268             } catch (SecurityException e) {
269                 throw new StatsUnavailableException(e.getMessage(), e);
270             }
271         }
272     }
273 
274     // TODO: Temporary for backwards compatibility. Remove.
275     /**
276      * @deprecated Use {@link #setBroadcastSubscriber(PendingIntent, long, long)}
277      */
278     @Deprecated
279     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
setBroadcastSubscriber( long configKey, long subscriberId, PendingIntent pendingIntent)280     public boolean setBroadcastSubscriber(
281             long configKey, long subscriberId, PendingIntent pendingIntent) {
282         try {
283             setBroadcastSubscriber(pendingIntent, configKey, subscriberId);
284             return true;
285         } catch (StatsUnavailableException e) {
286             return false;
287         }
288     }
289 
290     /**
291      * Registers the operation that is called to retrieve the metrics data. This must be called
292      * each time statsd starts. The config must have been added first (via {@link #addConfig},
293      * although addConfig could have been called on a previous boot). This operation allows
294      * statsd to send metrics data whenever statsd determines that the metrics in memory are
295      * approaching the memory limits. The fetch operation should call {@link #getReports} to fetch
296      * the data, which also deletes the retrieved metrics from statsd's memory.
297      * This call can block on statsd.
298      *
299      * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
300      *                      associated with the given subscriberId. May be null, in which case
301      *                      it removes any associated pending intent with this configKey.
302      * @param configKey     The integer naming the config to which this operation is attached.
303      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
304      */
305     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
setFetchReportsOperation(PendingIntent pendingIntent, long configKey)306     public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey)
307             throws StatsUnavailableException {
308         synchronized (sLock) {
309             try {
310                 IStatsManagerService service = getIStatsManagerServiceLocked();
311                 if (pendingIntent == null) {
312                     service.removeDataFetchOperation(configKey, mContext.getOpPackageName());
313                 } else {
314                     service.setDataFetchOperation(configKey, pendingIntent,
315                             mContext.getOpPackageName());
316                 }
317 
318             } catch (RemoteException e) {
319                 Log.e(TAG, "Failed to connect to statsmanager when registering data listener.");
320                 throw new StatsUnavailableException("could not connect", e);
321             } catch (SecurityException e) {
322                 throw new StatsUnavailableException(e.getMessage(), e);
323             }
324         }
325     }
326 
327     /**
328      * Registers the operation that is called whenever there is a change in which configs are
329      * active. This must be called each time statsd starts. This operation allows
330      * statsd to inform clients that they should pull data of the configs that are currently
331      * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs
332      * that are active and stop pulling data of configs that are no longer active.
333      * This call can block on statsd.
334      *
335      * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
336      *                      associated with the given subscriberId. May be null, in which case
337      *                      it removes any associated pending intent for this client.
338      * @return A list of configs that are currently active for this client. If the pendingIntent is
339      *         null, this will be an empty list.
340      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
341      */
342     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
setActiveConfigsChangedOperation(@ullable PendingIntent pendingIntent)343     public @NonNull long[] setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent)
344             throws StatsUnavailableException {
345         synchronized (sLock) {
346             try {
347                 IStatsManagerService service = getIStatsManagerServiceLocked();
348                 if (pendingIntent == null) {
349                     service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
350                     return new long[0];
351                 } else {
352                     return service.setActiveConfigsChangedOperation(pendingIntent,
353                             mContext.getOpPackageName());
354                 }
355 
356             } catch (RemoteException e) {
357                 Log.e(TAG, "Failed to connect to statsmanager "
358                         + "when registering active configs listener.");
359                 throw new StatsUnavailableException("could not connect", e);
360             } catch (SecurityException e) {
361                 throw new StatsUnavailableException(e.getMessage(), e);
362             }
363         }
364     }
365 
366     // TODO: Temporary for backwards compatibility. Remove.
367     /**
368      * @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
369      */
370     @Deprecated
371     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
setDataFetchOperation(long configKey, PendingIntent pendingIntent)372     public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
373         try {
374             setFetchReportsOperation(pendingIntent, configKey);
375             return true;
376         } catch (StatsUnavailableException e) {
377             return false;
378         }
379     }
380 
381     /**
382      * Request the data collected for the given configKey.
383      * This getter is destructive - it also clears the retrieved metrics from statsd's memory.
384      * This call can block on statsd.
385      *
386      * @param configKey Configuration key to retrieve data from.
387      * @return Serialized ConfigMetricsReportList proto.
388      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
389      */
390     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
getReports(long configKey)391     public byte[] getReports(long configKey) throws StatsUnavailableException {
392         synchronized (sLock) {
393             try {
394                 IStatsManagerService service = getIStatsManagerServiceLocked();
395                 return service.getData(configKey, mContext.getOpPackageName());
396             } catch (RemoteException e) {
397                 Log.e(TAG, "Failed to connect to statsmanager when getting data");
398                 throw new StatsUnavailableException("could not connect", e);
399             } catch (SecurityException e) {
400                 throw new StatsUnavailableException(e.getMessage(), e);
401             } catch (IllegalStateException e) {
402                 Log.e(TAG, "Failed to getReports in statsmanager");
403                 throw new StatsUnavailableException(e.getMessage(), e);
404             }
405         }
406     }
407 
408     // TODO: Temporary for backwards compatibility. Remove.
409     /**
410      * @deprecated Use {@link #getReports(long)}
411      */
412     @Deprecated
413     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
getData(long configKey)414     public @Nullable byte[] getData(long configKey) {
415         try {
416             return getReports(configKey);
417         } catch (StatsUnavailableException e) {
418             return null;
419         }
420     }
421 
422     /**
423      * Clients can request metadata for statsd. Will contain stats across all configurations but not
424      * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}.
425      * This getter is not destructive and will not reset any metrics/counters.
426      * This call can block on statsd.
427      *
428      * @return Serialized StatsdStatsReport proto.
429      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
430      */
431     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
getStatsMetadata()432     public byte[] getStatsMetadata() throws StatsUnavailableException {
433         synchronized (sLock) {
434             try {
435                 IStatsManagerService service = getIStatsManagerServiceLocked();
436                 return service.getMetadata(mContext.getOpPackageName());
437             } catch (RemoteException e) {
438                 Log.e(TAG, "Failed to connect to statsmanager when getting metadata");
439                 throw new StatsUnavailableException("could not connect", e);
440             } catch (SecurityException e) {
441                 throw new StatsUnavailableException(e.getMessage(), e);
442             } catch (IllegalStateException e) {
443                 Log.e(TAG, "Failed to getStatsMetadata in statsmanager");
444                 throw new StatsUnavailableException(e.getMessage(), e);
445             }
446         }
447     }
448 
449     // TODO: Temporary for backwards compatibility. Remove.
450     /**
451      * @deprecated Use {@link #getStatsMetadata()}
452      */
453     @Deprecated
454     @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
getMetadata()455     public @Nullable byte[] getMetadata() {
456         try {
457             return getStatsMetadata();
458         } catch (StatsUnavailableException e) {
459             return null;
460         }
461     }
462 
463     /**
464      * Returns the experiments IDs registered with statsd, or an empty array if there aren't any.
465      *
466      * This call can block on statsd.
467      *
468      * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
469      */
470     @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
getRegisteredExperimentIds()471     public long[] getRegisteredExperimentIds()
472             throws StatsUnavailableException {
473         synchronized (sLock) {
474             try {
475                 IStatsManagerService service = getIStatsManagerServiceLocked();
476                 return service.getRegisteredExperimentIds();
477             } catch (RemoteException e) {
478                 if (DEBUG) {
479                     Log.d(TAG,
480                             "Failed to connect to StatsManagerService when getting "
481                                     + "registered experiment IDs");
482                 }
483                 throw new StatsUnavailableException("could not connect", e);
484             } catch (SecurityException e) {
485               throw new StatsUnavailableException(e.getMessage(), e);
486             } catch (IllegalStateException e) {
487               Log.e(TAG, "Failed to getRegisteredExperimentIds in statsmanager");
488               throw new StatsUnavailableException(e.getMessage(), e);
489             }
490         }
491     }
492 
493     /**
494      * Sets a callback for an atom when that atom is to be pulled. The stats service will
495      * invoke pullData in the callback when the stats service determines that this atom needs to be
496      * pulled. This method should not be called by third-party apps.
497      *
498      * @param atomTag           The tag of the atom for this puller callback.
499      * @param metadata          Optional metadata specifying the timeout, cool down time, and
500      *                          additive fields for mapping isolated to host uids.
501      * @param executor          The executor in which to run the callback.
502      * @param callback          The callback to be invoked when the stats service pulls the atom.
503      *
504      */
505     @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, @NonNull @CallbackExecutor Executor executor, @NonNull StatsPullAtomCallback callback)506     public void setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
507             @NonNull @CallbackExecutor Executor executor,
508             @NonNull StatsPullAtomCallback callback) {
509         long coolDownMillis =
510                 metadata == null ? DEFAULT_COOL_DOWN_MILLIS : metadata.mCoolDownMillis;
511         long timeoutMillis = metadata == null ? DEFAULT_TIMEOUT_MILLIS : metadata.mTimeoutMillis;
512         int[] additiveFields = metadata == null ? new int[0] : metadata.mAdditiveFields;
513         if (additiveFields == null) {
514             additiveFields = new int[0];
515         }
516 
517         synchronized (sLock) {
518             try {
519                 IStatsManagerService service = getIStatsManagerServiceLocked();
520                 PullAtomCallbackInternal rec =
521                     new PullAtomCallbackInternal(atomTag, callback, executor);
522                 service.registerPullAtomCallback(
523                         atomTag, coolDownMillis, timeoutMillis, additiveFields, rec);
524             } catch (RemoteException e) {
525                 throw new RuntimeException("Unable to register pull callback", e);
526             }
527         }
528     }
529 
530     /**
531      * Clears a callback for an atom when that atom is to be pulled. Note that any ongoing
532      * pulls will still occur. This method should not be called by third-party apps.
533      *
534      * @param atomTag           The tag of the atom of which to unregister
535      *
536      */
537     @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
clearPullAtomCallback(int atomTag)538     public void clearPullAtomCallback(int atomTag) {
539         synchronized (sLock) {
540             try {
541                 IStatsManagerService service = getIStatsManagerServiceLocked();
542                 service.unregisterPullAtomCallback(atomTag);
543             } catch (RemoteException e) {
544                 throw new RuntimeException("Unable to unregister pull atom callback");
545             }
546         }
547     }
548 
549     private static class PullAtomCallbackInternal extends IPullAtomCallback.Stub {
550         public final int mAtomId;
551         public final StatsPullAtomCallback mCallback;
552         public final Executor mExecutor;
553 
PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor)554         PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor) {
555             mAtomId = atomId;
556             mCallback = callback;
557             mExecutor = executor;
558         }
559 
560         @Override
onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver)561         public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) {
562             final long token = Binder.clearCallingIdentity();
563             try {
564                 mExecutor.execute(() -> {
565                     List<StatsEvent> data = new ArrayList<>();
566                     int successInt = mCallback.onPullAtom(atomTag, data);
567                     boolean success = successInt == PULL_SUCCESS;
568                     StatsEventParcel[] parcels = new StatsEventParcel[data.size()];
569                     for (int i = 0; i < data.size(); i++) {
570                         parcels[i] = new StatsEventParcel();
571                         parcels[i].buffer = data.get(i).getBytes();
572                     }
573                     try {
574                         resultReceiver.pullFinished(atomTag, success, parcels);
575                     } catch (RemoteException e) {
576                         Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId
577                                 + " due to TransactionTooLarge. Calling pullFinish with no data");
578                         StatsEventParcel[] emptyData = new StatsEventParcel[0];
579                         try {
580                             resultReceiver.pullFinished(atomTag, /*success=*/false, emptyData);
581                         } catch (RemoteException nestedException) {
582                             Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId
583                                     + " with empty payload");
584                         }
585                     }
586                 });
587             } finally {
588                 Binder.restoreCallingIdentity(token);
589             }
590         }
591     }
592 
593     /**
594      * Metadata required for registering a StatsPullAtomCallback.
595      * All fields are optional, and defaults will be used for fields that are unspecified.
596      *
597      */
598     public static class PullAtomMetadata {
599         private final long mCoolDownMillis;
600         private final long mTimeoutMillis;
601         private final int[] mAdditiveFields;
602 
603         // Private Constructor for builder
PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields)604         private PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields) {
605             mCoolDownMillis = coolDownMillis;
606             mTimeoutMillis = timeoutMillis;
607             mAdditiveFields = additiveFields;
608         }
609 
610         /**
611          *  Builder for PullAtomMetadata.
612          */
613         public static class Builder {
614             private long mCoolDownMillis;
615             private long mTimeoutMillis;
616             private int[] mAdditiveFields;
617 
618             /**
619              * Returns a new PullAtomMetadata.Builder object for constructing PullAtomMetadata for
620              * StatsManager#registerPullAtomCallback
621              */
Builder()622             public Builder() {
623                 mCoolDownMillis = DEFAULT_COOL_DOWN_MILLIS;
624                 mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
625                 mAdditiveFields = null;
626             }
627 
628             /**
629              * Set the cool down time of the pull in milliseconds. If two successive pulls are
630              * issued within the cool down, a cached version of the first pull will be used for the
631              * second pull. The minimum allowed cool down is 1 second.
632              */
633             @NonNull
setCoolDownMillis(long coolDownMillis)634             public Builder setCoolDownMillis(long coolDownMillis) {
635                 mCoolDownMillis = coolDownMillis;
636                 return this;
637             }
638 
639             /**
640              * Set the maximum time the pull can take in milliseconds. The maximum allowed timeout
641              * is 10 seconds.
642              */
643             @NonNull
setTimeoutMillis(long timeoutMillis)644             public Builder setTimeoutMillis(long timeoutMillis) {
645                 mTimeoutMillis = timeoutMillis;
646                 return this;
647             }
648 
649             /**
650              * Set the additive fields of this pulled atom.
651              *
652              * This is only applicable for atoms which have a uid field. When tasks are run in
653              * isolated processes, the data will be attributed to the host uid. Additive fields
654              * will be combined when the non-additive fields are the same.
655              */
656             @NonNull
setAdditiveFields(@onNull int[] additiveFields)657             public Builder setAdditiveFields(@NonNull int[] additiveFields) {
658                 mAdditiveFields = additiveFields;
659                 return this;
660             }
661 
662             /**
663              * Builds and returns a PullAtomMetadata object with the values set in the builder and
664              * defaults for unset fields.
665              */
666             @NonNull
build()667             public PullAtomMetadata build() {
668                 return new PullAtomMetadata(mCoolDownMillis, mTimeoutMillis, mAdditiveFields);
669             }
670         }
671 
672         /**
673          * Return the cool down time of this pull in milliseconds.
674          */
getCoolDownMillis()675         public long getCoolDownMillis() {
676             return mCoolDownMillis;
677         }
678 
679         /**
680          * Return the maximum amount of time this pull can take in milliseconds.
681          */
getTimeoutMillis()682         public long getTimeoutMillis() {
683             return mTimeoutMillis;
684         }
685 
686         /**
687          * Return the additive fields of this pulled atom.
688          *
689          * This is only applicable for atoms that have a uid field. When tasks are run in
690          * isolated processes, the data will be attributed to the host uid. Additive fields
691          * will be combined when the non-additive fields are the same.
692          */
693         @Nullable
getAdditiveFields()694         public int[] getAdditiveFields() {
695             return mAdditiveFields;
696         }
697     }
698 
699     /**
700      * Callback interface for pulling atoms requested by the stats service.
701      *
702      */
703     public interface StatsPullAtomCallback {
704         /**
705          * Pull data for the specified atom tag, filling in the provided list of StatsEvent data.
706          * @return {@link #PULL_SUCCESS} if the pull was successful, or {@link #PULL_SKIP} if not.
707          */
onPullAtom(int atomTag, @NonNull List<StatsEvent> data)708         int onPullAtom(int atomTag, @NonNull List<StatsEvent> data);
709     }
710 
711     @GuardedBy("sLock")
getIStatsManagerServiceLocked()712     private IStatsManagerService getIStatsManagerServiceLocked() {
713         if (mStatsManagerService != null) {
714             return mStatsManagerService;
715         }
716         mStatsManagerService = IStatsManagerService.Stub.asInterface(
717                 StatsFrameworkInitializer
718                 .getStatsServiceManager()
719                 .getStatsManagerServiceRegisterer()
720                 .get());
721         return mStatsManagerService;
722     }
723 
724     /**
725      * Exception thrown when communication with the stats service fails (eg if it is not available).
726      * This might be thrown early during boot before the stats service has started or if it crashed.
727      */
728     public static class StatsUnavailableException extends AndroidException {
StatsUnavailableException(String reason)729         public StatsUnavailableException(String reason) {
730             super("Failed to connect to statsd: " + reason);
731         }
732 
StatsUnavailableException(String reason, Throwable e)733         public StatsUnavailableException(String reason, Throwable e) {
734             super("Failed to connect to statsd: " + reason, e);
735         }
736     }
737 }
738