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 * 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 }) addConfig(long configKey, byte[] config)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) { 152 Log.e(TAG, "Failed to connect to statsmanager when adding configuration"); 153 throw new StatsUnavailableException("could not connect", e); 154 } catch (SecurityException e) { 155 throw new StatsUnavailableException(e.getMessage(), e); 156 } catch (IllegalStateException e) { 157 Log.e(TAG, "Failed to addConfig in statsmanager"); 158 throw new StatsUnavailableException(e.getMessage(), e); 159 } 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 }) addConfiguration(long configKey, byte[] config)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 }) removeConfig(long configKey)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) { 191 Log.e(TAG, "Failed to connect to statsmanager when removing configuration"); 192 throw new StatsUnavailableException("could not connect", e); 193 } catch (SecurityException e) { 194 throw new StatsUnavailableException(e.getMessage(), e); 195 } catch (IllegalStateException e) { 196 Log.e(TAG, "Failed to removeConfig in statsmanager"); 197 throw new StatsUnavailableException(e.getMessage(), e); 198 } 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 }) removeConfiguration(long configKey)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 }) setBroadcastSubscriber( PendingIntent pendingIntent, long configKey, long subscriberId)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) { 261 Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", 262 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 }) setBroadcastSubscriber( long configKey, long subscriberId, PendingIntent pendingIntent)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 }) setFetchReportsOperation(PendingIntent pendingIntent, long configKey)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) { 314 Log.e(TAG, "Failed to connect to statsmanager when registering data listener."); 315 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 }) setActiveConfigsChangedOperation(@ullable PendingIntent pendingIntent)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) { 351 Log.e(TAG, "Failed to connect to statsmanager " 352 + "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 }) setDataFetchOperation(long configKey, PendingIntent pendingIntent)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 }) getReports(long configKey)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) { 390 Log.e(TAG, "Failed to connect to statsmanager when getting data"); 391 throw new StatsUnavailableException("could not connect", e); 392 } catch (SecurityException e) { 393 throw new StatsUnavailableException(e.getMessage(), e); 394 } catch (IllegalStateException e) { 395 Log.e(TAG, "Failed to getReports in statsmanager"); 396 throw new StatsUnavailableException(e.getMessage(), e); 397 } 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 }) getData(long configKey)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 }) getStatsMetadata()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) { 430 Log.e(TAG, "Failed to connect to statsmanager when getting metadata"); 431 throw new StatsUnavailableException("could not connect", e); 432 } catch (SecurityException e) { 433 throw new StatsUnavailableException(e.getMessage(), e); 434 } catch (IllegalStateException e) { 435 Log.e(TAG, "Failed to getStatsMetadata in statsmanager"); 436 throw new StatsUnavailableException(e.getMessage(), e); 437 } 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 }) getMetadata()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}) getRegisteredExperimentIds()461 public long[] getRegisteredExperimentIds() 462 throws StatsUnavailableException { 463 synchronized (sLock) { 464 try { 465 IStatsManagerService service = getIStatsManagerServiceLocked(); 466 return service.getRegisteredExperimentIds(); 467 } catch (RemoteException e) { 468 if (DEBUG) { 469 Log.d(TAG, 470 "Failed to connect to StatsManagerService when getting " 471 + "registered experiment IDs"); 472 } 473 throw new StatsUnavailableException("could not connect", e); 474 } catch (IllegalStateException e) { 475 Log.e(TAG, "Failed to getRegisteredExperimentIds in statsmanager"); 476 throw new StatsUnavailableException(e.getMessage(), e); 477 } 478 } 479 } 480 481 /** 482 * Sets a callback for an atom when that atom is to be pulled. The stats service will 483 * invoke pullData in the callback when the stats service determines that this atom needs to be 484 * pulled. This method should not be called by third-party apps. 485 * 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. 489 * @param executor The executor in which to run the callback. 490 * @param callback The callback to be invoked when the stats service pulls the atom. 491 * 492 */ 493 @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, @NonNull @CallbackExecutor Executor executor, @NonNull StatsPullAtomCallback callback)494 public void setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, 495 @NonNull @CallbackExecutor Executor executor, 496 @NonNull StatsPullAtomCallback callback) { 497 long coolDownMillis = 498 metadata == null ? DEFAULT_COOL_DOWN_MILLIS : metadata.mCoolDownMillis; 499 long timeoutMillis = metadata == null ? DEFAULT_TIMEOUT_MILLIS : metadata.mTimeoutMillis; 500 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); 510 service.registerPullAtomCallback( 511 atomTag, coolDownMillis, timeoutMillis, additiveFields, rec); 512 } catch (RemoteException e) { 513 throw new RuntimeException("Unable to register pull callback", e); 514 } 515 } 516 } 517 518 /** 519 * Clears a callback for an atom when that atom is to be pulled. Note that any ongoing 520 * pulls will still occur. This method should not be called by third-party apps. 521 * 522 * @param atomTag The tag of the atom of which to unregister 523 * 524 */ 525 @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) clearPullAtomCallback(int atomTag)526 public void clearPullAtomCallback(int atomTag) { 527 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 PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor)542 PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor) { 543 mAtomId = atomId; 544 mCallback = callback; 545 mExecutor = executor; 546 } 547 548 @Override onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver)549 public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) { 550 final long token = Binder.clearCallingIdentity(); 551 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) { 564 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 } 573 } 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 { 587 private final long mCoolDownMillis; 588 private final long mTimeoutMillis; 589 private final int[] mAdditiveFields; 590 591 // Private Constructor for builder PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields)592 private PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields) { 593 mCoolDownMillis = coolDownMillis; 594 mTimeoutMillis = timeoutMillis; 595 mAdditiveFields = additiveFields; 596 } 597 598 /** 599 * Builder for PullAtomMetadata. 600 */ 601 public static class Builder { 602 private long mCoolDownMillis; 603 private long mTimeoutMillis; 604 private int[] mAdditiveFields; 605 606 /** 607 * Returns a new PullAtomMetadata.Builder object for constructing PullAtomMetadata for 608 * StatsManager#registerPullAtomCallback 609 */ Builder()610 public Builder() { 611 mCoolDownMillis = DEFAULT_COOL_DOWN_MILLIS; 612 mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS; 613 mAdditiveFields = null; 614 } 615 616 /** 617 * 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 619 * second pull. The minimum allowed cool down is 1 second. 620 */ 621 @NonNull setCoolDownMillis(long coolDownMillis)622 public Builder setCoolDownMillis(long coolDownMillis) { 623 mCoolDownMillis = coolDownMillis; 624 return this; 625 } 626 627 /** 628 * Set the maximum time the pull can take in milliseconds. The maximum allowed timeout 629 * is 10 seconds. 630 */ 631 @NonNull setTimeoutMillis(long timeoutMillis)632 public Builder setTimeoutMillis(long timeoutMillis) { 633 mTimeoutMillis = timeoutMillis; 634 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 setAdditiveFields(@onNull int[] additiveFields)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 build()655 public PullAtomMetadata build() { 656 return new PullAtomMetadata(mCoolDownMillis, mTimeoutMillis, mAdditiveFields); 657 } 658 } 659 660 /** 661 * Return the cool down time of this pull in milliseconds. 662 */ getCoolDownMillis()663 public long getCoolDownMillis() { 664 return mCoolDownMillis; 665 } 666 667 /** 668 * Return the maximum amount of time this pull can take in milliseconds. 669 */ getTimeoutMillis()670 public long getTimeoutMillis() { 671 return mTimeoutMillis; 672 } 673 674 /** 675 * 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. 680 */ 681 @Nullable getAdditiveFields()682 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 */ onPullAtom(int atomTag, @NonNull List<StatsEvent> data)696 int onPullAtom(int atomTag, @NonNull List<StatsEvent> data); 697 } 698 699 @GuardedBy("sLock") getIStatsManagerServiceLocked()700 private IStatsManagerService getIStatsManagerServiceLocked() { 701 if (mStatsManagerService != null) { 702 return mStatsManagerService; 703 } 704 mStatsManagerService = IStatsManagerService.Stub.asInterface( 705 StatsFrameworkInitializer 706 .getStatsServiceManager() 707 .getStatsManagerServiceRegisterer() 708 .get()); 709 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 { StatsUnavailableException(String reason)717 public StatsUnavailableException(String reason) { 718 super("Failed to connect to statsd: " + reason); 719 } 720 StatsUnavailableException(String reason, Throwable e)721 public StatsUnavailableException(String reason, Throwable e) { 722 super("Failed to connect to statsd: " + reason, e); 723 } 724 } 725 } 726