1 /* 2 * Copyright (C) 2016 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 17 package android.os.health; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.FloatRange; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SystemService; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.Context; 27 import android.hardware.power.CpuHeadroomResult; 28 import android.hardware.power.GpuHeadroomResult; 29 import android.os.BatteryStats; 30 import android.os.Build; 31 import android.os.Bundle; 32 import android.os.CpuHeadroomParams; 33 import android.os.CpuHeadroomParamsInternal; 34 import android.os.GpuHeadroomParams; 35 import android.os.GpuHeadroomParamsInternal; 36 import android.os.IHintManager; 37 import android.os.IPowerStatsService; 38 import android.os.OutcomeReceiver; 39 import android.os.PowerMonitor; 40 import android.os.PowerMonitorReadings; 41 import android.os.Process; 42 import android.os.RemoteException; 43 import android.os.ResultReceiver; 44 import android.os.ServiceManager; 45 import android.os.SynchronousResultReceiver; 46 import android.util.Pair; 47 import android.util.Slog; 48 49 import com.android.internal.app.IBatteryStats; 50 import com.android.server.power.optimization.Flags; 51 52 import java.util.Arrays; 53 import java.util.Comparator; 54 import java.util.List; 55 import java.util.concurrent.Executor; 56 import java.util.concurrent.TimeoutException; 57 import java.util.function.Consumer; 58 59 /** 60 * Provides access to data about how various system resources are used by applications. 61 * @more 62 * <p> 63 * If you are going to be using this class to log your application's resource usage, 64 * please consider the amount of resources (battery, network, etc) that will be used 65 * by the logging itself. It can be substantial. 66 * <p> 67 * <b>Battery Usage</b><br> 68 * Since Android version {@link android.os.Build.VERSION_CODES#Q}, the statistics related to power 69 * (battery) usage are recorded since the device was last considered fully charged (for previous 70 * versions, it is instead since the device was last unplugged). 71 * It is expected that applications schedule more work to do while the device is 72 * plugged in (e.g. using {@link android.app.job.JobScheduler JobScheduler}), and 73 * while that can affect charging rates, it is still preferable to actually draining 74 * the battery. 75 * <p> 76 * <b>CPU/GPU Usage</b><br> 77 * CPU/GPU headroom APIs are designed to be best used by applications with consistent and intense 78 * workload such as games to query the remaining capacity headroom over a short period and perform 79 * optimization accordingly. Due to the nature of the fast job scheduling and frequency scaling of 80 * CPU and GPU, the headroom by nature will have "TOCTOU" problem which makes it less suitable for 81 * apps with inconsistent or low workload to take any useful action but simply monitoring. And to 82 * avoid oscillation it's not recommended to adjust workload too frequent (on each polling request) 83 * or too aggressively. As the headroom calculation is more based on reflecting past history usage 84 * than predicting future capacity. Take game as an example, if the API returns CPU headroom of 0 in 85 * one scenario (especially if it's constant across multiple calls), or some value significantly 86 * smaller than other scenarios, then it can reason that the recent performance result is more CPU 87 * bottlenecked. Then reducing the CPU workload intensity can help reserve some headroom to handle 88 * the load variance better, which can result in less frame drops or smooth FPS value. On the other 89 * hand, if the API returns large CPU headroom constantly, the app can be more confident to increase 90 * the workload and expect higher possibility of device meeting its performance expectation. 91 * App can also use thermal APIs to read the current thermal status and headroom first, then poll 92 * the CPU and GPU headroom if the device is (about to) getting thermal throttled. If the CPU/GPU 93 * headrooms provide enough significance such as one valued at 0 while the other at 100, then it can 94 * be used to infer that reducing CPU workload could be more efficient to cool down the device. 95 * There is a caveat that the power controller may scale down the frequency of the CPU and GPU due 96 * to thermal and other reasons, which can result in a higher than usual percentage usage of the 97 * capacity. 98 */ 99 @SystemService(Context.SYSTEM_HEALTH_SERVICE) 100 public class SystemHealthManager { 101 private static final String TAG = "SystemHealthManager"; 102 @NonNull 103 private final IBatteryStats mBatteryStats; 104 @Nullable 105 private final IPowerStatsService mPowerStats; 106 @Nullable 107 private final IHintManager mHintManager; 108 @Nullable 109 private final IHintManager.HintManagerClientData mHintManagerClientData; 110 private List<PowerMonitor> mPowerMonitorsInfo; 111 private final Object mPowerMonitorsLock = new Object(); 112 private static final long TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS = 10_000; 113 114 private static class PendingUidSnapshots { 115 public int[] uids; 116 public SynchronousResultReceiver resultReceiver; 117 } 118 119 private final PendingUidSnapshots mPendingUidSnapshots = new PendingUidSnapshots(); 120 121 /** 122 * Construct a new SystemHealthManager object. 123 * 124 * @hide 125 */ 126 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) SystemHealthManager()127 public SystemHealthManager() { 128 this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)), 129 IPowerStatsService.Stub.asInterface( 130 ServiceManager.getService(Context.POWER_STATS_SERVICE)), 131 IHintManager.Stub.asInterface( 132 ServiceManager.getService(Context.PERFORMANCE_HINT_SERVICE))); 133 } 134 135 /** {@hide} */ SystemHealthManager(@onNull IBatteryStats batteryStats, @Nullable IPowerStatsService powerStats, @Nullable IHintManager hintManager)136 public SystemHealthManager(@NonNull IBatteryStats batteryStats, 137 @Nullable IPowerStatsService powerStats, @Nullable IHintManager hintManager) { 138 mBatteryStats = batteryStats; 139 mPowerStats = powerStats; 140 mHintManager = hintManager; 141 IHintManager.HintManagerClientData data = null; 142 if (mHintManager != null) { 143 try { 144 data = mHintManager.getClientData(); 145 } catch (RemoteException e) { 146 Slog.e(TAG, "Failed to get hint manager client data", e); 147 } 148 } 149 mHintManagerClientData = data; 150 } 151 152 /** 153 * Provides an estimate of available CPU capacity headroom of the device. 154 * <p> 155 * The value can be used by the calling application to determine if the workload was CPU bound 156 * and then take action accordingly to ensure that the workload can be completed smoothly. It 157 * can also be used with the thermal status and headroom to determine if reducing the CPU bound 158 * workload can help reduce the device temperature to avoid thermal throttling. 159 * <p> 160 * If the params are valid, each call will perform at least one synchronous binder transaction 161 * that can take more than 1ms. So it's not recommended to call or wait for this on critical 162 * threads. Some devices may implement this as an on-demand API with lazy initialization, so the 163 * caller should expect higher latency when making the first call (especially with non-default 164 * params) since app starts or after changing params, as the device may need to change its data 165 * collection. 166 * 167 * @param params params to customize the CPU headroom calculation, or null to use default. 168 * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable due to 169 * server error or not enough user CPU workload. 170 * Each valid value ranges from [0, 100], where 0 indicates no more cpu resources can be 171 * granted 172 * @throws UnsupportedOperationException if the API is unsupported. 173 * @throws IllegalArgumentException if the params are invalid. 174 * @throws SecurityException if the TIDs of the params don't belong to the same process. 175 * @throws IllegalStateException if the TIDs of the params don't have the same affinity setting. 176 */ 177 @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) getCpuHeadroom( @ullable CpuHeadroomParams params)178 public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom( 179 @Nullable CpuHeadroomParams params) { 180 if (mHintManager == null || mHintManagerClientData == null 181 || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { 182 throw new UnsupportedOperationException(); 183 } 184 if (params != null) { 185 if (params.mInternal.tids != null && (params.mInternal.tids.length == 0 186 || params.mInternal.tids.length 187 > mHintManagerClientData.maxCpuHeadroomThreads)) { 188 throw new IllegalArgumentException( 189 "Invalid number of TIDs: " + params.mInternal.tids.length); 190 } 191 if (params.mInternal.calculationWindowMillis 192 < mHintManagerClientData.supportInfo.headroom.cpuMinCalculationWindowMillis 193 || params.mInternal.calculationWindowMillis 194 > mHintManagerClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis) { 195 throw new IllegalArgumentException( 196 "Invalid calculation window: " 197 + params.mInternal.calculationWindowMillis + ", expect range: [" 198 + mHintManagerClientData.supportInfo.headroom.cpuMinCalculationWindowMillis 199 + ", " 200 + mHintManagerClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis 201 + "]"); 202 } 203 } 204 try { 205 final CpuHeadroomResult ret = mHintManager.getCpuHeadroom( 206 params != null ? params.mInternal : new CpuHeadroomParamsInternal()); 207 if (ret == null || ret.getTag() != CpuHeadroomResult.globalHeadroom) { 208 return Float.NaN; 209 } 210 return ret.getGlobalHeadroom(); 211 } catch (RemoteException re) { 212 throw re.rethrowFromSystemServer(); 213 } 214 } 215 216 /** 217 * Gets the maximum number of TIDs this device supports for getting CPU headroom. 218 * <p> 219 * See {@link CpuHeadroomParams.Builder#setTids(int...)}. 220 * 221 * @return the maximum size of TIDs supported 222 * @throws UnsupportedOperationException if the CPU headroom API is unsupported. 223 */ 224 @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) getMaxCpuHeadroomTidsSize()225 public @IntRange(from = 1) int getMaxCpuHeadroomTidsSize() { 226 if (mHintManager == null || mHintManagerClientData == null 227 || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { 228 throw new UnsupportedOperationException(); 229 } 230 return mHintManagerClientData.maxCpuHeadroomThreads; 231 } 232 233 /** 234 * Provides an estimate of available GPU capacity headroom of the device. 235 * <p> 236 * The value can be used by the calling application to determine if the workload was GPU bound 237 * and then take action accordingly to ensure that the workload can be completed smoothly. It 238 * can also be used with the thermal status and headroom to determine if reducing the GPU bound 239 * workload can help reduce the device temperature to avoid thermal throttling. 240 * <p> 241 * If the params are valid, each call will perform at least one synchronous binder transaction 242 * that can take more than 1ms. So it's not recommended to call or wait for this on critical 243 * threads. Some devices may implement this as an on-demand API with lazy initialization, so the 244 * caller should expect higher latency when making the first call (especially with non-default 245 * params) since app starts or after changing params, as the device may need to change its data 246 * collection. 247 * 248 * @param params params to customize the GPU headroom calculation, or null to use default. 249 * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable. 250 * Each valid value ranges from [0, 100], where 0 indicates no more cpu resources can be 251 * granted. 252 * @throws UnsupportedOperationException if the API is unsupported. 253 * @throws IllegalArgumentException if the params are invalid. 254 */ 255 @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) getGpuHeadroom( @ullable GpuHeadroomParams params)256 public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom( 257 @Nullable GpuHeadroomParams params) { 258 if (mHintManager == null || mHintManagerClientData == null 259 || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) { 260 throw new UnsupportedOperationException(); 261 } 262 if (params != null) { 263 if (params.mInternal.calculationWindowMillis 264 < mHintManagerClientData.supportInfo.headroom.gpuMinCalculationWindowMillis 265 || params.mInternal.calculationWindowMillis 266 > mHintManagerClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis) { 267 throw new IllegalArgumentException( 268 "Invalid calculation window: " 269 + params.mInternal.calculationWindowMillis + ", expect range: [" 270 + mHintManagerClientData.supportInfo.headroom.gpuMinCalculationWindowMillis 271 + ", " 272 + mHintManagerClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis 273 + "]"); 274 } 275 } 276 try { 277 final GpuHeadroomResult ret = mHintManager.getGpuHeadroom( 278 params != null ? params.mInternal : new GpuHeadroomParamsInternal()); 279 if (ret == null || ret.getTag() != GpuHeadroomResult.globalHeadroom) { 280 return Float.NaN; 281 } 282 return ret.getGlobalHeadroom(); 283 } catch (RemoteException re) { 284 throw re.rethrowFromSystemServer(); 285 } 286 } 287 288 /** 289 * Gets the range of the calculation window size for CPU headroom. 290 * <p> 291 * See {@link CpuHeadroomParams.Builder#setCalculationWindowMillis(int)}. 292 * 293 * @return the range of the calculation window size supported in milliseconds. 294 * @throws UnsupportedOperationException if the CPU headroom API is unsupported. 295 */ 296 @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) 297 @NonNull getCpuHeadroomCalculationWindowRange()298 public Pair<Integer, Integer> getCpuHeadroomCalculationWindowRange() { 299 if (mHintManager == null || mHintManagerClientData == null 300 || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { 301 throw new UnsupportedOperationException(); 302 } 303 return new Pair<>( 304 mHintManagerClientData.supportInfo.headroom.cpuMinCalculationWindowMillis, 305 mHintManagerClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis); 306 } 307 308 /** 309 * Gets the range of the calculation window size for GPU headroom. 310 * <p> 311 * See {@link GpuHeadroomParams.Builder#setCalculationWindowMillis(int)}. 312 * 313 * @return the range of the calculation window size supported in milliseconds. 314 * @throws UnsupportedOperationException if the GPU headroom API is unsupported. 315 */ 316 @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) 317 @NonNull getGpuHeadroomCalculationWindowRange()318 public Pair<Integer, Integer> getGpuHeadroomCalculationWindowRange() { 319 if (mHintManager == null || mHintManagerClientData == null 320 || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) { 321 throw new UnsupportedOperationException(); 322 } 323 return new Pair<>( 324 mHintManagerClientData.supportInfo.headroom.gpuMinCalculationWindowMillis, 325 mHintManagerClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis); 326 } 327 328 /** 329 * Gets minimum polling interval for calling {@link #getCpuHeadroom(CpuHeadroomParams)} in 330 * milliseconds. 331 * <p> 332 * The {@link #getCpuHeadroom(CpuHeadroomParams)} API may return cached result if called more 333 * frequent than the interval. 334 * 335 * @throws UnsupportedOperationException if the API is unsupported. 336 */ 337 @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) getCpuHeadroomMinIntervalMillis()338 public long getCpuHeadroomMinIntervalMillis() { 339 if (mHintManager == null || mHintManagerClientData == null 340 || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { 341 throw new UnsupportedOperationException(); 342 } 343 return mHintManagerClientData.supportInfo.headroom.cpuMinIntervalMillis; 344 } 345 346 /** 347 * Gets minimum polling interval for calling {@link #getGpuHeadroom(GpuHeadroomParams)} in 348 * milliseconds. 349 * <p> 350 * The {@link #getGpuHeadroom(GpuHeadroomParams)} API may return cached result if called more 351 * frequent than the interval. 352 * 353 * @throws UnsupportedOperationException if the API is unsupported. 354 */ 355 @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) getGpuHeadroomMinIntervalMillis()356 public long getGpuHeadroomMinIntervalMillis() { 357 if (mHintManager == null || mHintManagerClientData == null 358 || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) { 359 throw new UnsupportedOperationException(); 360 } 361 return mHintManagerClientData.supportInfo.headroom.gpuMinIntervalMillis; 362 } 363 364 /** 365 * Obtain a SystemHealthManager object for the supplied context. 366 * 367 * @hide 368 */ 369 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) from(Context context)370 public static SystemHealthManager from(Context context) { 371 return (SystemHealthManager) context.getSystemService(Context.SYSTEM_HEALTH_SERVICE); 372 } 373 374 /** 375 * Return a {@link HealthStats} object containing a snapshot of system health 376 * metrics for the given uid (user-id, which in usually corresponds to application). 377 * 378 * @param uid User ID for a given application. 379 * @return A {@link HealthStats} object containing the metrics for the requested 380 * application. The keys for this HealthStats object will be from the {@link UidHealthStats} 381 * class. 382 * @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS 383 * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats 384 * other than its own. 385 * @see Process#myUid() Process.myUid() 386 */ takeUidSnapshot(int uid)387 public HealthStats takeUidSnapshot(int uid) { 388 if (!Flags.onewayBatteryStatsService()) { 389 try { 390 final HealthStatsParceler parceler = mBatteryStats.takeUidSnapshot(uid); 391 return parceler.getHealthStats(); 392 } catch (RemoteException ex) { 393 throw ex.rethrowFromSystemServer(); 394 } 395 } 396 final HealthStats[] result = takeUidSnapshots(new int[]{uid}); 397 if (result != null && result.length >= 1) { 398 return result[0]; 399 } 400 return null; 401 } 402 403 /** 404 * Return a {@link HealthStats} object containing a snapshot of system health 405 * metrics for the application calling this API. This method is the same as calling 406 * {@code takeUidSnapshot(Process.myUid())}. 407 * 408 * @return A {@link HealthStats} object containing the metrics for this application. The keys 409 * for this HealthStats object will be from the {@link UidHealthStats} class. 410 */ takeMyUidSnapshot()411 public HealthStats takeMyUidSnapshot() { 412 return takeUidSnapshot(Process.myUid()); 413 } 414 415 /** 416 * Return a {@link HealthStats} object containing a snapshot of system health 417 * metrics for the given uids (user-id, which in usually corresponds to application). 418 * 419 * @param uids An array of User IDs to retrieve. 420 * @return An array of {@link HealthStats} objects containing the metrics for each of 421 * the requested uids. The keys for this HealthStats object will be from the 422 * {@link UidHealthStats} class. 423 * @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS 424 * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats 425 * other than its own. 426 */ takeUidSnapshots(int[] uids)427 public HealthStats[] takeUidSnapshots(int[] uids) { 428 if (!Flags.onewayBatteryStatsService()) { 429 try { 430 final HealthStatsParceler[] parcelers = mBatteryStats.takeUidSnapshots(uids); 431 final int count = uids.length; 432 final HealthStats[] results = new HealthStats[count]; 433 for (int i = 0; i < count; i++) { 434 results[i] = parcelers[i].getHealthStats(); 435 } 436 return results; 437 } catch (RemoteException ex) { 438 throw ex.rethrowFromSystemServer(); 439 } 440 } 441 442 SynchronousResultReceiver resultReceiver; 443 synchronized (mPendingUidSnapshots) { 444 if (Arrays.equals(mPendingUidSnapshots.uids, uids)) { 445 resultReceiver = mPendingUidSnapshots.resultReceiver; 446 } else { 447 mPendingUidSnapshots.uids = Arrays.copyOf(uids, uids.length); 448 mPendingUidSnapshots.resultReceiver = resultReceiver = 449 new SynchronousResultReceiver("takeUidSnapshots"); 450 try { 451 mBatteryStats.takeUidSnapshotsAsync(uids, resultReceiver); 452 } catch (RemoteException ex) { 453 throw ex.rethrowFromSystemServer(); 454 } 455 } 456 } 457 458 SynchronousResultReceiver.Result result; 459 try { 460 result = resultReceiver.awaitResult(TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS); 461 } catch (TimeoutException e) { 462 throw new RuntimeException(e); 463 } finally { 464 synchronized (mPendingUidSnapshots) { 465 if (mPendingUidSnapshots.resultReceiver == resultReceiver) { 466 mPendingUidSnapshots.uids = null; 467 mPendingUidSnapshots.resultReceiver = null; 468 } 469 } 470 } 471 472 switch (result.resultCode) { 473 case IBatteryStats.RESULT_OK: { 474 final HealthStats[] results = new HealthStats[uids.length]; 475 if (result.bundle != null) { 476 HealthStatsParceler[] parcelers = result.bundle.getParcelableArray( 477 IBatteryStats.KEY_UID_SNAPSHOTS, HealthStatsParceler.class); 478 if (parcelers != null && parcelers.length == uids.length) { 479 for (int i = 0; i < parcelers.length; i++) { 480 results[i] = parcelers[i].getHealthStats(); 481 } 482 } 483 } 484 return results; 485 } 486 case IBatteryStats.RESULT_SECURITY_EXCEPTION: { 487 throw new SecurityException(result.bundle != null 488 ? result.bundle.getString(IBatteryStats.KEY_EXCEPTION_MESSAGE) : null); 489 } 490 case IBatteryStats.RESULT_RUNTIME_EXCEPTION: { 491 throw new RuntimeException(result.bundle != null 492 ? result.bundle.getString(IBatteryStats.KEY_EXCEPTION_MESSAGE) : null); 493 } 494 default: 495 throw new RuntimeException("Error code: " + result.resultCode); 496 } 497 } 498 499 /** 500 * Asynchronously retrieves a list of supported {@link PowerMonitor}'s, which include raw ODPM 501 * (on-device power rail monitor) rails and modeled energy consumers. If ODPM is unsupported 502 * on this device this method delivers an empty list. 503 * 504 * @param executor optional Handler to deliver the callback. If not supplied, the callback 505 * may be invoked on an arbitrary thread. 506 * @param onResult callback for the result 507 */ 508 @FlaggedApi("com.android.server.power.optimization.power_monitor_api") getSupportedPowerMonitors(@ullable Executor executor, @NonNull Consumer<List<PowerMonitor>> onResult)509 public void getSupportedPowerMonitors(@Nullable Executor executor, 510 @NonNull Consumer<List<PowerMonitor>> onResult) { 511 final List<PowerMonitor> result; 512 synchronized (mPowerMonitorsLock) { 513 if (mPowerMonitorsInfo != null) { 514 result = mPowerMonitorsInfo; 515 } else if (mPowerStats == null) { 516 mPowerMonitorsInfo = List.of(); 517 result = mPowerMonitorsInfo; 518 } else { 519 result = null; 520 } 521 } 522 if (result != null) { 523 if (executor != null) { 524 executor.execute(() -> onResult.accept(result)); 525 } else { 526 onResult.accept(result); 527 } 528 return; 529 } 530 try { 531 mPowerStats.getSupportedPowerMonitors(new ResultReceiver(null) { 532 @Override 533 protected void onReceiveResult(int resultCode, Bundle resultData) { 534 PowerMonitor[] array = resultData.getParcelableArray( 535 IPowerStatsService.KEY_MONITORS, PowerMonitor.class); 536 List<PowerMonitor> result = array != null ? Arrays.asList(array) : List.of(); 537 synchronized (mPowerMonitorsLock) { 538 mPowerMonitorsInfo = result; 539 } 540 if (executor != null) { 541 executor.execute(() -> onResult.accept(result)); 542 } else { 543 onResult.accept(result); 544 } 545 } 546 }); 547 } catch (RemoteException e) { 548 throw e.rethrowFromSystemServer(); 549 } 550 } 551 552 private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR = 553 Comparator.comparingInt(pm -> pm.index); 554 555 /** 556 * Asynchronously retrieves the accumulated power consumption reported by the specified power 557 * monitors. 558 * 559 * @param powerMonitors power monitors to be retrieved. 560 * @param executor optional Executor to deliver the callbacks. If not supplied, the 561 * callback may be invoked on an arbitrary thread. 562 * @param onResult callback for the result 563 */ 564 @FlaggedApi("com.android.server.power.optimization.power_monitor_api") getPowerMonitorReadings(@onNull List<PowerMonitor> powerMonitors, @Nullable Executor executor, @NonNull OutcomeReceiver<PowerMonitorReadings, RuntimeException> onResult)565 public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors, 566 @Nullable Executor executor, 567 @NonNull OutcomeReceiver<PowerMonitorReadings, RuntimeException> onResult) { 568 if (mPowerStats == null) { 569 IllegalArgumentException error = 570 new IllegalArgumentException("Unsupported power monitor"); 571 if (executor != null) { 572 executor.execute(() -> onResult.onError(error)); 573 } else { 574 onResult.onError(error); 575 } 576 return; 577 } 578 579 PowerMonitor[] powerMonitorsArray = 580 powerMonitors.toArray(new PowerMonitor[powerMonitors.size()]); 581 Arrays.sort(powerMonitorsArray, POWER_MONITOR_COMPARATOR); 582 int[] indices = new int[powerMonitors.size()]; 583 for (int i = 0; i < powerMonitors.size(); i++) { 584 indices[i] = powerMonitorsArray[i].index; 585 } 586 try { 587 mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(null) { 588 @Override 589 protected void onReceiveResult(int resultCode, Bundle resultData) { 590 if (resultCode == IPowerStatsService.RESULT_SUCCESS) { 591 PowerMonitorReadings result = new PowerMonitorReadings(powerMonitorsArray, 592 resultData.getLongArray(IPowerStatsService.KEY_ENERGY), 593 resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS), 594 resultData.getInt(IPowerStatsService.KEY_GRANULARITY)); 595 if (executor != null) { 596 executor.execute(() -> onResult.onResult(result)); 597 } else { 598 onResult.onResult(result); 599 } 600 } else { 601 RuntimeException error; 602 if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) { 603 error = new IllegalArgumentException("Unsupported power monitor"); 604 } else { 605 error = new IllegalStateException( 606 "Unrecognized result code " + resultCode); 607 } 608 if (executor != null) { 609 executor.execute(() -> onResult.onError(error)); 610 } else { 611 onResult.onError(error); 612 } 613 } 614 } 615 }); 616 } catch (RemoteException e) { 617 throw e.rethrowFromSystemServer(); 618 } 619 } 620 } 621