1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.internal.os;
17 
18 import android.os.BatteryConsumer;
19 import android.os.BatteryStats;
20 import android.os.BatteryUsageStats;
21 import android.os.BatteryUsageStatsQuery;
22 import android.os.UidBatteryConsumer;
23 import android.os.UserHandle;
24 import android.telephony.CellSignalStrength;
25 import android.util.Log;
26 import android.util.SparseArray;
27 
28 import java.util.List;
29 
30 public class MobileRadioPowerCalculator extends PowerCalculator {
31     private static final String TAG = "MobRadioPowerCalculator";
32     private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
33 
34     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
35             CellSignalStrength.getNumSignalStrengthLevels();
36 
37     private final UsageBasedPowerEstimator mActivePowerEstimator;
38     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
39             new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
40     private final UsageBasedPowerEstimator mScanPowerEstimator;
41 
42     private static class PowerAndDuration {
43         public long durationMs;
44         public double remainingPowerMah;
45         public long totalAppDurationMs;
46         public double totalAppPowerMah;
47         public long signalDurationMs;
48         public long noCoverageDurationMs;
49     }
50 
MobileRadioPowerCalculator(PowerProfile profile)51     public MobileRadioPowerCalculator(PowerProfile profile) {
52         // Power consumption when radio is active
53         double powerRadioActiveMa =
54                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
55         if (powerRadioActiveMa == -1) {
56             double sum = 0;
57             sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
58             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
59                 sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
60             }
61             powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
62         }
63 
64         mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
65 
66         // Power consumption when radio is on, but idle
67         if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
68             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
69                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
70                         profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
71             }
72         } else {
73             double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE);
74 
75             // Magical calculations preserved for historical compatibility
76             mIdlePowerEstimators[0] = new UsageBasedPowerEstimator(idle * 25 / 180);
77             for (int i = 1; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
78                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(Math.max(1, idle / 256));
79             }
80         }
81 
82         mScanPowerEstimator = new UsageBasedPowerEstimator(
83                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0));
84     }
85 
86     @Override
calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query)87     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
88             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
89 
90         PowerAndDuration total = new PowerAndDuration();
91 
92         final double powerPerPacketMah = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
93                 BatteryStats.STATS_SINCE_CHARGED);
94         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
95                 builder.getUidBatteryConsumerBuilders();
96         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
97             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
98             final BatteryStats.Uid uid = app.getBatteryStatsUid();
99             calculateApp(app, uid, powerPerPacketMah, total, query);
100         }
101 
102         final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
103         final int powerModel = getPowerModel(consumptionUC, query);
104         calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC);
105 
106         if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
107             builder.getAggregateBatteryConsumerBuilder(
108                     BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
109                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
110                             total.durationMs)
111                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
112                             total.remainingPowerMah + total.totalAppPowerMah, powerModel);
113 
114             builder.getAggregateBatteryConsumerBuilder(
115                     BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
116                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
117                             total.durationMs)
118                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
119                             total.totalAppPowerMah, powerModel);
120         }
121     }
122 
calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u, double powerPerPacketMah, PowerAndDuration total, BatteryUsageStatsQuery query)123     private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
124             double powerPerPacketMah, PowerAndDuration total,
125             BatteryUsageStatsQuery query) {
126         final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
127         total.totalAppDurationMs += radioActiveDurationMs;
128 
129         final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
130         final int powerModel = getPowerModel(consumptionUC, query);
131         final double powerMah = calculatePower(u, powerModel, powerPerPacketMah,
132                 radioActiveDurationMs, consumptionUC);
133         total.totalAppPowerMah += powerMah;
134 
135         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
136                         radioActiveDurationMs)
137                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
138                         powerModel);
139     }
140 
141     @Override
calculate(List<BatterySipper> sippers, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers)142     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
143             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
144         final double mobilePowerPerPacket = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
145                 statsType);
146         PowerAndDuration total = new PowerAndDuration();
147         for (int i = sippers.size() - 1; i >= 0; i--) {
148             final BatterySipper app = sippers.get(i);
149             if (app.drainType == BatterySipper.DrainType.APP) {
150                 final BatteryStats.Uid u = app.uidObj;
151                 calculateApp(app, u, statsType, mobilePowerPerPacket, total);
152             }
153         }
154 
155         BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
156         final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
157         final int powerModel = getPowerModel(consumptionUC);
158         calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC);
159         if (total.remainingPowerMah != 0) {
160             if (total.signalDurationMs != 0) {
161                 radio.noCoveragePercent =
162                         total.noCoverageDurationMs * 100.0 / total.signalDurationMs;
163             }
164             radio.mobileActive = total.durationMs;
165             radio.mobileActiveCount = batteryStats.getMobileRadioActiveUnknownCount(statsType);
166             radio.mobileRadioPowerMah = total.remainingPowerMah;
167             radio.sumPower();
168         }
169         if (radio.totalPowerMah > 0) {
170             sippers.add(radio);
171         }
172     }
173 
calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType, double powerPerPacketMah, PowerAndDuration total)174     private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
175             double powerPerPacketMah, PowerAndDuration total) {
176         app.mobileActive = calculateDuration(u, statsType);
177 
178         final long consumptionUC =  u.getMobileRadioMeasuredBatteryConsumptionUC();
179         final int powerModel = getPowerModel(consumptionUC);
180         app.mobileRadioPowerMah = calculatePower(u, powerModel, powerPerPacketMah, app.mobileActive,
181                 consumptionUC);
182         total.totalAppDurationMs += app.mobileActive;
183 
184         // Add cost of mobile traffic.
185         app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
186                 statsType);
187         app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
188                 statsType);
189         app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
190         app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
191                 statsType);
192         app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
193                 statsType);
194 
195         if (DEBUG && app.mobileRadioPowerMah != 0) {
196             Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
197                     + (app.mobileRxPackets + app.mobileTxPackets)
198                     + " active time " + app.mobileActive
199                     + " power=" + formatCharge(app.mobileRadioPowerMah));
200         }
201     }
202 
calculateDuration(BatteryStats.Uid u, int statsType)203     private long calculateDuration(BatteryStats.Uid u, int statsType) {
204         return u.getMobileRadioActiveTime(statsType) / 1000;
205     }
206 
calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel, double powerPerPacketMah, long radioActiveDurationMs, long measuredChargeUC)207     private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
208             double powerPerPacketMah, long radioActiveDurationMs, long measuredChargeUC) {
209         if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
210             return uCtoMah(measuredChargeUC);
211         }
212 
213         if (radioActiveDurationMs > 0) {
214             // We are tracking when the radio is up, so can use the active time to
215             // determine power use.
216             return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
217         } else {
218             // We are not tracking when the radio is up, so must approximate power use
219             // based on the number of packets.
220             final long mobileRxPackets = u.getNetworkActivityPackets(
221                     BatteryStats.NETWORK_MOBILE_RX_DATA,
222                     BatteryStats.STATS_SINCE_CHARGED);
223             final long mobileTxPackets = u.getNetworkActivityPackets(
224                     BatteryStats.NETWORK_MOBILE_TX_DATA,
225                     BatteryStats.STATS_SINCE_CHARGED);
226             return (mobileRxPackets + mobileTxPackets) * powerPerPacketMah;
227         }
228     }
229 
calculateRemaining(PowerAndDuration total, @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats, long rawRealtimeUs, long consumptionUC)230     private void calculateRemaining(PowerAndDuration total,
231             @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
232             long rawRealtimeUs, long consumptionUC) {
233         long signalTimeMs = 0;
234         double powerMah = 0;
235 
236         if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
237             powerMah = uCtoMah(consumptionUC);
238         }
239 
240         for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
241             long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
242                     BatteryStats.STATS_SINCE_CHARGED) / 1000;
243             if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
244                 final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
245                 if (DEBUG && p != 0) {
246                     Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
247                             + formatCharge(p));
248                 }
249                 powerMah += p;
250             }
251             signalTimeMs += strengthTimeMs;
252             if (i == 0) {
253                 total.noCoverageDurationMs = strengthTimeMs;
254             }
255         }
256 
257         final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
258                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
259         long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
260                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
261         long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
262 
263         if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
264             final double p = calcScanTimePowerMah(scanningTimeMs);
265             if (DEBUG && p != 0) {
266                 Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(
267                         p));
268             }
269             powerMah += p;
270 
271             if (remainingActiveTimeMs > 0) {
272                 powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
273             }
274         }
275         total.durationMs = radioActiveTimeMs;
276         total.remainingPowerMah = powerMah;
277         total.signalDurationMs = signalTimeMs;
278     }
279 
280     /**
281      * Calculates active radio power consumption (in milliamp-hours) from active radio duration.
282      */
calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs)283     public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) {
284         return mActivePowerEstimator.calculatePower(radioActiveDurationMs);
285     }
286 
287     /**
288      * Calculates idle radio power consumption (in milliamp-hours) for time spent at a cell signal
289      * strength level.
290      * see {@link CellSignalStrength#getNumSignalStrengthLevels()}
291      */
calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel)292     public double calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel) {
293         return mIdlePowerEstimators[strengthLevel].calculatePower(strengthTimeMs);
294     }
295 
296     /**
297      * Calculates radio scan power consumption (in milliamp-hours) from scan time.
298      */
calcScanTimePowerMah(long scanningTimeMs)299     public double calcScanTimePowerMah(long scanningTimeMs) {
300         return mScanPowerEstimator.calculatePower(scanningTimeMs);
301     }
302 
303     /**
304      * Return estimated power (in mAh) of sending or receiving a packet with the mobile radio.
305      */
getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType)306     private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) {
307         final long radioDataUptimeMs =
308                 stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
309         final double mobilePower = calcPowerFromRadioActiveDurationMah(radioDataUptimeMs);
310 
311         final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
312                 statsType);
313         final long mobileTx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
314                 statsType);
315         final long mobilePackets = mobileRx + mobileTx;
316 
317         return mobilePackets != 0 ? mobilePower / mobilePackets : 0;
318     }
319 }
320