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.server.power.stats;
17 
18 import android.annotation.Nullable;
19 import android.os.BatteryConsumer;
20 import android.os.BatteryStats;
21 import android.os.BatteryUsageStats;
22 import android.os.BatteryUsageStatsQuery;
23 import android.os.UidBatteryConsumer;
24 import android.telephony.CellSignalStrength;
25 import android.telephony.ServiceState;
26 import android.util.Log;
27 import android.util.LongArrayQueue;
28 import android.util.SparseArray;
29 
30 import com.android.internal.os.PowerProfile;
31 import com.android.internal.power.ModemPowerProfile;
32 
33 import java.util.ArrayList;
34 
35 public class MobileRadioPowerCalculator extends PowerCalculator {
36     private static final String TAG = "MobRadioPowerCalculator";
37     private static final boolean DEBUG = PowerCalculator.DEBUG;
38 
39     private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
40 
41     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
42             CellSignalStrength.getNumSignalStrengthLevels();
43 
44     private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
45     private static final int IGNORE = -1;
46 
47     private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
48     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
49             new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
50     private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
51 
52     @Nullable
53     private final UsageBasedPowerEstimator mSleepPowerEstimator;
54     @Nullable
55     private final UsageBasedPowerEstimator mIdlePowerEstimator;
56 
57     private final PowerProfile mPowerProfile;
58 
59     private static class PowerAndDuration {
60         public long remainingDurationMs;
61         public double remainingPowerMah;
62         public long totalAppDurationMs;
63         public double totalAppPowerMah;
64     }
65 
MobileRadioPowerCalculator(PowerProfile profile)66     public MobileRadioPowerCalculator(PowerProfile profile) {
67         mPowerProfile = profile;
68 
69         final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
70                 PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
71                 Double.NaN);
72         if (Double.isNaN(sleepDrainRateMa)) {
73             mSleepPowerEstimator = null;
74         } else {
75             mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
76         }
77 
78         final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
79                 PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
80                 Double.NaN);
81         if (Double.isNaN(idleDrainRateMa)) {
82             mIdlePowerEstimator = null;
83         } else {
84             mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
85         }
86 
87         // Instantiate legacy power estimators
88         double powerRadioActiveMa =
89                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
90         if (Double.isNaN(powerRadioActiveMa)) {
91             double sum = 0;
92             sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
93             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
94                 sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
95             }
96             powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
97         }
98         mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
99 
100         if (!Double.isNaN(
101                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
102             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
103                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
104                         profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
105             }
106         } else {
107             double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE);
108 
109             // Magical calculations preserved for historical compatibility
110             mIdlePowerEstimators[0] = new UsageBasedPowerEstimator(idle * 25 / 180);
111             for (int i = 1; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
112                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(Math.max(1, idle / 256));
113             }
114         }
115 
116         mScanPowerEstimator = new UsageBasedPowerEstimator(
117                 profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0));
118     }
119 
120     @Override
isPowerComponentSupported(@atteryConsumer.PowerComponent int powerComponent)121     public boolean isPowerComponentSupported(@BatteryConsumer.PowerComponent int powerComponent) {
122         return powerComponent == BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO;
123     }
124 
125     @Override
calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query)126     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
127             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
128 
129         PowerAndDuration total = new PowerAndDuration();
130 
131         final long totalConsumptionUC = batteryStats.getMobileRadioEnergyConsumptionUC();
132         final int powerModel = getPowerModel(totalConsumptionUC, query);
133 
134         final double totalActivePowerMah;
135         final ArrayList<UidBatteryConsumer.Builder> apps;
136         final LongArrayQueue appDurationsMs;
137         if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
138             // EnergyConsumer is available, don't bother calculating power.
139             totalActivePowerMah = Double.NaN;
140             apps = null;
141             appDurationsMs = null;
142         } else {
143             totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
144             apps = new ArrayList<>();
145             appDurationsMs = new LongArrayQueue();
146         }
147 
148         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
149                 builder.getUidBatteryConsumerBuilders();
150         BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
151 
152         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
153             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
154             final BatteryStats.Uid uid = app.getBatteryStatsUid();
155             if (keys == UNINITIALIZED_KEYS) {
156                 if (query.isProcessStateDataNeeded()) {
157                     keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
158                 } else {
159                     keys = null;
160                 }
161             }
162 
163             // Sum and populate each app's active radio duration.
164             final long radioActiveDurationMs = calculateDuration(uid,
165                     BatteryStats.STATS_SINCE_CHARGED);
166             if (!app.isVirtualUid()) {
167                 total.totalAppDurationMs += radioActiveDurationMs;
168             }
169             app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
170                     radioActiveDurationMs);
171 
172             if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
173                 // EnergyConsumer is available, populate the consumed power now.
174                 final long appConsumptionUC = uid.getMobileRadioEnergyConsumptionUC();
175                 if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
176                     final double appConsumptionMah = uCtoMah(appConsumptionUC);
177                     if (!app.isVirtualUid()) {
178                         total.totalAppPowerMah += appConsumptionMah;
179                     }
180                     app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
181                             appConsumptionMah, powerModel);
182 
183                     if (query.isProcessStateDataNeeded() && keys != null) {
184                         for (BatteryConsumer.Key key : keys) {
185                             final int processState = key.processState;
186                             if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
187                                 // Already populated with the total across all process states
188                                 continue;
189                             }
190                             final long consumptionInStateUc =
191                                     uid.getMobileRadioEnergyConsumptionUC(processState);
192                             final double powerInStateMah = uCtoMah(consumptionInStateUc);
193                             app.setConsumedPower(key, powerInStateMah, powerModel);
194                         }
195                     }
196                 }
197             } else {
198                 // Cache the app and its active duration for later calculations.
199                 apps.add(app);
200                 appDurationsMs.addLast(radioActiveDurationMs);
201             }
202         }
203 
204         long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
205                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
206         if (totalActiveDurationMs < total.totalAppDurationMs) {
207             totalActiveDurationMs = total.totalAppDurationMs;
208         }
209 
210         if (powerModel != BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
211             // Need to smear the calculated total active power across the apps based on app
212             // active durations.
213             final int appSize = apps.size();
214             for (int i = 0; i < appSize; i++) {
215                 final UidBatteryConsumer.Builder app = apps.get(i);
216                 final long activeDurationMs = appDurationsMs.get(i);
217 
218                 // Proportionally attribute radio power consumption based on active duration.
219                 final double appConsumptionMah;
220                 if (totalActiveDurationMs == 0.0) {
221                     appConsumptionMah = 0.0;
222                 } else {
223                     appConsumptionMah =
224                             (totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
225                 }
226 
227                 if (!app.isVirtualUid()) {
228                     total.totalAppPowerMah += appConsumptionMah;
229                 }
230                 app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
231                         appConsumptionMah, powerModel);
232 
233                 if (query.isProcessStateDataNeeded() && keys != null) {
234                     final BatteryStats.Uid uid = app.getBatteryStatsUid();
235                     for (BatteryConsumer.Key key : keys) {
236                         final int processState = key.processState;
237                         if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
238                             // Already populated with the total across all process states
239                             continue;
240                         }
241 
242                         final long durationInStateMs =
243                                 uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
244                         // Proportionally attribute per process state radio power consumption
245                         // based on time state duration.
246                         final double powerInStateMah;
247                         if (activeDurationMs == 0.0) {
248                             powerInStateMah = 0.0;
249                         } else {
250                             powerInStateMah =
251                                     (appConsumptionMah * durationInStateMs) / activeDurationMs;
252                         }
253                         app.setConsumedPower(key, powerInStateMah, powerModel);
254                     }
255                 }
256             }
257         }
258 
259         total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
260 
261         // Calculate remaining power consumption.
262         if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
263             total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
264             if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
265         } else {
266             // Smear unattributed active time and add it to the remaining power consumption.
267             if (totalActiveDurationMs != 0) {
268                 total.remainingPowerMah +=
269                         (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
270             }
271 
272             // Calculate the inactive modem power consumption.
273             final BatteryStats.ControllerActivityCounter modemActivity =
274                     batteryStats.getModemControllerActivity();
275             double inactivePowerMah = Double.NaN;
276             if (modemActivity != null) {
277                 final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
278                         BatteryStats.STATS_SINCE_CHARGED);
279                 final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
280                         BatteryStats.STATS_SINCE_CHARGED);
281                 inactivePowerMah = calcInactiveStatePowerMah(sleepDurationMs, idleDurationMs);
282             }
283             if (Double.isNaN(inactivePowerMah)) {
284                 // Modem activity counters unavailable. Use legacy calculations for inactive usage.
285                 final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
286                         BatteryStats.STATS_SINCE_CHARGED) / 1000;
287                 inactivePowerMah = calcScanTimePowerMah(scanningTimeMs);
288                 for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
289                     long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
290                             BatteryStats.STATS_SINCE_CHARGED) / 1000;
291                     inactivePowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
292                 }
293             }
294             if (!Double.isNaN(inactivePowerMah)) {
295                 total.remainingPowerMah += inactivePowerMah;
296             }
297 
298         }
299 
300         if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
301             builder.getAggregateBatteryConsumerBuilder(
302                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
303                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
304                             total.remainingDurationMs + total.totalAppDurationMs)
305                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
306                             total.remainingPowerMah + total.totalAppPowerMah, powerModel);
307 
308             builder.getAggregateBatteryConsumerBuilder(
309                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
310                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
311                             total.totalAppDurationMs)
312                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
313                             total.totalAppPowerMah, powerModel);
314         }
315     }
316 
calculateDuration(BatteryStats.Uid u, int statsType)317     private long calculateDuration(BatteryStats.Uid u, int statsType) {
318         return u.getMobileRadioActiveTime(statsType) / 1000;
319     }
320 
calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs)321     private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
322         final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
323         final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
324         double consumptionMah = 0.0;
325 
326         if (DEBUG) {
327             Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
328                     + elapsedRealtimeMs + " ms");
329         }
330 
331         boolean hasConstants = false;
332 
333         for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
334             final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
335                     ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
336             for (int freq = 0; freq < freqCount; freq++) {
337                 for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
338                     final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
339                             elapsedRealtimeMs);
340                     if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
341                         continue;
342                     }
343                     final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
344                             txDurationMs);
345                     if (Double.isNaN(txConsumptionMah)) {
346                         continue;
347                     }
348                     hasConstants = true;
349                     consumptionMah += txConsumptionMah;
350                 }
351 
352                 final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
353                         elapsedRealtimeMs);
354                 if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
355                     continue;
356                 }
357                 final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
358                 if (Double.isNaN(rxConsumptionMah)) {
359                     continue;
360                 }
361                 hasConstants = true;
362                 consumptionMah += rxConsumptionMah;
363             }
364         }
365 
366         if (!hasConstants) {
367             final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
368                     BatteryStats.STATS_SINCE_CHARGED) / 1000;
369             if (DEBUG) {
370                 Log.d(TAG,
371                         "Failed to calculate radio power consumption. Reattempted with legacy "
372                                 + "method. Radio active duration : "
373                                 + radioActiveDurationMs + " ms");
374             }
375             if (radioActiveDurationMs > 0) {
376                 consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
377             } else {
378                 consumptionMah = 0.0;
379             }
380         }
381 
382         if (DEBUG) {
383             Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
384                     + " mAH.");
385         }
386 
387         return consumptionMah;
388     }
389 
buildModemPowerProfileKey(@odemPowerProfile.ModemDrainType int drainType, @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, int txLevel)390     private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
391             @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
392             int txLevel) {
393         long key = PowerProfile.SUBSYSTEM_MODEM;
394 
395         // Attach Modem drain type to the key if specified.
396         if (drainType != IGNORE) {
397             key |= drainType;
398         }
399 
400         // Attach RadioAccessTechnology to the key if specified.
401         switch (rat) {
402             case IGNORE:
403                 // do nothing
404                 break;
405             case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
406                 key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
407                 break;
408             case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
409                 key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
410                 break;
411             case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
412                 key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
413                 break;
414             default:
415                 Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
416         }
417 
418         // Attach NR Frequency Range to the key if specified.
419         switch (freqRange) {
420             case IGNORE:
421                 // do nothing
422                 break;
423             case ServiceState.FREQUENCY_RANGE_UNKNOWN:
424                 key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
425                 break;
426             case ServiceState.FREQUENCY_RANGE_LOW:
427                 key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
428                 break;
429             case ServiceState.FREQUENCY_RANGE_MID:
430                 key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
431                 break;
432             case ServiceState.FREQUENCY_RANGE_HIGH:
433                 key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
434                 break;
435             case ServiceState.FREQUENCY_RANGE_MMWAVE:
436                 key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
437                 break;
438             default:
439                 Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
440         }
441 
442         // Attach transmission level to the key if specified.
443         switch (txLevel) {
444             case IGNORE:
445                 // do nothing
446                 break;
447             case 0:
448                 key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
449                 break;
450             case 1:
451                 key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
452                 break;
453             case 2:
454                 key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
455                 break;
456             case 3:
457                 key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
458                 break;
459             case 4:
460                 key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
461                 break;
462             default:
463                 Log.w(TAG, "Unexpected transmission level : " + txLevel);
464         }
465         return key;
466     }
467 
468     /**
469      * Calculates active receive radio power consumption (in milliamp-hours) from the given state's
470      * duration.
471      */
calcRxStatePowerMah(@atteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, long rxDurationMs)472     public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
473             @ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
474         final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
475                 freqRange, IGNORE);
476         final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
477                 Double.NaN);
478         if (Double.isNaN(drainRateMa)) {
479             Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
480             return Double.NaN;
481         }
482 
483         final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
484         if (DEBUG) {
485             Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
486                     + drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
487                     + ModemPowerProfile.keyToString((int) rxKey));
488         }
489         return consumptionMah;
490     }
491 
492     /**
493      * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
494      * duration.
495      */
calcTxStatePowerMah(@atteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs)496     public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
497             @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
498         final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
499                 freqRange, txLevel);
500         final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
501                 Double.NaN);
502         if (Double.isNaN(drainRateMa)) {
503             Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
504             return Double.NaN;
505         }
506 
507         final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
508         if (DEBUG) {
509             Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
510                     + drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
511                     + ModemPowerProfile.keyToString((int) txKey));
512         }
513         return consumptionMah;
514     }
515 
516     /**
517      * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
518      * duration.
519      */
calcInactiveStatePowerMah(long sleepDurationMs, long idleDurationMs)520     public double calcInactiveStatePowerMah(long sleepDurationMs, long idleDurationMs) {
521         if (mSleepPowerEstimator == null || mIdlePowerEstimator == null) return Double.NaN;
522         final double sleepConsumptionMah = mSleepPowerEstimator.calculatePower(sleepDurationMs);
523         final double idleConsumptionMah = mIdlePowerEstimator.calculatePower(idleDurationMs);
524         if (DEBUG) {
525             Log.d(TAG, "Calculated sleep consumption " + sleepConsumptionMah
526                     + " mAH from a duration of " + sleepDurationMs + " ms and idle consumption "
527                     + idleConsumptionMah + " mAH from a duration of " + idleDurationMs);
528         }
529         return sleepConsumptionMah + idleConsumptionMah;
530     }
531 
532     /**
533      * Calculates active radio power consumption (in milliamp-hours) from active radio duration.
534      */
calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs)535     public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) {
536         return mActivePowerEstimator.calculatePower(radioActiveDurationMs);
537     }
538 
539     /**
540      * Calculates idle radio power consumption (in milliamp-hours) for time spent at a cell signal
541      * strength level.
542      * see {@link CellSignalStrength#getNumSignalStrengthLevels()}
543      */
calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel)544     public double calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel) {
545         return mIdlePowerEstimators[strengthLevel].calculatePower(strengthTimeMs);
546     }
547 
548     /**
549      * Calculates radio scan power consumption (in milliamp-hours) from scan time.
550      */
calcScanTimePowerMah(long scanningTimeMs)551     public double calcScanTimePowerMah(long scanningTimeMs) {
552         return mScanPowerEstimator.calculatePower(scanningTimeMs);
553     }
554 }
555