Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a84c096a authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Make ScreenPowerCalculator use measured energy with BatteryUsageStats

Test: atest FrameworksCoreTests:com.android.internal.os.ScreenPowerCalculatorTest

Bug: 158137862
Bug: 178140704
Change-Id: I8066e0712db88a8825dc973d9efa45fdf70a2ef5
parent 4db015d0
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ public abstract class BatteryConsumer {
            POWER_COMPONENT_SYSTEM_SERVICES,
            POWER_COMPONENT_SENSORS,
            POWER_COMPONENT_GNSS,
            POWER_COMPONENT_SCREEN,
    })
    @Retention(RetentionPolicy.SOURCE)
    public static @interface PowerComponent {
@@ -63,8 +64,9 @@ public abstract class BatteryConsumer {
    public static final int POWER_COMPONENT_MOBILE_RADIO = 8;
    public static final int POWER_COMPONENT_SENSORS = 9;
    public static final int POWER_COMPONENT_GNSS = 10;
    public static final int POWER_COMPONENT_SCREEN = 13;

    public static final int POWER_COMPONENT_COUNT = 11;
    public static final int POWER_COMPONENT_COUNT = 14;

    public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
    public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -85,6 +87,7 @@ public abstract class BatteryConsumer {
            TIME_COMPONENT_MOBILE_RADIO,
            TIME_COMPONENT_SENSORS,
            TIME_COMPONENT_GNSS,
            TIME_COMPONENT_SCREEN,
    })
    @Retention(RetentionPolicy.SOURCE)
    public static @interface TimeComponent {
@@ -101,8 +104,9 @@ public abstract class BatteryConsumer {
    public static final int TIME_COMPONENT_MOBILE_RADIO = 8;
    public static final int TIME_COMPONENT_SENSORS = 9;
    public static final int TIME_COMPONENT_GNSS = 10;
    public static final int TIME_COMPONENT_SCREEN = 13;

    public static final int TIME_COMPONENT_COUNT = 11;
    public static final int TIME_COMPONENT_COUNT = 14;

    public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
    public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
+2 −1
Original line number Diff line number Diff line
@@ -99,12 +99,13 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela
        private boolean mExcludeFromBatteryUsageStats;

        public Builder(int customPowerComponentCount, int customTimeComponentCount,
                BatteryStats.Uid batteryStatsUid) {
                @NonNull BatteryStats.Uid batteryStatsUid) {
            super(customPowerComponentCount, customTimeComponentCount);
            mBatteryStatsUid = batteryStatsUid;
            mUid = batteryStatsUid.getUid();
        }

        @NonNull
        public BatteryStats.Uid getBatteryStatsUid() {
            return mBatteryStatsUid;
        }
+1 −1
Original line number Diff line number Diff line
@@ -83,7 +83,7 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator {

    private double getMeasuredOrEstimatedPower(long measuredEnergyUJ, long durationMs) {
        if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
            return mAhToUJ(measuredEnergyUJ);
            return uJtoMah(measuredEnergyUJ);
        } else {
            return mPowerEstimator.calculatePower(durationMs);
        }
+5 −1
Original line number Diff line number Diff line
@@ -156,7 +156,11 @@ public abstract class PowerCalculator {
        return String.format(Locale.ENGLISH, format, power);
    }

    static double mAhToUJ(long energyUJ) {
    static double uJtoMah(long energyUJ) {
        if (energyUJ == 0) {
            return 0;
        }

        // TODO(b/173765509): Convert properly. This is mJ / V * (h/3600s) = mAh with V = 3.7 fixed.
        //                    Leaving for later since desired units of energy have yet to be decided
        return energyUJ / 1000.0 / 3.7  / 3600;
+124 −48
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.SystemBatteryConsumer;
import android.os.SystemClock;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.text.format.DateUtils;
import android.util.Slog;
@@ -39,9 +39,17 @@ 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));
@@ -52,17 +60,40 @@ public class ScreenPowerCalculator extends PowerCalculator {
    @Override
    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
        final long durationMs = computeDuration(batteryStats, rawRealtimeUs,
                BatteryStats.STATS_SINCE_CHARGED);
        final double powerMah = computePower(batteryStats, rawRealtimeUs,
                BatteryStats.STATS_SINCE_CHARGED, durationMs);
        if (powerMah != 0) {
        final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
        final boolean forceUsePowerProfileModel = (query.getFlags()
                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0;

        final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
                batteryStats, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED,
                forceUsePowerProfileModel);

        builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
                    .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
                .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE,
                        totalPowerAndDuration.durationMs)
                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE,
                        totalPowerAndDuration.powerMah);

        // 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();
        if (useEnergyData) {
            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.TIME_COMPONENT_SCREEN,
                                appPowerAndDuration.durationMs)
                        .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
                                appPowerAndDuration.powerMah);
            }
        } else {
            smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
                    rawRealtimeUs);
        }
        // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
        // TODO(b/178140704): Attribute (measured/smeared) usage *per app* for BatteryUsageStats.
    }

    /**
@@ -71,51 +102,79 @@ public class ScreenPowerCalculator extends PowerCalculator {
    @Override
    public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
            long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {

        final long energyUJ = batteryStats.getScreenOnEnergy();
        final boolean isMeasuredDataAvailable = energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE;

        final long durationMs = computeDuration(batteryStats, rawRealtimeUs, statsType);
        final double powerMah = getMeasuredOrComputedPower(
                energyUJ, batteryStats, rawRealtimeUs, statsType, durationMs);
        if (powerMah == 0) {
        final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
        final boolean useEnergyData = calculateTotalDurationAndPower(totalPowerAndDuration,
                batteryStats, rawRealtimeUs, statsType, false);
        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 = powerMah;
        bs.usageTimeMs = durationMs;
        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.
        if (isMeasuredDataAvailable) {
            super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
        if (useEnergyData) {
            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;
                }
            }
        } else {
            smearScreenBatterySipper(sippers, bs);
            smearScreenBatterySipper(sippers, bs, rawRealtimeUs);
        }
    }

    @Override
    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
            long rawUptimeUs, int statsType) {
    /**
     * Stores duration and power information in totalPowerAndDuration and returns true if measured
     * energy data is available and should be used by the model.
     */
    private boolean calculateTotalDurationAndPower(PowerAndDuration totalPowerAndDuration,
            BatteryStats batteryStats, long rawRealtimeUs, int statsType,
            boolean forceUsePowerProfileModel) {
        totalPowerAndDuration.durationMs = calculateDuration(batteryStats, rawRealtimeUs,
                statsType);

        if (!forceUsePowerProfileModel) {
            final long energyUJ = batteryStats.getScreenOnEnergy();
            if (energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
                totalPowerAndDuration.powerMah = uJtoMah(energyUJ);
                return true;
            }
        }

        totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats,
                rawRealtimeUs, statsType, totalPowerAndDuration.durationMs);
        return false;
    }

    private void calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration,
            BatteryStats.Uid u, long rawRealtimeUs) {
        appPowerAndDuration.durationMs = getProcessForegroundTimeMs(u, rawRealtimeUs);

        final long energyUJ = u.getScreenOnEnergy();
        if (energyUJ < 0) {
            Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
            appPowerAndDuration.powerMah = 0;
            return;
        }
        if (energyUJ == 0) return;
        app.screenPowerMah = mAhToUJ(u.getScreenOnEnergy());

        appPowerAndDuration.powerMah = uJtoMah(energyUJ);
    }

    private long computeDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
    private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
        return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000;
    }

    private double computePower(BatteryStats batteryStats, long rawRealtimeUs, int statsType,
            long durationMs) {
    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 =
@@ -131,35 +190,25 @@ public class ScreenPowerCalculator extends PowerCalculator {
        return power;
    }

    private double getMeasuredOrComputedPower(long measuredEnergyUJ,
            BatteryStats batteryStats, long rawRealtimeUs, int statsType, long durationMs) {

        if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
            return mAhToUJ(measuredEnergyUJ);
        } else {
            return computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
        }
    }

    /**
     * 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) {

    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);
                final long timeMs = getProcessForegroundTimeMs(uid, rawRealtimeUs);
                activityTimeArray.put(uid.getUid(), timeMs);
                totalActivityTimeMs += timeMs;
            }
        }

        if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
        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);
@@ -170,10 +219,37 @@ public class ScreenPowerCalculator extends PowerCalculator {
        }
    }

    /**
     * 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.TIME_COMPONENT_SCREEN, durationMs)
                        .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah);
            }
        }
    }

    /** Get the minimum of the uid's ForegroundActivity time and its TOP time. */
    @VisibleForTesting
    public long getProcessForegroundTimeMs(BatteryStats.Uid uid) {
        final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000;
    public long getProcessForegroundTimeMs(BatteryStats.Uid uid, long rawRealTimeUs) {
        final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP};

        long timeUs = 0;
Loading