• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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