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