blob: 41803cfd696028099c72f305968dd128192ef524 [file] [log] [blame]
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -08001/*
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 */
16package android.app;
17
18import static android.Manifest.permission.DUMP;
19import static android.Manifest.permission.PACKAGE_USAGE_STATS;
20
21import android.annotation.CallbackExecutor;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.RequiresPermission;
25import android.annotation.SystemApi;
26import android.content.Context;
27import android.os.Binder;
28import android.os.IPullAtomCallback;
29import android.os.IPullAtomResultReceiver;
30import android.os.IStatsManagerService;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -080031import android.os.RemoteException;
Jeffrey Huang49790742020-01-23 13:22:10 -080032import android.os.StatsFrameworkInitializer;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -080033import android.util.AndroidException;
Muhammad Qureshi4df703d2020-02-09 11:05:47 -080034import android.util.Log;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -080035import android.util.StatsEvent;
36import android.util.StatsEventParcel;
37
38import com.android.internal.annotations.GuardedBy;
39import com.android.internal.annotations.VisibleForTesting;
40
41import java.util.ArrayList;
42import java.util.List;
43import java.util.concurrent.Executor;
44
45/**
46 * API for statsd clients to send configurations and retrieve data.
47 *
48 * @hide
49 */
50@SystemApi
51public 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")
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -080059 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 **/
Tej Singhdeec45c2020-02-26 23:46:29 -0800118 @VisibleForTesting public static final long DEFAULT_COOL_DOWN_MILLIS = 1_000L; // 1 second.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800119
120 /**
121 * @hide
122 **/
Tej Singh947d71b2020-05-21 19:28:42 -0700123 @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 2_000L; // 2 seconds.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800124
125 /**
126 * Constructor for StatsManagerClient.
127 *
128 * @hide
129 */
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 *
138 * @param configKey An arbitrary integer that allows clients to track the configuration.
139 * @param config Wire-encoded StatsdConfig proto that specifies metrics (and all
140 * dependencies eg, conditions and matchers).
141 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
142 * @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto
143 */
144 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
145 public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
146 synchronized (sLock) {
147 try {
148 IStatsManagerService service = getIStatsManagerServiceLocked();
149 // can throw IllegalArgumentException
150 service.addConfiguration(configKey, config, mContext.getOpPackageName());
151 } catch (RemoteException e) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800152 Log.e(TAG, "Failed to connect to statsmanager when adding configuration");
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800153 throw new StatsUnavailableException("could not connect", e);
154 } catch (SecurityException e) {
155 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huang47c1a732020-02-20 15:19:07 -0800156 } catch (IllegalStateException e) {
157 Log.e(TAG, "Failed to addConfig in statsmanager");
158 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800159 }
160 }
161 }
162
163 // TODO: Temporary for backwards compatibility. Remove.
164 /**
165 * @deprecated Use {@link #addConfig(long, byte[])}
166 */
167 @Deprecated
168 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
169 public boolean addConfiguration(long configKey, byte[] config) {
170 try {
171 addConfig(configKey, config);
172 return true;
173 } catch (StatsUnavailableException | IllegalArgumentException e) {
174 return false;
175 }
176 }
177
178 /**
179 * Remove a configuration from logging.
180 *
181 * @param configKey Configuration key to remove.
182 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
183 */
184 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
185 public void removeConfig(long configKey) throws StatsUnavailableException {
186 synchronized (sLock) {
187 try {
188 IStatsManagerService service = getIStatsManagerServiceLocked();
189 service.removeConfiguration(configKey, mContext.getOpPackageName());
190 } catch (RemoteException e) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800191 Log.e(TAG, "Failed to connect to statsmanager when removing configuration");
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800192 throw new StatsUnavailableException("could not connect", e);
193 } catch (SecurityException e) {
194 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huang47c1a732020-02-20 15:19:07 -0800195 } catch (IllegalStateException e) {
196 Log.e(TAG, "Failed to removeConfig in statsmanager");
197 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800198 }
199 }
200 }
201
202 // TODO: Temporary for backwards compatibility. Remove.
203 /**
204 * @deprecated Use {@link #removeConfig(long)}
205 */
206 @Deprecated
207 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
208 public boolean removeConfiguration(long configKey) {
209 try {
210 removeConfig(configKey);
211 return true;
212 } catch (StatsUnavailableException e) {
213 return false;
214 }
215 }
216
217 /**
218 * Set the PendingIntent to be used when broadcasting subscriber information to the given
219 * subscriberId within the given config.
220 * <p>
221 * Suppose that the calling uid has added a config with key configKey, and that in this config
222 * it is specified that when a particular anomaly is detected, a broadcast should be sent to
223 * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
224 * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
225 * when the anomaly is detected.
226 * <p>
227 * When statsd sends the broadcast, the PendingIntent will used to send an intent with
228 * information of
229 * {@link #EXTRA_STATS_CONFIG_UID},
230 * {@link #EXTRA_STATS_CONFIG_KEY},
231 * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
232 * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID},
233 * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and
234 * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
235 * <p>
236 * This function can only be called by the owner (uid) of the config. It must be called each
237 * time statsd starts. The config must have been added first (via {@link #addConfig}).
238 *
239 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
240 * associated with the given subscriberId. May be null, in which case
241 * it undoes any previous setting of this subscriberId.
242 * @param configKey The integer naming the config to which this subscriber is attached.
243 * @param subscriberId ID of the subscriber, as used in the config.
244 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
245 */
246 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
247 public void setBroadcastSubscriber(
248 PendingIntent pendingIntent, long configKey, long subscriberId)
249 throws StatsUnavailableException {
250 synchronized (sLock) {
251 try {
252 IStatsManagerService service = getIStatsManagerServiceLocked();
253 if (pendingIntent != null) {
254 service.setBroadcastSubscriber(configKey, subscriberId, pendingIntent,
255 mContext.getOpPackageName());
256 } else {
257 service.unsetBroadcastSubscriber(configKey, subscriberId,
258 mContext.getOpPackageName());
259 }
260 } catch (RemoteException e) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800261 Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800262 e);
263 throw new StatsUnavailableException("could not connect", e);
264 } catch (SecurityException e) {
265 throw new StatsUnavailableException(e.getMessage(), e);
266 }
267 }
268 }
269
270 // TODO: Temporary for backwards compatibility. Remove.
271 /**
272 * @deprecated Use {@link #setBroadcastSubscriber(PendingIntent, long, long)}
273 */
274 @Deprecated
275 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
276 public boolean setBroadcastSubscriber(
277 long configKey, long subscriberId, PendingIntent pendingIntent) {
278 try {
279 setBroadcastSubscriber(pendingIntent, configKey, subscriberId);
280 return true;
281 } catch (StatsUnavailableException e) {
282 return false;
283 }
284 }
285
286 /**
287 * Registers the operation that is called to retrieve the metrics data. This must be called
288 * each time statsd starts. The config must have been added first (via {@link #addConfig},
289 * although addConfig could have been called on a previous boot). This operation allows
290 * statsd to send metrics data whenever statsd determines that the metrics in memory are
291 * approaching the memory limits. The fetch operation should call {@link #getReports} to fetch
292 * the data, which also deletes the retrieved metrics from statsd's memory.
293 *
294 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
295 * associated with the given subscriberId. May be null, in which case
296 * it removes any associated pending intent with this configKey.
297 * @param configKey The integer naming the config to which this operation is attached.
298 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
299 */
300 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
301 public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey)
302 throws StatsUnavailableException {
303 synchronized (sLock) {
304 try {
305 IStatsManagerService service = getIStatsManagerServiceLocked();
306 if (pendingIntent == null) {
307 service.removeDataFetchOperation(configKey, mContext.getOpPackageName());
308 } else {
309 service.setDataFetchOperation(configKey, pendingIntent,
310 mContext.getOpPackageName());
311 }
312
313 } catch (RemoteException e) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800314 Log.e(TAG, "Failed to connect to statsmanager when registering data listener.");
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800315 throw new StatsUnavailableException("could not connect", e);
316 } catch (SecurityException e) {
317 throw new StatsUnavailableException(e.getMessage(), e);
318 }
319 }
320 }
321
322 /**
323 * Registers the operation that is called whenever there is a change in which configs are
324 * active. This must be called each time statsd starts. This operation allows
325 * statsd to inform clients that they should pull data of the configs that are currently
326 * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs
327 * that are active and stop pulling data of configs that are no longer active.
328 *
329 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
330 * associated with the given subscriberId. May be null, in which case
331 * it removes any associated pending intent for this client.
332 * @return A list of configs that are currently active for this client. If the pendingIntent is
333 * null, this will be an empty list.
334 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
335 */
336 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
337 public @NonNull long[] setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent)
338 throws StatsUnavailableException {
339 synchronized (sLock) {
340 try {
341 IStatsManagerService service = getIStatsManagerServiceLocked();
342 if (pendingIntent == null) {
343 service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
344 return new long[0];
345 } else {
346 return service.setActiveConfigsChangedOperation(pendingIntent,
347 mContext.getOpPackageName());
348 }
349
350 } catch (RemoteException e) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800351 Log.e(TAG, "Failed to connect to statsmanager "
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800352 + "when registering active configs listener.");
353 throw new StatsUnavailableException("could not connect", e);
354 } catch (SecurityException e) {
355 throw new StatsUnavailableException(e.getMessage(), e);
356 }
357 }
358 }
359
360 // TODO: Temporary for backwards compatibility. Remove.
361 /**
362 * @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
363 */
364 @Deprecated
365 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
366 public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) {
367 try {
368 setFetchReportsOperation(pendingIntent, configKey);
369 return true;
370 } catch (StatsUnavailableException e) {
371 return false;
372 }
373 }
374
375 /**
376 * Request the data collected for the given configKey.
377 * This getter is destructive - it also clears the retrieved metrics from statsd's memory.
378 *
379 * @param configKey Configuration key to retrieve data from.
380 * @return Serialized ConfigMetricsReportList proto.
381 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
382 */
383 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
384 public byte[] getReports(long configKey) throws StatsUnavailableException {
385 synchronized (sLock) {
386 try {
387 IStatsManagerService service = getIStatsManagerServiceLocked();
388 return service.getData(configKey, mContext.getOpPackageName());
389 } catch (RemoteException e) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800390 Log.e(TAG, "Failed to connect to statsmanager when getting data");
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800391 throw new StatsUnavailableException("could not connect", e);
392 } catch (SecurityException e) {
393 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huang47c1a732020-02-20 15:19:07 -0800394 } catch (IllegalStateException e) {
395 Log.e(TAG, "Failed to getReports in statsmanager");
396 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800397 }
398 }
399 }
400
401 // TODO: Temporary for backwards compatibility. Remove.
402 /**
403 * @deprecated Use {@link #getReports(long)}
404 */
405 @Deprecated
406 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
407 public @Nullable byte[] getData(long configKey) {
408 try {
409 return getReports(configKey);
410 } catch (StatsUnavailableException e) {
411 return null;
412 }
413 }
414
415 /**
416 * Clients can request metadata for statsd. Will contain stats across all configurations but not
417 * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}.
418 * This getter is not destructive and will not reset any metrics/counters.
419 *
420 * @return Serialized StatsdStatsReport proto.
421 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
422 */
423 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
424 public byte[] getStatsMetadata() throws StatsUnavailableException {
425 synchronized (sLock) {
426 try {
427 IStatsManagerService service = getIStatsManagerServiceLocked();
428 return service.getMetadata(mContext.getOpPackageName());
429 } catch (RemoteException e) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800430 Log.e(TAG, "Failed to connect to statsmanager when getting metadata");
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800431 throw new StatsUnavailableException("could not connect", e);
432 } catch (SecurityException e) {
433 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huang47c1a732020-02-20 15:19:07 -0800434 } catch (IllegalStateException e) {
435 Log.e(TAG, "Failed to getStatsMetadata in statsmanager");
436 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800437 }
438 }
439 }
440
441 // TODO: Temporary for backwards compatibility. Remove.
442 /**
443 * @deprecated Use {@link #getStatsMetadata()}
444 */
445 @Deprecated
446 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
447 public @Nullable byte[] getMetadata() {
448 try {
449 return getStatsMetadata();
450 } catch (StatsUnavailableException e) {
451 return null;
452 }
453 }
454
455 /**
456 * Returns the experiments IDs registered with statsd, or an empty array if there aren't any.
457 *
458 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
459 */
460 @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS})
461 public long[] getRegisteredExperimentIds()
462 throws StatsUnavailableException {
463 synchronized (sLock) {
464 try {
465 IStatsManagerService service = getIStatsManagerServiceLocked();
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800466 return service.getRegisteredExperimentIds();
467 } catch (RemoteException e) {
468 if (DEBUG) {
Muhammad Qureshi4df703d2020-02-09 11:05:47 -0800469 Log.d(TAG,
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800470 "Failed to connect to StatsManagerService when getting "
471 + "registered experiment IDs");
472 }
473 throw new StatsUnavailableException("could not connect", e);
Jeffrey Huang47c1a732020-02-20 15:19:07 -0800474 } catch (IllegalStateException e) {
475 Log.e(TAG, "Failed to getRegisteredExperimentIds in statsmanager");
476 throw new StatsUnavailableException(e.getMessage(), e);
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800477 }
478 }
479 }
480
481 /**
Tej Singhdeec45c2020-02-26 23:46:29 -0800482 * Sets a callback for an atom when that atom is to be pulled. The stats service will
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800483 * invoke pullData in the callback when the stats service determines that this atom needs to be
Muhammad Qureshi2dc80aa2020-01-31 17:31:36 -0800484 * pulled. This method should not be called by third-party apps.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800485 *
486 * @param atomTag The tag of the atom for this puller callback.
487 * @param metadata Optional metadata specifying the timeout, cool down time, and
488 * additive fields for mapping isolated to host uids.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800489 * @param executor The executor in which to run the callback.
Tej Singhdeec45c2020-02-26 23:46:29 -0800490 * @param callback The callback to be invoked when the stats service pulls the atom.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800491 *
492 */
Muhammad Qureshi2dc80aa2020-01-31 17:31:36 -0800493 @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
Tej Singhdeec45c2020-02-26 23:46:29 -0800494 public void setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800495 @NonNull @CallbackExecutor Executor executor,
496 @NonNull StatsPullAtomCallback callback) {
Tej Singhdeec45c2020-02-26 23:46:29 -0800497 long coolDownMillis =
498 metadata == null ? DEFAULT_COOL_DOWN_MILLIS : metadata.mCoolDownMillis;
499 long timeoutMillis = metadata == null ? DEFAULT_TIMEOUT_MILLIS : metadata.mTimeoutMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800500 int[] additiveFields = metadata == null ? new int[0] : metadata.mAdditiveFields;
501 if (additiveFields == null) {
502 additiveFields = new int[0];
503 }
504
505 synchronized (sLock) {
506 try {
507 IStatsManagerService service = getIStatsManagerServiceLocked();
508 PullAtomCallbackInternal rec =
509 new PullAtomCallbackInternal(atomTag, callback, executor);
Tej Singhdeec45c2020-02-26 23:46:29 -0800510 service.registerPullAtomCallback(
511 atomTag, coolDownMillis, timeoutMillis, additiveFields, rec);
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800512 } catch (RemoteException e) {
513 throw new RuntimeException("Unable to register pull callback", e);
514 }
515 }
516 }
517
518 /**
Tej Singhdeec45c2020-02-26 23:46:29 -0800519 * Clears a callback for an atom when that atom is to be pulled. Note that any ongoing
Muhammad Qureshi2dc80aa2020-01-31 17:31:36 -0800520 * pulls will still occur. This method should not be called by third-party apps.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800521 *
522 * @param atomTag The tag of the atom of which to unregister
523 *
524 */
Muhammad Qureshi2dc80aa2020-01-31 17:31:36 -0800525 @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
Tej Singhdeec45c2020-02-26 23:46:29 -0800526 public void clearPullAtomCallback(int atomTag) {
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800527 synchronized (sLock) {
528 try {
529 IStatsManagerService service = getIStatsManagerServiceLocked();
530 service.unregisterPullAtomCallback(atomTag);
531 } catch (RemoteException e) {
532 throw new RuntimeException("Unable to unregister pull atom callback");
533 }
534 }
535 }
536
537 private static class PullAtomCallbackInternal extends IPullAtomCallback.Stub {
538 public final int mAtomId;
539 public final StatsPullAtomCallback mCallback;
540 public final Executor mExecutor;
541
542 PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor) {
543 mAtomId = atomId;
544 mCallback = callback;
545 mExecutor = executor;
546 }
547
548 @Override
549 public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) {
Jeff Sharkeyf7fb8e02020-10-06 11:18:09 -0600550 final long token = Binder.clearCallingIdentity();
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800551 try {
552 mExecutor.execute(() -> {
553 List<StatsEvent> data = new ArrayList<>();
554 int successInt = mCallback.onPullAtom(atomTag, data);
555 boolean success = successInt == PULL_SUCCESS;
556 StatsEventParcel[] parcels = new StatsEventParcel[data.size()];
557 for (int i = 0; i < data.size(); i++) {
558 parcels[i] = new StatsEventParcel();
559 parcels[i].buffer = data.get(i).getBytes();
560 }
561 try {
562 resultReceiver.pullFinished(atomTag, success, parcels);
563 } catch (RemoteException e) {
Tej Singhe22b75a2020-05-29 15:13:23 -0700564 Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId
565 + " due to TransactionTooLarge. Calling pullFinish with no data");
566 StatsEventParcel[] emptyData = new StatsEventParcel[0];
567 try {
568 resultReceiver.pullFinished(atomTag, /*success=*/false, emptyData);
569 } catch (RemoteException nestedException) {
570 Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId
571 + " with empty payload");
572 }
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800573 }
574 });
575 } finally {
576 Binder.restoreCallingIdentity(token);
577 }
578 }
579 }
580
581 /**
582 * Metadata required for registering a StatsPullAtomCallback.
583 * All fields are optional, and defaults will be used for fields that are unspecified.
584 *
585 */
586 public static class PullAtomMetadata {
Tej Singhdeec45c2020-02-26 23:46:29 -0800587 private final long mCoolDownMillis;
588 private final long mTimeoutMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800589 private final int[] mAdditiveFields;
590
591 // Private Constructor for builder
Tej Singhdeec45c2020-02-26 23:46:29 -0800592 private PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields) {
593 mCoolDownMillis = coolDownMillis;
594 mTimeoutMillis = timeoutMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800595 mAdditiveFields = additiveFields;
596 }
597
598 /**
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800599 * Builder for PullAtomMetadata.
600 */
601 public static class Builder {
Tej Singhdeec45c2020-02-26 23:46:29 -0800602 private long mCoolDownMillis;
603 private long mTimeoutMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800604 private int[] mAdditiveFields;
605
606 /**
607 * Returns a new PullAtomMetadata.Builder object for constructing PullAtomMetadata for
608 * StatsManager#registerPullAtomCallback
609 */
610 public Builder() {
Tej Singhdeec45c2020-02-26 23:46:29 -0800611 mCoolDownMillis = DEFAULT_COOL_DOWN_MILLIS;
612 mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800613 mAdditiveFields = null;
614 }
615
616 /**
Tej Singhdeec45c2020-02-26 23:46:29 -0800617 * Set the cool down time of the pull in milliseconds. If two successive pulls are
618 * issued within the cool down, a cached version of the first pull will be used for the
Tej Singh2cf0fab2020-03-30 17:19:36 -0700619 * second pull. The minimum allowed cool down is 1 second.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800620 */
621 @NonNull
Tej Singhdeec45c2020-02-26 23:46:29 -0800622 public Builder setCoolDownMillis(long coolDownMillis) {
623 mCoolDownMillis = coolDownMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800624 return this;
625 }
626
627 /**
Tej Singh2cf0fab2020-03-30 17:19:36 -0700628 * Set the maximum time the pull can take in milliseconds. The maximum allowed timeout
629 * is 10 seconds.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800630 */
631 @NonNull
Tej Singhdeec45c2020-02-26 23:46:29 -0800632 public Builder setTimeoutMillis(long timeoutMillis) {
633 mTimeoutMillis = timeoutMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800634 return this;
635 }
636
637 /**
638 * Set the additive fields of this pulled atom.
639 *
640 * This is only applicable for atoms which have a uid field. When tasks are run in
641 * isolated processes, the data will be attributed to the host uid. Additive fields
642 * will be combined when the non-additive fields are the same.
643 */
644 @NonNull
645 public Builder setAdditiveFields(@NonNull int[] additiveFields) {
646 mAdditiveFields = additiveFields;
647 return this;
648 }
649
650 /**
651 * Builds and returns a PullAtomMetadata object with the values set in the builder and
652 * defaults for unset fields.
653 */
654 @NonNull
655 public PullAtomMetadata build() {
Tej Singhdeec45c2020-02-26 23:46:29 -0800656 return new PullAtomMetadata(mCoolDownMillis, mTimeoutMillis, mAdditiveFields);
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800657 }
658 }
659
660 /**
Tej Singhdeec45c2020-02-26 23:46:29 -0800661 * Return the cool down time of this pull in milliseconds.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800662 */
Tej Singhdeec45c2020-02-26 23:46:29 -0800663 public long getCoolDownMillis() {
664 return mCoolDownMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800665 }
666
667 /**
Tej Singhdeec45c2020-02-26 23:46:29 -0800668 * Return the maximum amount of time this pull can take in milliseconds.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800669 */
Tej Singhdeec45c2020-02-26 23:46:29 -0800670 public long getTimeoutMillis() {
671 return mTimeoutMillis;
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800672 }
673
674 /**
Tej Singhdeec45c2020-02-26 23:46:29 -0800675 * Return the additive fields of this pulled atom.
676 *
677 * This is only applicable for atoms that have a uid field. When tasks are run in
678 * isolated processes, the data will be attributed to the host uid. Additive fields
679 * will be combined when the non-additive fields are the same.
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800680 */
Tej Singhdeec45c2020-02-26 23:46:29 -0800681 @Nullable
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800682 public int[] getAdditiveFields() {
683 return mAdditiveFields;
684 }
685 }
686
687 /**
688 * Callback interface for pulling atoms requested by the stats service.
689 *
690 */
691 public interface StatsPullAtomCallback {
692 /**
693 * Pull data for the specified atom tag, filling in the provided list of StatsEvent data.
694 * @return {@link #PULL_SUCCESS} if the pull was successful, or {@link #PULL_SKIP} if not.
695 */
696 int onPullAtom(int atomTag, @NonNull List<StatsEvent> data);
697 }
698
699 @GuardedBy("sLock")
700 private IStatsManagerService getIStatsManagerServiceLocked() {
701 if (mStatsManagerService != null) {
702 return mStatsManagerService;
703 }
704 mStatsManagerService = IStatsManagerService.Stub.asInterface(
Jeffrey Huang49790742020-01-23 13:22:10 -0800705 StatsFrameworkInitializer
706 .getStatsServiceManager()
707 .getStatsManagerServiceRegisterer()
708 .get());
Jeffrey Huangfe5d22d2020-01-22 20:22:59 -0800709 return mStatsManagerService;
710 }
711
712 /**
713 * Exception thrown when communication with the stats service fails (eg if it is not available).
714 * This might be thrown early during boot before the stats service has started or if it crashed.
715 */
716 public static class StatsUnavailableException extends AndroidException {
717 public StatsUnavailableException(String reason) {
718 super("Failed to connect to statsd: " + reason);
719 }
720
721 public StatsUnavailableException(String reason, Throwable e) {
722 super("Failed to connect to statsd: " + reason, e);
723 }
724 }
725}