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