blob: eb5993dc2d61b55122cb1248b2b4e4b3b4d25af9 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.telephony.CellSignalStrength;
import android.util.Log;
import android.util.SparseArray;
import java.util.List;
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobRadioPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
private static final int NUM_SIGNAL_STRENGTH_LEVELS =
CellSignalStrength.getNumSignalStrengthLevels();
private final UsageBasedPowerEstimator mActivePowerEstimator;
private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
private final UsageBasedPowerEstimator mScanPowerEstimator;
private static class PowerAndDuration {
public long durationMs;
public double remainingPowerMah;
public long totalAppDurationMs;
public double totalAppPowerMah;
public long signalDurationMs;
public long noCoverageDurationMs;
}
public MobileRadioPowerCalculator(PowerProfile profile) {
// Power consumption when radio is active
double powerRadioActiveMa =
profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
if (powerRadioActiveMa == -1) {
double sum = 0;
sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
}
powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
}
mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
// Power consumption when radio is on, but idle
if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
}
} else {
double idle = profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE);
// Magical calculations preserved for historical compatibility
mIdlePowerEstimators[0] = new UsageBasedPowerEstimator(idle * 25 / 180);
for (int i = 1; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(Math.max(1, idle / 256));
}
}
mScanPowerEstimator = new UsageBasedPowerEstimator(
profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_SCANNING, 0));
}
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
PowerAndDuration total = new PowerAndDuration();
final double powerPerPacketMah = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
final BatteryStats.Uid uid = app.getBatteryStatsUid();
calculateApp(app, uid, powerPerPacketMah, total, query);
}
final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC);
if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.remainingPowerMah + total.totalAppPowerMah, powerModel);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.totalAppPowerMah, powerModel);
}
}
private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
double powerPerPacketMah, PowerAndDuration total,
BatteryUsageStatsQuery query) {
final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
total.totalAppDurationMs += radioActiveDurationMs;
final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
final double powerMah = calculatePower(u, powerModel, powerPerPacketMah,
radioActiveDurationMs, consumptionUC);
total.totalAppPowerMah += powerMah;
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
radioActiveDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
powerModel);
}
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
final double mobilePowerPerPacket = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
statsType);
PowerAndDuration total = new PowerAndDuration();
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper app = sippers.get(i);
if (app.drainType == BatterySipper.DrainType.APP) {
final BatteryStats.Uid u = app.uidObj;
calculateApp(app, u, statsType, mobilePowerPerPacket, total);
}
}
BatterySipper radio = new BatterySipper(BatterySipper.DrainType.CELL, null, 0);
final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC);
calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC);
if (total.remainingPowerMah != 0) {
if (total.signalDurationMs != 0) {
radio.noCoveragePercent =
total.noCoverageDurationMs * 100.0 / total.signalDurationMs;
}
radio.mobileActive = total.durationMs;
radio.mobileActiveCount = batteryStats.getMobileRadioActiveUnknownCount(statsType);
radio.mobileRadioPowerMah = total.remainingPowerMah;
radio.sumPower();
}
if (radio.totalPowerMah > 0) {
sippers.add(radio);
}
}
private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
double powerPerPacketMah, PowerAndDuration total) {
app.mobileActive = calculateDuration(u, statsType);
final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC);
app.mobileRadioPowerMah = calculatePower(u, powerModel, powerPerPacketMah, app.mobileActive,
consumptionUC);
total.totalAppDurationMs += app.mobileActive;
// Add cost of mobile traffic.
app.mobileRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
app.mobileTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
app.mobileActiveCount = u.getMobileRadioActiveCount(statsType);
app.mobileRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
app.mobileTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
if (DEBUG && app.mobileRadioPowerMah != 0) {
Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+ (app.mobileRxPackets + app.mobileTxPackets)
+ " active time " + app.mobileActive
+ " power=" + formatCharge(app.mobileRadioPowerMah));
}
}
private long calculateDuration(BatteryStats.Uid u, int statsType) {
return u.getMobileRadioActiveTime(statsType) / 1000;
}
private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
double powerPerPacketMah, long radioActiveDurationMs, long measuredChargeUC) {
if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
return uCtoMah(measuredChargeUC);
}
if (radioActiveDurationMs > 0) {
// We are tracking when the radio is up, so can use the active time to
// determine power use.
return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
} else {
// We are not tracking when the radio is up, so must approximate power use
// based on the number of packets.
final long mobileRxPackets = u.getNetworkActivityPackets(
BatteryStats.NETWORK_MOBILE_RX_DATA,
BatteryStats.STATS_SINCE_CHARGED);
final long mobileTxPackets = u.getNetworkActivityPackets(
BatteryStats.NETWORK_MOBILE_TX_DATA,
BatteryStats.STATS_SINCE_CHARGED);
return (mobileRxPackets + mobileTxPackets) * powerPerPacketMah;
}
}
private void calculateRemaining(PowerAndDuration total,
@BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
long rawRealtimeUs, long consumptionUC) {
long signalTimeMs = 0;
double powerMah = 0;
if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
powerMah = uCtoMah(consumptionUC);
}
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
if (DEBUG && p != 0) {
Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+ formatCharge(p));
}
powerMah += p;
}
signalTimeMs += strengthTimeMs;
if (i == 0) {
total.noCoverageDurationMs = strengthTimeMs;
}
}
final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED) / 1000;
long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
final double p = calcScanTimePowerMah(scanningTimeMs);
if (DEBUG && p != 0) {
Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + formatCharge(
p));
}
powerMah += p;
if (remainingActiveTimeMs > 0) {
powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
}
}
total.durationMs = radioActiveTimeMs;
total.remainingPowerMah = powerMah;
total.signalDurationMs = signalTimeMs;
}
/**
* Calculates active radio power consumption (in milliamp-hours) from active radio duration.
*/
public double calcPowerFromRadioActiveDurationMah(long radioActiveDurationMs) {
return mActivePowerEstimator.calculatePower(radioActiveDurationMs);
}
/**
* Calculates idle radio power consumption (in milliamp-hours) for time spent at a cell signal
* strength level.
* see {@link CellSignalStrength#getNumSignalStrengthLevels()}
*/
public double calcIdlePowerAtSignalStrengthMah(long strengthTimeMs, int strengthLevel) {
return mIdlePowerEstimators[strengthLevel].calculatePower(strengthTimeMs);
}
/**
* Calculates radio scan power consumption (in milliamp-hours) from scan time.
*/
public double calcScanTimePowerMah(long scanningTimeMs) {
return mScanPowerEstimator.calculatePower(scanningTimeMs);
}
/**
* Return estimated power (in mAh) of sending or receiving a packet with the mobile radio.
*/
private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) {
final long radioDataUptimeMs =
stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
final double mobilePower = calcPowerFromRadioActiveDurationMah(radioDataUptimeMs);
final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
statsType);
final long mobileTx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
statsType);
final long mobilePackets = mobileRx + mobileTx;
return mobilePackets != 0 ? mobilePower / mobilePackets : 0;
}
}