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 import static android.Manifest.permission.READ_RESTRICTED_STATS; 21 22 import android.annotation.CallbackExecutor; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.annotation.SystemApi; 27 import android.content.Context; 28 import android.os.Binder; 29 import android.os.Build; 30 import android.os.IPullAtomCallback; 31 import android.os.IPullAtomResultReceiver; 32 import android.os.IStatsManagerService; 33 import android.os.IStatsQueryCallback; 34 import android.os.OutcomeReceiver; 35 import android.os.RemoteException; 36 import android.os.StatsFrameworkInitializer; 37 import android.util.AndroidException; 38 import android.util.Log; 39 import android.util.StatsEvent; 40 import android.util.StatsEventParcel; 41 42 import androidx.annotation.RequiresApi; 43 44 import com.android.internal.annotations.GuardedBy; 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.modules.utils.build.SdkLevel; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.concurrent.Executor; 51 52 /** 53 * API for statsd clients to send configurations and retrieve data. 54 * 55 * @hide 56 */ 57 @SystemApi 58 public final class StatsManager { 59 private static final String TAG = "StatsManager"; 60 private static final boolean DEBUG = false; 61 62 private static final Object sLock = new Object(); 63 private final Context mContext; 64 65 @GuardedBy("sLock") 66 private IStatsManagerService mStatsManagerService; 67 68 /** 69 * Long extra of uid that added the relevant stats config. 70 */ 71 public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; 72 /** 73 * Long extra of the relevant stats config's configKey. 74 */ 75 public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; 76 /** 77 * Long extra of the relevant statsd_config.proto's Subscription.id. 78 */ 79 public static final String EXTRA_STATS_SUBSCRIPTION_ID = 80 "android.app.extra.STATS_SUBSCRIPTION_ID"; 81 /** 82 * Long extra of the relevant statsd_config.proto's Subscription.rule_id. 83 */ 84 public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = 85 "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; 86 /** 87 * List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie. 88 * Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}. 89 */ 90 public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = 91 "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; 92 /** 93 * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value 94 * information. 95 */ 96 public static final String EXTRA_STATS_DIMENSIONS_VALUE = 97 "android.app.extra.STATS_DIMENSIONS_VALUE"; 98 /** 99 * Long array extra of the active configs for the uid that added those configs. 100 */ 101 public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = 102 "android.app.extra.STATS_ACTIVE_CONFIG_KEYS"; 103 104 /** 105 * Long array extra of the restricted metric ids present for the client. 106 */ 107 public static final String EXTRA_STATS_RESTRICTED_METRIC_IDS = 108 "android.app.extra.STATS_RESTRICTED_METRIC_IDS"; 109 110 /** 111 * Broadcast Action: Statsd has started. 112 * Configurations and PendingIntents can now be sent to it. 113 */ 114 public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; 115 116 // Pull atom callback return codes. 117 /** 118 * Value indicating that this pull was successful and that the result should be used. 119 * 120 **/ 121 public static final int PULL_SUCCESS = 0; 122 123 /** 124 * Value indicating that this pull was unsuccessful and that the result should not be used. 125 **/ 126 public static final int PULL_SKIP = 1; 127 128 /** 129 * @hide 130 **/ 131 @VisibleForTesting public static final long DEFAULT_COOL_DOWN_MILLIS = 1_000L; // 1 second. 132 133 /** 134 * @hide 135 **/ 136 @VisibleForTesting public static final long DEFAULT_TIMEOUT_MILLIS = 1_500L; // 1.5 seconds. 137 138 /** 139 * Constructor for StatsManagerClient. 140 * 141 * @hide 142 */ StatsManager(Context context)143 public StatsManager(Context context) { 144 mContext = context; 145 } 146 147 /** 148 * Adds the given configuration and associates it with the given configKey. If a config with the 149 * given configKey already exists for the caller's uid, it is replaced with the new one. 150 * This call can block on statsd. 151 * 152 * @param configKey An arbitrary integer that allows clients to track the configuration. 153 * @param config Wire-encoded StatsdConfig proto that specifies metrics (and all 154 * dependencies eg, conditions and matchers). 155 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 156 * @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto 157 */ 158 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) addConfig(long configKey, byte[] config)159 public void addConfig(long configKey, byte[] config) throws StatsUnavailableException { 160 synchronized (sLock) { 161 try { 162 IStatsManagerService service = getIStatsManagerServiceLocked(); 163 // can throw IllegalArgumentException 164 service.addConfiguration(configKey, config, mContext.getOpPackageName()); 165 } catch (RemoteException e) { 166 Log.e(TAG, "Failed to connect to statsmanager when adding configuration"); 167 throw new StatsUnavailableException("could not connect", e); 168 } catch (SecurityException e) { 169 throw new StatsUnavailableException(e.getMessage(), e); 170 } catch (IllegalStateException e) { 171 Log.e(TAG, "Failed to addConfig in statsmanager"); 172 throw new StatsUnavailableException(e.getMessage(), e); 173 } 174 } 175 } 176 177 // TODO: Temporary for backwards compatibility. Remove. 178 /** 179 * @deprecated Use {@link #addConfig(long, byte[])} 180 */ 181 @Deprecated 182 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) addConfiguration(long configKey, byte[] config)183 public boolean addConfiguration(long configKey, byte[] config) { 184 try { 185 addConfig(configKey, config); 186 return true; 187 } catch (StatsUnavailableException | IllegalArgumentException e) { 188 return false; 189 } 190 } 191 192 /** 193 * Remove a configuration from logging. 194 * 195 * This call can block on statsd. 196 * 197 * @param configKey Configuration key to remove. 198 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 199 */ 200 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) removeConfig(long configKey)201 public void removeConfig(long configKey) throws StatsUnavailableException { 202 synchronized (sLock) { 203 try { 204 IStatsManagerService service = getIStatsManagerServiceLocked(); 205 service.removeConfiguration(configKey, mContext.getOpPackageName()); 206 } catch (RemoteException e) { 207 Log.e(TAG, "Failed to connect to statsmanager when removing configuration"); 208 throw new StatsUnavailableException("could not connect", e); 209 } catch (SecurityException e) { 210 throw new StatsUnavailableException(e.getMessage(), e); 211 } catch (IllegalStateException e) { 212 Log.e(TAG, "Failed to removeConfig in statsmanager"); 213 throw new StatsUnavailableException(e.getMessage(), e); 214 } 215 } 216 } 217 218 // TODO: Temporary for backwards compatibility. Remove. 219 /** 220 * @deprecated Use {@link #removeConfig(long)} 221 */ 222 @Deprecated 223 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) removeConfiguration(long configKey)224 public boolean removeConfiguration(long configKey) { 225 try { 226 removeConfig(configKey); 227 return true; 228 } catch (StatsUnavailableException e) { 229 return false; 230 } 231 } 232 233 /** 234 * Set the PendingIntent to be used when broadcasting subscriber information to the given 235 * subscriberId within the given config. 236 * <p> 237 * Suppose that the calling uid has added a config with key configKey, and that in this config 238 * it is specified that when a particular anomaly is detected, a broadcast should be sent to 239 * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with 240 * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast 241 * when the anomaly is detected. 242 * <p> 243 * When statsd sends the broadcast, the PendingIntent will used to send an intent with 244 * information of 245 * {@link #EXTRA_STATS_CONFIG_UID}, 246 * {@link #EXTRA_STATS_CONFIG_KEY}, 247 * {@link #EXTRA_STATS_SUBSCRIPTION_ID}, 248 * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, 249 * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and 250 * {@link #EXTRA_STATS_DIMENSIONS_VALUE}. 251 * <p> 252 * This function can only be called by the owner (uid) of the config. It must be called each 253 * time statsd starts. The config must have been added first (via {@link #addConfig}). 254 * This call can block on statsd. 255 * 256 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber 257 * associated with the given subscriberId. May be null, in which case 258 * it undoes any previous setting of this subscriberId. 259 * @param configKey The integer naming the config to which this subscriber is attached. 260 * @param subscriberId ID of the subscriber, as used in the config. 261 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 262 */ 263 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) setBroadcastSubscriber( PendingIntent pendingIntent, long configKey, long subscriberId)264 public void setBroadcastSubscriber( 265 PendingIntent pendingIntent, long configKey, long subscriberId) 266 throws StatsUnavailableException { 267 synchronized (sLock) { 268 try { 269 IStatsManagerService service = getIStatsManagerServiceLocked(); 270 if (pendingIntent != null) { 271 service.setBroadcastSubscriber(configKey, subscriberId, pendingIntent, 272 mContext.getOpPackageName()); 273 } else { 274 service.unsetBroadcastSubscriber(configKey, subscriberId, 275 mContext.getOpPackageName()); 276 } 277 } catch (RemoteException e) { 278 Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", 279 e); 280 throw new StatsUnavailableException("could not connect", e); 281 } catch (SecurityException e) { 282 throw new StatsUnavailableException(e.getMessage(), e); 283 } 284 } 285 } 286 287 // TODO: Temporary for backwards compatibility. Remove. 288 /** 289 * @deprecated Use {@link #setBroadcastSubscriber(PendingIntent, long, long)} 290 */ 291 @Deprecated 292 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) setBroadcastSubscriber( long configKey, long subscriberId, PendingIntent pendingIntent)293 public boolean setBroadcastSubscriber( 294 long configKey, long subscriberId, PendingIntent pendingIntent) { 295 try { 296 setBroadcastSubscriber(pendingIntent, configKey, subscriberId); 297 return true; 298 } catch (StatsUnavailableException e) { 299 return false; 300 } 301 } 302 303 /** 304 * Registers the operation that is called to retrieve the metrics data. This must be called 305 * each time statsd starts. The config must have been added first (via {@link #addConfig}, 306 * although addConfig could have been called on a previous boot). This operation allows 307 * statsd to send metrics data whenever statsd determines that the metrics in memory are 308 * approaching the memory limits. The fetch operation should call {@link #getReports} to fetch 309 * the data, which also deletes the retrieved metrics from statsd's memory. 310 * This call can block on statsd. 311 * 312 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber 313 * associated with the given subscriberId. May be null, in which case 314 * it removes any associated pending intent with this configKey. 315 * @param configKey The integer naming the config to which this operation is attached. 316 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 317 */ 318 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) setFetchReportsOperation(PendingIntent pendingIntent, long configKey)319 public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey) 320 throws StatsUnavailableException { 321 synchronized (sLock) { 322 try { 323 IStatsManagerService service = getIStatsManagerServiceLocked(); 324 if (pendingIntent == null) { 325 service.removeDataFetchOperation(configKey, mContext.getOpPackageName()); 326 } else { 327 service.setDataFetchOperation(configKey, pendingIntent, 328 mContext.getOpPackageName()); 329 } 330 331 } catch (RemoteException e) { 332 Log.e(TAG, "Failed to connect to statsmanager when registering data listener."); 333 throw new StatsUnavailableException("could not connect", e); 334 } catch (SecurityException e) { 335 throw new StatsUnavailableException(e.getMessage(), e); 336 } 337 } 338 } 339 340 /** 341 * Registers the operation that is called whenever there is a change in which configs are 342 * active. This must be called each time statsd starts. This operation allows 343 * statsd to inform clients that they should pull data of the configs that are currently 344 * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs 345 * that are active and stop pulling data of configs that are no longer active. 346 * This call can block on statsd. 347 * 348 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber 349 * associated with the given subscriberId. May be null, in which case 350 * it removes any associated pending intent for this client. 351 * @return A list of configs that are currently active for this client. If the pendingIntent is 352 * null, this will be an empty list. 353 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 354 */ 355 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) setActiveConfigsChangedOperation(@ullable PendingIntent pendingIntent)356 public @NonNull long[] setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent) 357 throws StatsUnavailableException { 358 synchronized (sLock) { 359 try { 360 IStatsManagerService service = getIStatsManagerServiceLocked(); 361 if (pendingIntent == null) { 362 service.removeActiveConfigsChangedOperation(mContext.getOpPackageName()); 363 return new long[0]; 364 } else { 365 return service.setActiveConfigsChangedOperation(pendingIntent, 366 mContext.getOpPackageName()); 367 } 368 369 } catch (RemoteException e) { 370 Log.e(TAG, "Failed to connect to statsmanager " 371 + "when registering active configs listener."); 372 throw new StatsUnavailableException("could not connect", e); 373 } catch (SecurityException e) { 374 throw new StatsUnavailableException(e.getMessage(), e); 375 } 376 } 377 } 378 379 /** 380 * Registers the operation that is called whenever there is a change in the restricted metrics 381 * for a specified config that are present for this client. This operation allows statsd to 382 * inform the client about the current restricted metric ids available to be queried for the 383 * specified config. This call can block on statsd. 384 * 385 * If there is no config in statsd that matches the provided config package and key, an empty 386 * list is returned. The pending intent will be tracked, and the operation will be called 387 * whenever a matching config is added. 388 * 389 * @param configKey The configKey passed by the package that added the config in 390 * StatsManager#addConfig 391 * @param configPackage The package that added the config in StatsManager#addConfig 392 * @param pendingIntent the PendingIntent to use when broadcasting info to caller. 393 * May be null, in which case it removes any associated pending intent 394 * for this client. 395 * @return A list of metric ids identifying the restricted metrics that are currently available 396 * to be queried for the specified config. 397 * If the pendingIntent is null, this will be an empty list. 398 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 399 */ 400 @RequiresPermission(READ_RESTRICTED_STATS) 401 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) setRestrictedMetricsChangedOperation(long configKey, @NonNull String configPackage, @Nullable PendingIntent pendingIntent)402 public @NonNull long[] setRestrictedMetricsChangedOperation(long configKey, 403 @NonNull String configPackage, 404 @Nullable PendingIntent pendingIntent) 405 throws StatsUnavailableException { 406 synchronized (sLock) { 407 try { 408 IStatsManagerService service = getIStatsManagerServiceLocked(); 409 if (pendingIntent == null) { 410 service.removeRestrictedMetricsChangedOperation(configKey, configPackage); 411 return new long[0]; 412 } else { 413 return service.setRestrictedMetricsChangedOperation(pendingIntent, 414 configKey, configPackage); 415 } 416 417 } catch (RemoteException e) { 418 Log.e(TAG, "Failed to connect to statsmanager " 419 + "when registering restricted metrics listener."); 420 throw new StatsUnavailableException("could not connect", e); 421 } catch (SecurityException e) { 422 throw new StatsUnavailableException(e.getMessage(), e); 423 } 424 } 425 } 426 427 /** 428 * Queries the underlying service based on query received and populates the OutcomeReceiver via 429 * callback. This call is blocking on statsd being available, but is otherwise nonblocking. 430 * i.e. the call can return before the query processing is done. 431 * <p> 432 * Two types of tables are supported: Metric tables and the device information table. 433 * </p> 434 * <p> 435 * The device information table is named device_info and contains the following columns: 436 * sdkVersion, model, product, hardware, device, osBuild, fingerprint, brand, manufacturer, and 437 * board. These columns correspond to {@link Build.VERSION.SDK_INT}, {@link Build.MODEL}, 438 * {@link Build.PRODUCT}, {@link Build.HARDWARE}, {@link Build.DEVICE}, {@link Build.ID}, 439 * {@link Build.FINGERPRINT}, {@link Build.BRAND}, {@link Build.MANUFACTURER}, 440 * {@link Build.BOARD} respectively. 441 * </p> 442 * <p> 443 * The metric tables are named metric_METRIC_ID where METRIC_ID is the metric id that is part 444 * of the wire encoded config passed to {@link #addConfig(long, byte[])}. If the metric id is 445 * negative, then the '-' character is replaced with 'n' in the table name. Each metric table 446 * contains the 3 columns followed by n columns of the following form: atomId, 447 * elapsedTimestampNs, wallTimestampNs, field_1, field_2, field_3 ... field_n. These 448 * columns correspond to to the id of the atom from frameworks/proto_logging/stats/atoms.proto, 449 * time when the atom is recorded, and the data fields within each atom. 450 * </p> 451 * @param configKey The configKey passed by the package that added 452 * the config being queried in StatsManager#addConfig 453 * @param configPackage The package that added the config being queried in 454 * StatsManager#addConfig 455 * @param query the query object encapsulating a sql-string and necessary config to query 456 * underlying sql-based data store. 457 * @param executor the executor on which outcomeReceiver will be invoked. 458 * @param outcomeReceiver the receiver to be populated with cursor pointing to result data. 459 */ 460 @RequiresPermission(READ_RESTRICTED_STATS) 461 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) query(long configKey, @NonNull String configPackage, @NonNull StatsQuery query, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<StatsCursor, StatsQueryException> outcomeReceiver)462 public void query(long configKey, @NonNull String configPackage, @NonNull StatsQuery query, 463 @NonNull @CallbackExecutor Executor executor, 464 @NonNull OutcomeReceiver<StatsCursor, StatsQueryException> outcomeReceiver) 465 throws StatsUnavailableException { 466 if(query.getSqlDialect() != StatsQuery.DIALECT_SQLITE) { 467 executor.execute(() -> { 468 outcomeReceiver.onError(new StatsQueryException("Unsupported Sql Dialect")); 469 }); 470 return; 471 } 472 473 StatsQueryCallbackInternal callbackInternal = 474 new StatsQueryCallbackInternal(outcomeReceiver, executor); 475 synchronized (sLock) { 476 try { 477 IStatsManagerService service = getIStatsManagerServiceLocked(); 478 service.querySql(query.getRawSql(), query.getMinSqlClientVersion(), 479 query.getPolicyConfig(), callbackInternal, configKey, 480 configPackage); 481 } catch (RemoteException | IllegalStateException e) { 482 throw new StatsUnavailableException("could not connect", e); 483 } 484 } 485 } 486 487 488 // TODO: Temporary for backwards compatibility. Remove. 489 /** 490 * @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)} 491 */ 492 @Deprecated 493 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) setDataFetchOperation(long configKey, PendingIntent pendingIntent)494 public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) { 495 try { 496 setFetchReportsOperation(pendingIntent, configKey); 497 return true; 498 } catch (StatsUnavailableException e) { 499 return false; 500 } 501 } 502 503 /** 504 * Request the data collected for the given configKey. 505 * This getter is destructive - it also clears the retrieved metrics from statsd's memory. 506 * This call can block on statsd. 507 * 508 * @param configKey Configuration key to retrieve data from. 509 * @return Serialized ConfigMetricsReportList proto. 510 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 511 */ 512 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) getReports(long configKey)513 public byte[] getReports(long configKey) throws StatsUnavailableException { 514 synchronized (sLock) { 515 try { 516 IStatsManagerService service = getIStatsManagerServiceLocked(); 517 return service.getData(configKey, mContext.getOpPackageName()); 518 } catch (RemoteException e) { 519 Log.e(TAG, "Failed to connect to statsmanager when getting data"); 520 throw new StatsUnavailableException("could not connect", e); 521 } catch (SecurityException e) { 522 throw new StatsUnavailableException(e.getMessage(), e); 523 } catch (IllegalStateException e) { 524 Log.e(TAG, "Failed to getReports in statsmanager"); 525 throw new StatsUnavailableException(e.getMessage(), e); 526 } 527 } 528 } 529 530 // TODO: Temporary for backwards compatibility. Remove. 531 /** 532 * @deprecated Use {@link #getReports(long)} 533 */ 534 @Deprecated 535 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) getData(long configKey)536 public @Nullable byte[] getData(long configKey) { 537 try { 538 return getReports(configKey); 539 } catch (StatsUnavailableException e) { 540 return null; 541 } 542 } 543 544 /** 545 * Clients can request metadata for statsd. Will contain stats across all configurations but not 546 * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}. 547 * This getter is not destructive and will not reset any metrics/counters. 548 * This call can block on statsd. 549 * 550 * @return Serialized StatsdStatsReport proto. 551 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 552 */ 553 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) getStatsMetadata()554 public byte[] getStatsMetadata() throws StatsUnavailableException { 555 synchronized (sLock) { 556 try { 557 IStatsManagerService service = getIStatsManagerServiceLocked(); 558 return service.getMetadata(mContext.getOpPackageName()); 559 } catch (RemoteException e) { 560 Log.e(TAG, "Failed to connect to statsmanager when getting metadata"); 561 throw new StatsUnavailableException("could not connect", e); 562 } catch (SecurityException e) { 563 throw new StatsUnavailableException(e.getMessage(), e); 564 } catch (IllegalStateException e) { 565 Log.e(TAG, "Failed to getStatsMetadata in statsmanager"); 566 throw new StatsUnavailableException(e.getMessage(), e); 567 } 568 } 569 } 570 571 // TODO: Temporary for backwards compatibility. Remove. 572 /** 573 * @deprecated Use {@link #getStatsMetadata()} 574 */ 575 @Deprecated 576 @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) getMetadata()577 public @Nullable byte[] getMetadata() { 578 try { 579 return getStatsMetadata(); 580 } catch (StatsUnavailableException e) { 581 return null; 582 } 583 } 584 585 /** 586 * Returns the experiments IDs registered with statsd, or an empty array if there aren't any. 587 * 588 * This call can block on statsd. 589 * 590 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 591 */ 592 @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) getRegisteredExperimentIds()593 public long[] getRegisteredExperimentIds() 594 throws StatsUnavailableException { 595 synchronized (sLock) { 596 try { 597 IStatsManagerService service = getIStatsManagerServiceLocked(); 598 return service.getRegisteredExperimentIds(); 599 } catch (RemoteException e) { 600 if (DEBUG) { 601 Log.d(TAG, 602 "Failed to connect to StatsManagerService when getting " 603 + "registered experiment IDs"); 604 } 605 throw new StatsUnavailableException("could not connect", e); 606 } catch (SecurityException e) { 607 throw new StatsUnavailableException(e.getMessage(), e); 608 } catch (IllegalStateException e) { 609 Log.e(TAG, "Failed to getRegisteredExperimentIds in statsmanager"); 610 throw new StatsUnavailableException(e.getMessage(), e); 611 } 612 } 613 } 614 615 /** 616 * Sets a callback for an atom when that atom is to be pulled. The stats service will 617 * invoke pullData in the callback when the stats service determines that this atom needs to be 618 * pulled. This method should not be called by third-party apps. 619 * 620 * @param atomTag The tag of the atom for this puller callback. 621 * @param metadata Optional metadata specifying the timeout, cool down time, and 622 * additive fields for mapping isolated to host uids. 623 * @param executor The executor in which to run the callback. 624 * @param callback The callback to be invoked when the stats service pulls the atom. 625 * 626 */ 627 @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, @NonNull @CallbackExecutor Executor executor, @NonNull StatsPullAtomCallback callback)628 public void setPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, 629 @NonNull @CallbackExecutor Executor executor, 630 @NonNull StatsPullAtomCallback callback) { 631 long coolDownMillis = 632 metadata == null ? DEFAULT_COOL_DOWN_MILLIS : metadata.mCoolDownMillis; 633 long timeoutMillis = metadata == null ? DEFAULT_TIMEOUT_MILLIS : metadata.mTimeoutMillis; 634 int[] additiveFields = metadata == null ? new int[0] : metadata.mAdditiveFields; 635 if (additiveFields == null) { 636 additiveFields = new int[0]; 637 } 638 639 synchronized (sLock) { 640 try { 641 IStatsManagerService service = getIStatsManagerServiceLocked(); 642 PullAtomCallbackInternal rec = 643 new PullAtomCallbackInternal(atomTag, callback, executor); 644 service.registerPullAtomCallback( 645 atomTag, coolDownMillis, timeoutMillis, additiveFields, rec); 646 } catch (RemoteException e) { 647 throw new RuntimeException("Unable to register pull callback", e); 648 } 649 } 650 } 651 652 /** 653 * Clears a callback for an atom when that atom is to be pulled. Note that any ongoing 654 * pulls will still occur. This method should not be called by third-party apps. 655 * 656 * @param atomTag The tag of the atom of which to unregister 657 * 658 */ 659 @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) clearPullAtomCallback(int atomTag)660 public void clearPullAtomCallback(int atomTag) { 661 synchronized (sLock) { 662 try { 663 IStatsManagerService service = getIStatsManagerServiceLocked(); 664 service.unregisterPullAtomCallback(atomTag); 665 } catch (RemoteException e) { 666 throw new RuntimeException("Unable to unregister pull atom callback"); 667 } 668 } 669 } 670 671 private static class PullAtomCallbackInternal extends IPullAtomCallback.Stub { 672 public final int mAtomId; 673 public final StatsPullAtomCallback mCallback; 674 public final Executor mExecutor; 675 PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor)676 PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor) { 677 mAtomId = atomId; 678 mCallback = callback; 679 mExecutor = executor; 680 } 681 682 @Override onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver)683 public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) { 684 final long token = Binder.clearCallingIdentity(); 685 try { 686 mExecutor.execute(() -> { 687 List<StatsEvent> data = new ArrayList<>(); 688 int successInt = mCallback.onPullAtom(atomTag, data); 689 boolean success = successInt == PULL_SUCCESS; 690 StatsEventParcel[] parcels = new StatsEventParcel[data.size()]; 691 for (int i = 0; i < data.size(); i++) { 692 parcels[i] = new StatsEventParcel(); 693 parcels[i].buffer = data.get(i).getBytes(); 694 } 695 try { 696 resultReceiver.pullFinished(atomTag, success, parcels); 697 } catch (RemoteException e) { 698 Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId 699 + " due to TransactionTooLarge. Calling pullFinish with no data"); 700 StatsEventParcel[] emptyData = new StatsEventParcel[0]; 701 try { 702 resultReceiver.pullFinished(atomTag, /*success=*/false, emptyData); 703 } catch (RemoteException nestedException) { 704 Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId 705 + " with empty payload"); 706 } 707 } 708 }); 709 } finally { 710 Binder.restoreCallingIdentity(token); 711 } 712 } 713 } 714 715 /** 716 * Metadata required for registering a StatsPullAtomCallback. 717 * All fields are optional, and defaults will be used for fields that are unspecified. 718 * 719 */ 720 public static class PullAtomMetadata { 721 private final long mCoolDownMillis; 722 private final long mTimeoutMillis; 723 private final int[] mAdditiveFields; 724 725 // Private Constructor for builder PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields)726 private PullAtomMetadata(long coolDownMillis, long timeoutMillis, int[] additiveFields) { 727 mCoolDownMillis = coolDownMillis; 728 mTimeoutMillis = timeoutMillis; 729 mAdditiveFields = additiveFields; 730 } 731 732 /** 733 * Builder for PullAtomMetadata. 734 */ 735 public static class Builder { 736 private long mCoolDownMillis; 737 private long mTimeoutMillis; 738 private int[] mAdditiveFields; 739 740 /** 741 * Returns a new PullAtomMetadata.Builder object for constructing PullAtomMetadata for 742 * StatsManager#registerPullAtomCallback 743 */ Builder()744 public Builder() { 745 mCoolDownMillis = DEFAULT_COOL_DOWN_MILLIS; 746 mTimeoutMillis = DEFAULT_TIMEOUT_MILLIS; 747 mAdditiveFields = null; 748 } 749 750 /** 751 * Set the cool down time of the pull in milliseconds. If two successive pulls are 752 * issued within the cool down, a cached version of the first pull will be used for the 753 * second pull. The minimum allowed cool down is 1 second. 754 */ 755 @NonNull setCoolDownMillis(long coolDownMillis)756 public Builder setCoolDownMillis(long coolDownMillis) { 757 mCoolDownMillis = coolDownMillis; 758 return this; 759 } 760 761 /** 762 * Set the maximum time the pull can take in milliseconds. The maximum allowed timeout 763 * is 10 seconds. 764 */ 765 @NonNull setTimeoutMillis(long timeoutMillis)766 public Builder setTimeoutMillis(long timeoutMillis) { 767 mTimeoutMillis = timeoutMillis; 768 return this; 769 } 770 771 /** 772 * Set the additive fields of this pulled atom. 773 * 774 * This is only applicable for atoms which have a uid field. When tasks are run in 775 * isolated processes, the data will be attributed to the host uid. Additive fields 776 * will be combined when the non-additive fields are the same. 777 */ 778 @NonNull setAdditiveFields(@onNull int[] additiveFields)779 public Builder setAdditiveFields(@NonNull int[] additiveFields) { 780 mAdditiveFields = additiveFields; 781 return this; 782 } 783 784 /** 785 * Builds and returns a PullAtomMetadata object with the values set in the builder and 786 * defaults for unset fields. 787 */ 788 @NonNull build()789 public PullAtomMetadata build() { 790 return new PullAtomMetadata(mCoolDownMillis, mTimeoutMillis, mAdditiveFields); 791 } 792 } 793 794 /** 795 * Return the cool down time of this pull in milliseconds. 796 */ getCoolDownMillis()797 public long getCoolDownMillis() { 798 return mCoolDownMillis; 799 } 800 801 /** 802 * Return the maximum amount of time this pull can take in milliseconds. 803 */ getTimeoutMillis()804 public long getTimeoutMillis() { 805 return mTimeoutMillis; 806 } 807 808 /** 809 * Return the additive fields of this pulled atom. 810 * 811 * This is only applicable for atoms that have a uid field. When tasks are run in 812 * isolated processes, the data will be attributed to the host uid. Additive fields 813 * will be combined when the non-additive fields are the same. 814 */ 815 @Nullable getAdditiveFields()816 public int[] getAdditiveFields() { 817 return mAdditiveFields; 818 } 819 } 820 821 /** 822 * Callback interface for pulling atoms requested by the stats service. 823 * 824 */ 825 public interface StatsPullAtomCallback { 826 /** 827 * Pull data for the specified atom tag, filling in the provided list of StatsEvent data. 828 * @return {@link #PULL_SUCCESS} if the pull was successful, or {@link #PULL_SKIP} if not. 829 */ onPullAtom(int atomTag, @NonNull List<StatsEvent> data)830 int onPullAtom(int atomTag, @NonNull List<StatsEvent> data); 831 } 832 833 @GuardedBy("sLock") getIStatsManagerServiceLocked()834 private IStatsManagerService getIStatsManagerServiceLocked() { 835 if (mStatsManagerService != null) { 836 return mStatsManagerService; 837 } 838 mStatsManagerService = IStatsManagerService.Stub.asInterface( 839 StatsFrameworkInitializer 840 .getStatsServiceManager() 841 .getStatsManagerServiceRegisterer() 842 .get()); 843 return mStatsManagerService; 844 } 845 846 private static class StatsQueryCallbackInternal extends IStatsQueryCallback.Stub { 847 OutcomeReceiver<StatsCursor, StatsQueryException> queryCallback; 848 Executor mExecutor; 849 StatsQueryCallbackInternal(OutcomeReceiver<StatsCursor, StatsQueryException> queryCallback, @NonNull @CallbackExecutor Executor executor)850 StatsQueryCallbackInternal(OutcomeReceiver<StatsCursor, StatsQueryException> queryCallback, 851 @NonNull @CallbackExecutor Executor executor) { 852 this.queryCallback = queryCallback; 853 this.mExecutor = executor; 854 } 855 856 @Override sendResults(String[] queryData, String[] columnNames, int[] columnTypes, int rowCount)857 public void sendResults(String[] queryData, String[] columnNames, int[] columnTypes, 858 int rowCount) { 859 if (!SdkLevel.isAtLeastU()) { 860 throw new IllegalStateException( 861 "StatsManager#query is not available before Android U"); 862 } 863 final long token = Binder.clearCallingIdentity(); 864 try { 865 mExecutor.execute(() -> { 866 StatsCursor cursor = new StatsCursor(queryData, columnNames, columnTypes, 867 rowCount); 868 queryCallback.onResult(cursor); 869 }); 870 } finally { 871 Binder.restoreCallingIdentity(token); 872 } 873 } 874 875 @Override sendFailure(String error)876 public void sendFailure(String error) { 877 if (!SdkLevel.isAtLeastU()) { 878 throw new IllegalStateException( 879 "StatsManager#query is not available before Android U"); 880 } 881 final long token = Binder.clearCallingIdentity(); 882 try { 883 mExecutor.execute(() -> { 884 queryCallback.onError(new StatsQueryException(error)); 885 }); 886 } finally { 887 Binder.restoreCallingIdentity(token); 888 } 889 } 890 } 891 892 /** 893 * Exception thrown when communication with the stats service fails (eg if it is not available). 894 * This might be thrown early during boot before the stats service has started or if it crashed. 895 */ 896 public static class StatsUnavailableException extends AndroidException { StatsUnavailableException(String reason)897 public StatsUnavailableException(String reason) { 898 super("Failed to connect to statsd: " + reason); 899 } 900 StatsUnavailableException(String reason, Throwable e)901 public StatsUnavailableException(String reason, Throwable e) { 902 super("Failed to connect to statsd: " + reason, e); 903 } 904 } 905 906 /** 907 * Exception thrown when executing a query in statsd fails for any reason. This might be thrown 908 * if the query is malformed or if there is a database error when executing the query. 909 */ 910 public static class StatsQueryException extends AndroidException { StatsQueryException(@onNull String reason)911 public StatsQueryException(@NonNull String reason) { 912 super("Failed to query statsd: " + reason); 913 } 914 StatsQueryException(@onNull String reason, @NonNull Throwable e)915 public StatsQueryException(@NonNull String reason, @NonNull Throwable e) { 916 super("Failed to query statsd: " + reason, e); 917 } 918 } 919 } 920