1 /*
2  * Copyright (C) 2020 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 com.android.internal.os;
18 
19 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
20 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
21 
22 import android.os.BatteryConsumer;
23 import android.os.BatteryStats;
24 import android.os.BatteryUsageStats;
25 import android.os.BatteryUsageStatsQuery;
26 import android.os.UidBatteryConsumer;
27 import android.os.UserHandle;
28 import android.text.format.DateUtils;
29 import android.util.Slog;
30 import android.util.SparseArray;
31 import android.util.SparseLongArray;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.List;
36 
37 /**
38  * Estimates power consumed by the screen(s)
39  */
40 public class ScreenPowerCalculator extends PowerCalculator {
41     private static final String TAG = "ScreenPowerCalculator";
42     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
43 
44     // Minimum amount of time the screen should be on to start smearing drain to apps
45     public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS;
46 
47     private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators;
48     private final UsageBasedPowerEstimator[] mScreenFullPowerEstimators;
49 
50     private static class PowerAndDuration {
51         public long durationMs;
52         public double powerMah;
53     }
54 
ScreenPowerCalculator(PowerProfile powerProfile)55     public ScreenPowerCalculator(PowerProfile powerProfile) {
56         final int numDisplays = powerProfile.getNumDisplays();
57         mScreenOnPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
58         mScreenFullPowerEstimators = new UsageBasedPowerEstimator[numDisplays];
59         for (int display = 0; display < numDisplays; display++) {
60             mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator(
61                     powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display));
62             mScreenFullPowerEstimators[display] = new UsageBasedPowerEstimator(
63                     powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL,
64                             display));
65         }
66     }
67 
68     @Override
calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query)69     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
70             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
71         final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
72 
73         final long consumptionUC = batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
74         final int powerModel = getPowerModel(consumptionUC, query);
75         calculateTotalDurationAndPower(totalPowerAndDuration, powerModel, batteryStats,
76                 rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, consumptionUC);
77 
78         double totalAppPower = 0;
79         long totalAppDuration = 0;
80 
81         // Now deal with each app's UidBatteryConsumer. The results are stored in the
82         // BatteryConsumer.POWER_COMPONENT_SCREEN power component, which is considered smeared,
83         // but the method depends on the data source.
84         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
85                 builder.getUidBatteryConsumerBuilders();
86         switch (powerModel) {
87             case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
88                 final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
89                 for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
90                     final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
91                     calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.getBatteryStatsUid(),
92                             rawRealtimeUs);
93                     app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
94                                     appPowerAndDuration.durationMs)
95                             .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
96                                     appPowerAndDuration.powerMah, powerModel);
97                     totalAppPower += appPowerAndDuration.powerMah;
98                     totalAppDuration += appPowerAndDuration.durationMs;
99                 }
100                 break;
101             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
102             default:
103                 smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
104                         rawRealtimeUs);
105                 totalAppPower = totalPowerAndDuration.powerMah;
106                 totalAppDuration = totalPowerAndDuration.durationMs;
107         }
108 
109         builder.getAggregateBatteryConsumerBuilder(
110                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
111                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
112                         Math.max(totalPowerAndDuration.powerMah, totalAppPower), powerModel)
113                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
114                         totalPowerAndDuration.durationMs);
115 
116         builder.getAggregateBatteryConsumerBuilder(
117                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
118                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppPower, powerModel)
119                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppDuration);
120     }
121 
122     /**
123      * Screen power is the additional power the screen takes while the device is running.
124      */
125     @Override
calculate(List<BatterySipper> sippers, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers)126     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
127             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
128         final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
129         final long consumptionUC = batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
130         final int powerModel = getPowerModel(consumptionUC);
131         calculateTotalDurationAndPower(totalPowerAndDuration, powerModel, batteryStats,
132                 rawRealtimeUs, statsType, consumptionUC);
133         if (totalPowerAndDuration.powerMah == 0) {
134             return;
135         }
136 
137         // First deal with the SCREEN BatterySipper (since we need this for smearing over apps).
138         final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
139         bs.usagePowerMah = totalPowerAndDuration.powerMah;
140         bs.usageTimeMs = totalPowerAndDuration.durationMs;
141         bs.sumPower();
142         sippers.add(bs);
143 
144         // Now deal with each app's BatterySipper. The results are stored in the screenPowerMah
145         // field, which is considered smeared, but the method depends on the data source.
146         switch (powerModel) {
147             case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
148                 final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
149                 for (int i = sippers.size() - 1; i >= 0; i--) {
150                     final BatterySipper app = sippers.get(i);
151                     if (app.drainType == BatterySipper.DrainType.APP) {
152                         calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.uidObj,
153                                 rawRealtimeUs);
154                         app.screenPowerMah = appPowerAndDuration.powerMah;
155                     }
156                 }
157                 break;
158             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
159             default:
160                 smearScreenBatterySipper(sippers, bs, rawRealtimeUs);
161         }
162     }
163 
164     /**
165      * Stores duration and power information in totalPowerAndDuration.
166      */
calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration, @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats, long rawRealtimeUs, int statsType, long consumptionUC)167     private void calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration,
168             @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
169             long rawRealtimeUs, int statsType, long consumptionUC) {
170         totalPowerAndDuration.durationMs = calculateDuration(batteryStats, rawRealtimeUs,
171                 statsType);
172 
173         switch (powerModel) {
174             case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
175                 totalPowerAndDuration.powerMah = uCtoMah(consumptionUC);
176                 break;
177             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
178             default:
179                 totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
180                         rawRealtimeUs);
181         }
182     }
183 
calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration, BatteryStats.Uid u, long rawRealtimeUs)184     private void calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration,
185             BatteryStats.Uid u, long rawRealtimeUs) {
186         appPowerAndDuration.durationMs = getProcessForegroundTimeMs(u, rawRealtimeUs);
187 
188         final long chargeUC = u.getScreenOnMeasuredBatteryConsumptionUC();
189         if (chargeUC < 0) {
190             Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
191             appPowerAndDuration.powerMah = 0;
192             return;
193         }
194 
195         appPowerAndDuration.powerMah = uCtoMah(chargeUC);
196     }
197 
calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType)198     private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
199         return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
200     }
201 
calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs)202     private double calculateTotalPowerFromBrightness(BatteryStats batteryStats,
203             long rawRealtimeUs) {
204         final int numDisplays = mScreenOnPowerEstimators.length;
205         double power = 0;
206         for (int display = 0; display < numDisplays; display++) {
207             final long displayTime = batteryStats.getDisplayScreenOnTime(display, rawRealtimeUs)
208                     / 1000;
209             power += mScreenOnPowerEstimators[display].calculatePower(displayTime);
210             for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
211                 final long brightnessTime = batteryStats.getDisplayScreenBrightnessTime(display,
212                         bin, rawRealtimeUs) / 1000;
213                 final double binPowerMah = mScreenFullPowerEstimators[display].calculatePower(
214                         brightnessTime) * (bin + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
215                 if (DEBUG && binPowerMah != 0) {
216                     Slog.d(TAG, "Screen bin #" + bin + ": time=" + brightnessTime
217                             + " power=" + formatCharge(binPowerMah));
218                 }
219                 power += binPowerMah;
220             }
221         }
222         return power;
223     }
224 
225     /**
226      * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
227      * time, and store this in the {@link BatterySipper#screenPowerMah} field.
228      */
229     @VisibleForTesting
smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper, long rawRealtimeUs)230     public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper,
231             long rawRealtimeUs) {
232         long totalActivityTimeMs = 0;
233         final SparseLongArray activityTimeArray = new SparseLongArray();
234         for (int i = sippers.size() - 1; i >= 0; i--) {
235             final BatteryStats.Uid uid = sippers.get(i).uidObj;
236             if (uid != null) {
237                 final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
238                 activityTimeArray.put(uid.getUid(), timeMs);
239                 totalActivityTimeMs += timeMs;
240             }
241         }
242 
243         if (screenSipper != null && totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
244             final double totalScreenPowerMah = screenSipper.totalPowerMah;
245             for (int i = sippers.size() - 1; i >= 0; i--) {
246                 final BatterySipper sipper = sippers.get(i);
247                 sipper.screenPowerMah = totalScreenPowerMah
248                         * activityTimeArray.get(sipper.getUid(), 0)
249                         / totalActivityTimeMs;
250             }
251         }
252     }
253 
254     /**
255      * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
256      * time, and store this in the {@link BatterySipper#screenPowerMah} field.
257      */
smearScreenBatteryDrain( SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders, PowerAndDuration totalPowerAndDuration, long rawRealtimeUs)258     private void smearScreenBatteryDrain(
259             SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders,
260             PowerAndDuration totalPowerAndDuration, long rawRealtimeUs) {
261         long totalActivityTimeMs = 0;
262         final SparseLongArray activityTimeArray = new SparseLongArray();
263         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
264             final BatteryStats.Uid uid = uidBatteryConsumerBuilders.valueAt(i).getBatteryStatsUid();
265             final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
266             activityTimeArray.put(uid.getUid(), timeMs);
267             totalActivityTimeMs += timeMs;
268         }
269 
270         if (totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
271             final double totalScreenPowerMah = totalPowerAndDuration.powerMah;
272             for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
273                 final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
274                 final long durationMs = activityTimeArray.get(app.getUid(), 0);
275                 final double powerMah = totalScreenPowerMah * durationMs / totalActivityTimeMs;
276                 app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, durationMs)
277                         .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah,
278                                 BatteryConsumer.POWER_MODEL_POWER_PROFILE);
279             }
280         }
281     }
282 
283     /** Get the minimum of the uid's ForegroundActivity time and its TOP time. */
284     @VisibleForTesting
getProcessForegroundTimeMs(BatteryStats.Uid uid, long rawRealTimeUs)285     public long getProcessForegroundTimeMs(BatteryStats.Uid uid, long rawRealTimeUs) {
286         final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP};
287 
288         long timeUs = 0;
289         for (int type : foregroundTypes) {
290             final long localTime = uid.getProcessStateTime(type, rawRealTimeUs,
291                     BatteryStats.STATS_SINCE_CHARGED);
292             timeUs += localTime;
293         }
294 
295         // Return the min value of STATE_TOP time and foreground activity time, since both of these
296         // time have some errors.
297         return Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)) / 1000;
298     }
299 
300     /** Get the ForegroundActivity time of the given uid. */
301     @VisibleForTesting
getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs)302     public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
303         final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
304         if (timer == null) {
305             return 0;
306         }
307         return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
308     }
309 }
310