blob: 1b3bc234fc0fa91c0aaf3a18ffa3c9f299b17f9f [file] [log] [blame]
/*
* Copyright (C) 2020 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.text.format.DateUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
/**
* Estimates power consumed by the screen(s)
*/
public class ScreenPowerCalculator extends PowerCalculator {
private static final String TAG = "ScreenPowerCalculator";
private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
// Minimum amount of time the screen should be on to start smearing drain to apps
public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS;
private final UsageBasedPowerEstimator mScreenOnPowerEstimator;
private final UsageBasedPowerEstimator mScreenFullPowerEstimator;
private static class PowerAndDuration {
public long durationMs;
public double powerMah;
}
public ScreenPowerCalculator(PowerProfile powerProfile) {
mScreenOnPowerEstimator = new UsageBasedPowerEstimator(
powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON));
mScreenFullPowerEstimator = new UsageBasedPowerEstimator(
powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL));
}
@Override
public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
final long consumptionUC = batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC, query);
calculateTotalDurationAndPower(totalPowerAndDuration, powerModel, batteryStats,
rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, consumptionUC);
double totalAppPower = 0;
long totalAppDuration = 0;
// Now deal with each app's UidBatteryConsumer. The results are stored in the
// BatteryConsumer.POWER_COMPONENT_SCREEN power component, which is considered smeared,
// but the method depends on the data source.
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
switch (powerModel) {
case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.getBatteryStatsUid(),
rawRealtimeUs);
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
appPowerAndDuration.durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
appPowerAndDuration.powerMah, powerModel);
totalAppPower += appPowerAndDuration.powerMah;
totalAppDuration += appPowerAndDuration.durationMs;
}
break;
case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
default:
smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
rawRealtimeUs);
totalAppPower = totalPowerAndDuration.powerMah;
totalAppDuration = totalPowerAndDuration.durationMs;
}
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
Math.max(totalPowerAndDuration.powerMah, totalAppPower), powerModel)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
totalPowerAndDuration.durationMs);
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppPower, powerModel)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppDuration);
}
/**
* Screen power is the additional power the screen takes while the device is running.
*/
@Override
public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
final long consumptionUC = batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
final int powerModel = getPowerModel(consumptionUC);
calculateTotalDurationAndPower(totalPowerAndDuration, powerModel, batteryStats,
rawRealtimeUs, statsType, consumptionUC);
if (totalPowerAndDuration.powerMah == 0) {
return;
}
// First deal with the SCREEN BatterySipper (since we need this for smearing over apps).
final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
bs.usagePowerMah = totalPowerAndDuration.powerMah;
bs.usageTimeMs = totalPowerAndDuration.durationMs;
bs.sumPower();
sippers.add(bs);
// Now deal with each app's BatterySipper. The results are stored in the screenPowerMah
// field, which is considered smeared, but the method depends on the data source.
switch (powerModel) {
case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper app = sippers.get(i);
if (app.drainType == BatterySipper.DrainType.APP) {
calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.uidObj,
rawRealtimeUs);
app.screenPowerMah = appPowerAndDuration.powerMah;
}
}
break;
case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
default:
smearScreenBatterySipper(sippers, bs, rawRealtimeUs);
}
}
/**
* Stores duration and power information in totalPowerAndDuration.
*/
private void calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration,
@BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
long rawRealtimeUs, int statsType, long consumptionUC) {
totalPowerAndDuration.durationMs = calculateDuration(batteryStats, rawRealtimeUs,
statsType);
switch (powerModel) {
case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
totalPowerAndDuration.powerMah = uCtoMah(consumptionUC);
break;
case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
default:
totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
rawRealtimeUs, statsType, totalPowerAndDuration.durationMs);
}
}
private void calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration,
BatteryStats.Uid u, long rawRealtimeUs) {
appPowerAndDuration.durationMs = getProcessForegroundTimeMs(u, rawRealtimeUs);
final long chargeUC = u.getScreenOnMeasuredBatteryConsumptionUC();
if (chargeUC < 0) {
Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
appPowerAndDuration.powerMah = 0;
return;
}
appPowerAndDuration.powerMah = uCtoMah(chargeUC);
}
private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
}
private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs,
int statsType, long durationMs) {
double power = mScreenOnPowerEstimator.calculatePower(durationMs);
for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
final long brightnessTime =
batteryStats.getScreenBrightnessTime(i, rawRealtimeUs, statsType) / 1000;
final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime)
* (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
if (DEBUG && binPowerMah != 0) {
Slog.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+ " power=" + formatCharge(binPowerMah));
}
power += binPowerMah;
}
return power;
}
/**
* Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
* time, and store this in the {@link BatterySipper#screenPowerMah} field.
*/
@VisibleForTesting
public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper,
long rawRealtimeUs) {
long totalActivityTimeMs = 0;
final SparseLongArray activityTimeArray = new SparseLongArray();
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatteryStats.Uid uid = sippers.get(i).uidObj;
if (uid != null) {
final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
activityTimeArray.put(uid.getUid(), timeMs);
totalActivityTimeMs += timeMs;
}
}
if (screenSipper != null && totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
final double totalScreenPowerMah = screenSipper.totalPowerMah;
for (int i = sippers.size() - 1; i >= 0; i--) {
final BatterySipper sipper = sippers.get(i);
sipper.screenPowerMah = totalScreenPowerMah
* activityTimeArray.get(sipper.getUid(), 0)
/ totalActivityTimeMs;
}
}
}
/**
* Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
* time, and store this in the {@link BatterySipper#screenPowerMah} field.
*/
private void smearScreenBatteryDrain(
SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders,
PowerAndDuration totalPowerAndDuration, long rawRealtimeUs) {
long totalActivityTimeMs = 0;
final SparseLongArray activityTimeArray = new SparseLongArray();
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final BatteryStats.Uid uid = uidBatteryConsumerBuilders.valueAt(i).getBatteryStatsUid();
final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
activityTimeArray.put(uid.getUid(), timeMs);
totalActivityTimeMs += timeMs;
}
if (totalActivityTimeMs >= MIN_ACTIVE_TIME_FOR_SMEARING) {
final double totalScreenPowerMah = totalPowerAndDuration.powerMah;
for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
final long durationMs = activityTimeArray.get(app.getUid(), 0);
final double powerMah = totalScreenPowerMah * durationMs / totalActivityTimeMs;
app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, durationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah,
BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
}
}
/** Get the minimum of the uid's ForegroundActivity time and its TOP time. */
@VisibleForTesting
public long getProcessForegroundTimeMs(BatteryStats.Uid uid, long rawRealTimeUs) {
final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP};
long timeUs = 0;
for (int type : foregroundTypes) {
final long localTime = uid.getProcessStateTime(type, rawRealTimeUs,
BatteryStats.STATS_SINCE_CHARGED);
timeUs += localTime;
}
// Return the min value of STATE_TOP time and foreground activity time, since both of these
// time have some errors.
return Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)) / 1000;
}
/** Get the ForegroundActivity time of the given uid. */
@VisibleForTesting
public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
if (timer == null) {
return 0;
}
return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
}
}