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.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemService;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.Context;
25 import android.os.BatteryStats;
26 import android.os.Build;
27 import android.os.Bundle;
28 import android.os.IPowerStatsService;
29 import android.os.OutcomeReceiver;
30 import android.os.PowerMonitor;
31 import android.os.PowerMonitorReadings;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.ResultReceiver;
35 import android.os.ServiceManager;
36 import android.os.SynchronousResultReceiver;
37 
38 import com.android.internal.app.IBatteryStats;
39 import com.android.server.power.optimization.Flags;
40 
41 import java.util.Arrays;
42 import java.util.Comparator;
43 import java.util.List;
44 import java.util.concurrent.Executor;
45 import java.util.concurrent.TimeoutException;
46 import java.util.function.Consumer;
47 
48 /**
49  * Provides access to data about how various system resources are used by applications.
50  * @more
51  * <p>
52  * If you are going to be using this class to log your application's resource usage,
53  * please consider the amount of resources (battery, network, etc) that will be used
54  * by the logging itself.  It can be substantial.
55  * <p>
56  * <b>Battery Usage</b><br>
57  * Since Android version {@link android.os.Build.VERSION_CODES#Q}, the statistics related to power
58  * (battery) usage are recorded since the device was last considered fully charged (for previous
59  * versions, it is instead since the device was last unplugged).
60  * It is expected that applications schedule more work to do while the device is
61  * plugged in (e.g. using {@link android.app.job.JobScheduler JobScheduler}), and
62  * while that can affect charging rates, it is still preferable to actually draining
63  * the battery.
64  */
65 @SystemService(Context.SYSTEM_HEALTH_SERVICE)
66 public class SystemHealthManager {
67     @NonNull
68     private final IBatteryStats mBatteryStats;
69     @Nullable
70     private final IPowerStatsService mPowerStats;
71     private List<PowerMonitor> mPowerMonitorsInfo;
72     private final Object mPowerMonitorsLock = new Object();
73     private static final long TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS = 10_000;
74 
75     private static class PendingUidSnapshots {
76         public int[] uids;
77         public SynchronousResultReceiver resultReceiver;
78     }
79 
80     private final PendingUidSnapshots mPendingUidSnapshots = new PendingUidSnapshots();
81 
82     /**
83      * Construct a new SystemHealthManager object.
84      *
85      * @hide
86      */
87     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
SystemHealthManager()88     public SystemHealthManager() {
89         this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)),
90                 IPowerStatsService.Stub.asInterface(
91                         ServiceManager.getService(Context.POWER_STATS_SERVICE)));
92     }
93 
94     /** {@hide} */
SystemHealthManager(@onNull IBatteryStats batteryStats, @Nullable IPowerStatsService powerStats)95     public SystemHealthManager(@NonNull IBatteryStats batteryStats,
96             @Nullable IPowerStatsService powerStats) {
97         mBatteryStats = batteryStats;
98         mPowerStats = powerStats;
99     }
100 
101     /**
102      * Obtain a SystemHealthManager object for the supplied context.
103      *
104      * @hide
105      */
106     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
from(Context context)107     public static SystemHealthManager from(Context context) {
108         return (SystemHealthManager) context.getSystemService(Context.SYSTEM_HEALTH_SERVICE);
109     }
110 
111     /**
112      * Return a {@link HealthStats} object containing a snapshot of system health
113      * metrics for the given uid (user-id, which in usually corresponds to application).
114      *
115      * @param uid User ID for a given application.
116      * @return A {@link HealthStats} object containing the metrics for the requested
117      * application. The keys for this HealthStats object will be from the {@link UidHealthStats}
118      * class.
119      * @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS
120      * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
121      * other than its own.
122      * @see Process#myUid() Process.myUid()
123      */
takeUidSnapshot(int uid)124     public HealthStats takeUidSnapshot(int uid) {
125         if (!Flags.onewayBatteryStatsService()) {
126             try {
127                 final HealthStatsParceler parceler = mBatteryStats.takeUidSnapshot(uid);
128                 return parceler.getHealthStats();
129             } catch (RemoteException ex) {
130                 throw ex.rethrowFromSystemServer();
131             }
132         }
133         final HealthStats[] result = takeUidSnapshots(new int[]{uid});
134         if (result != null && result.length >= 1) {
135             return result[0];
136         }
137         return null;
138     }
139 
140     /**
141      * Return a {@link HealthStats} object containing a snapshot of system health
142      * metrics for the application calling this API. This method is the same as calling
143      * {@code takeUidSnapshot(Process.myUid())}.
144      *
145      * @return A {@link HealthStats} object containing the metrics for this application. The keys
146      * for this HealthStats object will be from the {@link UidHealthStats} class.
147      */
takeMyUidSnapshot()148     public HealthStats takeMyUidSnapshot() {
149         return takeUidSnapshot(Process.myUid());
150     }
151 
152     /**
153      * Return a {@link HealthStats} object containing a snapshot of system health
154      * metrics for the given uids (user-id, which in usually corresponds to application).
155      *
156      * @param uids An array of User IDs to retrieve.
157      * @return An array of {@link HealthStats} objects containing the metrics for each of
158      * the requested uids. The keys for this HealthStats object will be from the
159      * {@link UidHealthStats} class.
160      * @more An application must hold the {@link android.Manifest.permission#BATTERY_STATS
161      * android.permission.BATTERY_STATS} permission in order to retrieve any HealthStats
162      * other than its own.
163      */
takeUidSnapshots(int[] uids)164     public HealthStats[] takeUidSnapshots(int[] uids) {
165         if (!Flags.onewayBatteryStatsService()) {
166             try {
167                 final HealthStatsParceler[] parcelers = mBatteryStats.takeUidSnapshots(uids);
168                 final int count = uids.length;
169                 final HealthStats[] results = new HealthStats[count];
170                 for (int i = 0; i < count; i++) {
171                     results[i] = parcelers[i].getHealthStats();
172                 }
173                 return results;
174             } catch (RemoteException ex) {
175                 throw ex.rethrowFromSystemServer();
176             }
177         }
178 
179         SynchronousResultReceiver resultReceiver;
180         synchronized (mPendingUidSnapshots) {
181             if (Arrays.equals(mPendingUidSnapshots.uids, uids)) {
182                 resultReceiver = mPendingUidSnapshots.resultReceiver;
183             } else {
184                 mPendingUidSnapshots.uids = Arrays.copyOf(uids, uids.length);
185                 mPendingUidSnapshots.resultReceiver = resultReceiver =
186                         new SynchronousResultReceiver("takeUidSnapshots");
187                 try {
188                     mBatteryStats.takeUidSnapshotsAsync(uids, resultReceiver);
189                 } catch (RemoteException ex) {
190                     throw ex.rethrowFromSystemServer();
191                 }
192             }
193         }
194 
195         SynchronousResultReceiver.Result result;
196         try {
197             result = resultReceiver.awaitResult(TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS);
198         } catch (TimeoutException e) {
199             throw new RuntimeException(e);
200         } finally {
201             synchronized (mPendingUidSnapshots) {
202                 if (mPendingUidSnapshots.resultReceiver == resultReceiver) {
203                     mPendingUidSnapshots.uids = null;
204                     mPendingUidSnapshots.resultReceiver = null;
205                 }
206             }
207         }
208 
209         final HealthStats[] results = new HealthStats[uids.length];
210         if (result.bundle != null) {
211             HealthStatsParceler[] parcelers = result.bundle.getParcelableArray(
212                     IBatteryStats.KEY_UID_SNAPSHOTS, HealthStatsParceler.class);
213             if (parcelers != null && parcelers.length == uids.length) {
214                 for (int i = 0; i < parcelers.length; i++) {
215                     results[i] = parcelers[i].getHealthStats();
216                 }
217             }
218         }
219         return results;
220     }
221 
222     /**
223      * Asynchronously retrieves a list of supported  {@link PowerMonitor}'s, which include raw ODPM
224      * (on-device power rail monitor) rails and modeled energy consumers.  If ODPM is unsupported
225      * on this device this method delivers an empty list.
226      *
227      * @param executor optional Handler to deliver the callback. If not supplied, the callback
228      *                 may be invoked on an arbitrary thread.
229      * @param onResult callback for the result
230      */
231     @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
getSupportedPowerMonitors(@ullable Executor executor, @NonNull Consumer<List<PowerMonitor>> onResult)232     public void getSupportedPowerMonitors(@Nullable Executor executor,
233             @NonNull Consumer<List<PowerMonitor>> onResult) {
234         final List<PowerMonitor> result;
235         synchronized (mPowerMonitorsLock) {
236             if (mPowerMonitorsInfo != null) {
237                 result = mPowerMonitorsInfo;
238             } else if (mPowerStats == null) {
239                 mPowerMonitorsInfo = List.of();
240                 result = mPowerMonitorsInfo;
241             } else {
242                 result = null;
243             }
244         }
245         if (result != null) {
246             if (executor != null) {
247                 executor.execute(() -> onResult.accept(result));
248             } else {
249                 onResult.accept(result);
250             }
251             return;
252         }
253         try {
254             mPowerStats.getSupportedPowerMonitors(new ResultReceiver(null) {
255                 @Override
256                 protected void onReceiveResult(int resultCode, Bundle resultData) {
257                     PowerMonitor[] array = resultData.getParcelableArray(
258                             IPowerStatsService.KEY_MONITORS, PowerMonitor.class);
259                     List<PowerMonitor> result = array != null ? Arrays.asList(array) : List.of();
260                     synchronized (mPowerMonitorsLock) {
261                         mPowerMonitorsInfo = result;
262                     }
263                     if (executor != null) {
264                         executor.execute(()-> onResult.accept(result));
265                     } else {
266                         onResult.accept(result);
267                     }
268                 }
269             });
270         } catch (RemoteException e) {
271             throw e.rethrowFromSystemServer();
272         }
273     }
274 
275     private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
276             Comparator.comparingInt(pm -> pm.index);
277 
278     /**
279      * Asynchronously retrieves the accumulated power consumption reported by the specified power
280      * monitors.
281      *
282      * @param powerMonitors power monitors to be retrieved.
283      * @param executor      optional Executor to deliver the callbacks. If not supplied, the
284      *                      callback may be invoked on an arbitrary thread.
285      * @param onResult      callback for the result
286      */
287     @FlaggedApi("com.android.server.power.optimization.power_monitor_api")
getPowerMonitorReadings(@onNull List<PowerMonitor> powerMonitors, @Nullable Executor executor, @NonNull OutcomeReceiver<PowerMonitorReadings, RuntimeException> onResult)288     public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,
289             @Nullable Executor executor,
290             @NonNull OutcomeReceiver<PowerMonitorReadings, RuntimeException> onResult) {
291         if (mPowerStats == null) {
292             IllegalArgumentException error =
293                     new IllegalArgumentException("Unsupported power monitor");
294             if (executor != null) {
295                 executor.execute(() -> onResult.onError(error));
296             } else {
297                 onResult.onError(error);
298             }
299             return;
300         }
301 
302         PowerMonitor[] powerMonitorsArray =
303                 powerMonitors.toArray(new PowerMonitor[powerMonitors.size()]);
304         Arrays.sort(powerMonitorsArray, POWER_MONITOR_COMPARATOR);
305         int[] indices = new int[powerMonitors.size()];
306         for (int i = 0; i < powerMonitors.size(); i++) {
307             indices[i] = powerMonitorsArray[i].index;
308         }
309         try {
310             mPowerStats.getPowerMonitorReadings(indices, new ResultReceiver(null) {
311                 @Override
312                 protected void onReceiveResult(int resultCode, Bundle resultData) {
313                     if (resultCode == IPowerStatsService.RESULT_SUCCESS) {
314                         PowerMonitorReadings result = new PowerMonitorReadings(powerMonitorsArray,
315                                 resultData.getLongArray(IPowerStatsService.KEY_ENERGY),
316                                 resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS));
317                         if (executor != null) {
318                             executor.execute(() -> onResult.onResult(result));
319                         } else {
320                             onResult.onResult(result);
321                         }
322                     } else {
323                         RuntimeException error;
324                         if (resultCode == IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR) {
325                             error = new IllegalArgumentException("Unsupported power monitor");
326                         } else {
327                             error = new IllegalStateException(
328                                     "Unrecognized result code " + resultCode);
329                         }
330                         if (executor != null) {
331                             executor.execute(() -> onResult.onError(error));
332                         } else {
333                             onResult.onError(error);
334                         }
335                     }
336                 }
337             });
338         } catch (RemoteException e) {
339             throw e.rethrowFromSystemServer();
340         }
341     }
342 }
343