diff options
6 files changed, 656 insertions, 9 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index b2f333ae58e8..cdffea4e9ede 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2491,7 +2491,7 @@ public abstract class BatteryStats { public static final int SCREEN_BRIGHTNESS_LIGHT = 3; public static final int SCREEN_BRIGHTNESS_BRIGHT = 4; - static final String[] SCREEN_BRIGHTNESS_NAMES = { + public static final String[] SCREEN_BRIGHTNESS_NAMES = { "dark", "dim", "medium", "light", "bright" }; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index d2c6e555e24a..0f01fe044702 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -636,6 +636,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryConsumer.POWER_COMPONENT_CPU, Flags.streamlinedBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_SCREEN, + Flags.streamlinedMiscBatteryStats()); mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, Flags.streamlinedConnectivityBatteryStats()); mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 1b7bf89d7b44..6f7b6fa5c584 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -296,6 +296,7 @@ public class BatteryStatsImpl extends BatteryStats { private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>(); private int[] mCpuPowerBracketMap; private final CpuPowerStatsCollector mCpuPowerStatsCollector; + private final ScreenPowerStatsCollector mScreenPowerStatsCollector; private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector; private final WifiPowerStatsCollector mWifiPowerStatsCollector; private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector; @@ -303,6 +304,54 @@ public class BatteryStatsImpl extends BatteryStats { private final GnssPowerStatsCollector mGnssPowerStatsCollector; private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector; private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray(); + private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever = + new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() { + + @Override + public long getScreenOnTimeMs(int display) { + synchronized (BatteryStatsImpl.this) { + return getDisplayScreenOnTime(display, + mClock.elapsedRealtime() * 1000) / 1000; + } + } + + @Override + public long getBrightnessLevelTimeMs(int display, int brightnessLevel) { + synchronized (BatteryStatsImpl.this) { + return getDisplayScreenBrightnessTime(display, brightnessLevel, + mClock.elapsedRealtime() * 1000) / 1000; + } + } + + @Override + public long getScreenDozeTimeMs(int display) { + synchronized (BatteryStatsImpl.this) { + return getDisplayScreenDozeTime(display, + mClock.elapsedRealtime() * 1000) / 1000; + } + } + + @Override + public void retrieveTopActivityTimes(Callback callback) { + synchronized (BatteryStatsImpl.this) { + long elapsedTimeUs = mClock.elapsedRealtime() * 1000; + for (int i = mUidStats.size() - 1; i >= 0; i--) { + Uid uid = mUidStats.valueAt(i); + long topStateTime = uid.getProcessStateTime(Uid.PROCESS_STATE_TOP, + elapsedTimeUs, STATS_SINCE_CHARGED) / 1000; + Timer timer = uid.getForegroundActivityTimer(); + if (timer == null) { + callback.onUidTopActivityTime(uid.mUid, topStateTime); + } else { + long topActivityTime = timer.getTotalTimeLocked(elapsedTimeUs, + STATS_SINCE_CHARGED) / 1000; + callback.onUidTopActivityTime(uid.mUid, Math.min(topStateTime, + topActivityTime)); + } + } + } + } + }; private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever = new WifiPowerStatsCollector.WifiStatsRetriever() { @Override @@ -1966,8 +2015,9 @@ public class BatteryStatsImpl extends BatteryStats { } private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector, - MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector, - BluetoothPowerStatsCollector.Injector, EnergyConsumerPowerStatsCollector.Injector { + ScreenPowerStatsCollector.Injector, MobileRadioPowerStatsCollector.Injector, + WifiPowerStatsCollector.Injector, BluetoothPowerStatsCollector.Injector, + EnergyConsumerPowerStatsCollector.Injector { private PackageManager mPackageManager; private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; private NetworkStatsManager mNetworkStatsManager; @@ -2039,6 +2089,16 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() { + return mScreenUsageTimeRetriever; + } + + @Override + public int getDisplayCount() { + return BatteryStatsImpl.this.getDisplayCount(); + } + + @Override public Supplier<NetworkStats> getMobileNetworkStatsSupplier() { return () -> readMobileNetworkStatsLocked(mNetworkStatsManager); } @@ -5736,13 +5796,17 @@ public class BatteryStatsImpl extends BatteryStats { maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs); if (shouldScheduleSync) { - final int numDisplays = mPerDisplayBatteryStats.length; - final int[] displayStates = new int[numDisplays]; - for (int i = 0; i < numDisplays; i++) { - displayStates[i] = mPerDisplayBatteryStats[i].screenState; + if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) { + mScreenPowerStatsCollector.schedule(); + } else { + final int numDisplays = mPerDisplayBatteryStats.length; + final int[] displayStates = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + displayStates[i] = mPerDisplayBatteryStats[i].screenState; + } + mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag, + batteryRunning, batteryScreenOffRunning, state, displayStates); } - mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag, - batteryRunning, batteryScreenOffRunning, state, displayStates); } } @@ -11290,6 +11354,9 @@ public class BatteryStatsImpl extends BatteryStats { mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); + mScreenPowerStatsCollector = new ScreenPowerStatsCollector(mPowerStatsCollectorInjector); + mScreenPowerStatsCollector.addConsumer(this::recordPowerStats); + mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector( mPowerStatsCollectorInjector, this::onMobileRadioPowerStatsRetrieved); mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats); @@ -14750,6 +14817,10 @@ public class BatteryStatsImpl extends BatteryStats { mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)); mCpuPowerStatsCollector.schedule(); + mScreenPowerStatsCollector.setEnabled( + mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)); + mScreenPowerStatsCollector.schedule(); + mMobileRadioPowerStatsCollector.setEnabled( mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)); mMobileRadioPowerStatsCollector.schedule(); @@ -14786,6 +14857,8 @@ public class BatteryStatsImpl extends BatteryStats { switch (powerComponent) { case BatteryConsumer.POWER_COMPONENT_CPU: return mCpuPowerStatsCollector; + case BatteryConsumer.POWER_COMPONENT_SCREEN: + return mScreenPowerStatsCollector; case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO: return mMobileRadioPowerStatsCollector; case BatteryConsumer.POWER_COMPONENT_WIFI: @@ -16329,6 +16402,7 @@ public class BatteryStatsImpl extends BatteryStats { */ public void schedulePowerStatsSampleCollection() { mCpuPowerStatsCollector.forceSchedule(); + mScreenPowerStatsCollector.forceSchedule(); mMobileRadioPowerStatsCollector.forceSchedule(); mWifiPowerStatsCollector.forceSchedule(); mBluetoothPowerStatsCollector.forceSchedule(); @@ -16351,6 +16425,7 @@ public class BatteryStatsImpl extends BatteryStats { */ public void dumpStatsSample(PrintWriter pw) { mCpuPowerStatsCollector.collectAndDump(pw); + mScreenPowerStatsCollector.collectAndDump(pw); mMobileRadioPowerStatsCollector.collectAndDump(pw); mWifiPowerStatsCollector.collectAndDump(pw); mBluetoothPowerStatsCollector.collectAndDump(pw); diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java new file mode 100644 index 000000000000..291f28940424 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 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.server.power.stats; + +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.PersistableBundle; +import android.util.Slog; +import android.util.SparseLongArray; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import java.util.Arrays; +import java.util.function.IntSupplier; + +public class ScreenPowerStatsCollector extends PowerStatsCollector { + private static final String TAG = "ScreenPowerStatsCollector"; + + interface ScreenUsageTimeRetriever { + interface Callback { + void onUidTopActivityTime(int uid, long topActivityTimeMs); + } + + void retrieveTopActivityTimes(Callback callback); + + long getScreenOnTimeMs(int display); + long getBrightnessLevelTimeMs(int display, int brightnessLevel); + long getScreenDozeTimeMs(int display); + } + + interface Injector { + Handler getHandler(); + Clock getClock(); + PowerStatsUidResolver getUidResolver(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); + ConsumedEnergyRetriever getConsumedEnergyRetriever(); + IntSupplier getVoltageSupplier(); + ScreenUsageTimeRetriever getScreenUsageTimeRetriever(); + int getDisplayCount(); + } + + private static final long ENERGY_UNSPECIFIED = -1; + + private final Injector mInjector; + private boolean mIsInitialized; + private ScreenPowerStatsLayout mLayout; + private int mDisplayCount; + private PowerStats mPowerStats; + private ConsumedEnergyRetriever mConsumedEnergyRetriever; + private IntSupplier mVoltageSupplier; + private ScreenUsageTimeRetriever mScreenUsageTimeRetriever; + private int[] mEnergyConsumerIds = new int[0]; + private long[] mLastConsumedEnergyUws; + private int mLastVoltageMv; + private boolean mFirstSample = true; + private long[] mLastScreenOnTime; + private long[][] mLastBrightnessLevelTime; + private long[] mLastDozeTime; + private final SparseLongArray mLastTopActivityTime = new SparseLongArray(); + private long mLastCollectionTime; + + ScreenPowerStatsCollector(Injector injector) { + super(injector.getHandler(), + injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_SCREEN)), + injector.getUidResolver(), injector.getClock()); + mInjector = injector; + } + + private boolean ensureInitialized() { + if (mIsInitialized) { + return true; + } + + if (!isEnabled()) { + return false; + } + + mDisplayCount = mInjector.getDisplayCount(); + mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); + mVoltageSupplier = mInjector.getVoltageSupplier(); + mScreenUsageTimeRetriever = mInjector.getScreenUsageTimeRetriever(); + mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds( + EnergyConsumerType.DISPLAY); + mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length]; + Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); + + mLayout = new ScreenPowerStatsLayout(); + mLayout.addDeviceScreenUsageDurationSection(mInjector.getDisplayCount()); + mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length); + mLayout.addDeviceSectionUsageDuration(); + mLayout.addDeviceSectionPowerEstimate(); + mLayout.addUidTopActivitiyDuration(); + mLayout.addUidSectionPowerEstimate(); + + PersistableBundle extras = new PersistableBundle(); + mLayout.toExtras(extras); + PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor( + BatteryConsumer.POWER_COMPONENT_SCREEN, mLayout.getDeviceStatsArrayLength(), + null, 0, mLayout.getUidStatsArrayLength(), + extras); + + mLastScreenOnTime = new long[mDisplayCount]; + mLastBrightnessLevelTime = new long[mDisplayCount][BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS]; + mLastDozeTime = new long[mDisplayCount]; + + mPowerStats = new PowerStats(powerStatsDescriptor); + + mIsInitialized = true; + return true; + } + + @Override + protected PowerStats collectStats() { + if (!ensureInitialized()) { + return null; + } + + if (mEnergyConsumerIds.length != 0) { + collectEnergyConsumers(); + } + + for (int display = 0; display < mDisplayCount; display++) { + long screenOnTimeMs = mScreenUsageTimeRetriever.getScreenOnTimeMs(display); + if (!mFirstSample) { + mLayout.setScreenOnDuration(mPowerStats.stats, display, + screenOnTimeMs - mLastScreenOnTime[display]); + } + mLastScreenOnTime[display] = screenOnTimeMs; + + for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) { + long brightnessLevelTimeMs = + mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(display, level); + if (!mFirstSample) { + mLayout.setBrightnessLevelDuration(mPowerStats.stats, display, level, + brightnessLevelTimeMs - mLastBrightnessLevelTime[display][level]); + } + mLastBrightnessLevelTime[display][level] = brightnessLevelTimeMs; + } + long screenDozeTimeMs = mScreenUsageTimeRetriever.getScreenDozeTimeMs(display); + if (!mFirstSample) { + mLayout.setScreenDozeDuration(mPowerStats.stats, display, + screenDozeTimeMs - mLastDozeTime[display]); + } + mLastDozeTime[display] = screenDozeTimeMs; + } + + mPowerStats.uidStats.clear(); + + mScreenUsageTimeRetriever.retrieveTopActivityTimes((uid, topActivityTimeMs) -> { + long topActivityDuration = topActivityTimeMs - mLastTopActivityTime.get(uid); + if (topActivityDuration == 0) { + return; + } + mLastTopActivityTime.put(uid, topActivityTimeMs); + + int mappedUid = mUidResolver.mapUid(uid); + long[] uidStats = mPowerStats.uidStats.get(mappedUid); + if (uidStats == null) { + uidStats = new long[mLayout.getUidStatsArrayLength()]; + mPowerStats.uidStats.put(mappedUid, uidStats); + } + + mLayout.setUidTopActivityDuration(uidStats, + mLayout.getUidTopActivityDuration(uidStats) + topActivityDuration); + }); + + long elapsedRealtime = mClock.elapsedRealtime(); + mPowerStats.durationMs = elapsedRealtime - mLastCollectionTime; + mLastCollectionTime = elapsedRealtime; + + mFirstSample = false; + + return mPowerStats; + } + + private void collectEnergyConsumers() { + int voltageMv = mVoltageSupplier.getAsInt(); + if (voltageMv <= 0) { + Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv + + " mV) when querying energy consumers"); + return; + } + + int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; + mLastVoltageMv = voltageMv; + + long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds); + if (energyUws == null) { + return; + } + + for (int i = energyUws.length - 1; i >= 0; i--) { + long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED + ? energyUws[i] - mLastConsumedEnergyUws[i] : 0; + if (energyDelta < 0) { + // Likely, restart of powerstats HAL + energyDelta = 0; + } + mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage)); + mLastConsumedEnergyUws[i] = energyUws[i]; + } + } + + @Override + protected void onUidRemoved(int uid) { + mLastTopActivityTime.delete(uid); + } +} diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java new file mode 100644 index 000000000000..24fee9ee6bbf --- /dev/null +++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2024 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.server.power.stats; + +import android.os.BatteryStats; +import android.os.PersistableBundle; + +/** + * Captures the positions and lengths of sections of the stats array, such as time-in-state, + * power usage estimates etc. + */ +public class ScreenPowerStatsLayout extends PowerStatsLayout { + private static final String EXTRA_DEVICE_SCREEN_COUNT = "dsc"; + private static final String EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION = "dsd"; + private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbd"; + private static final String EXTRA_DEVICE_DOZE_DURATION_POSITION = "ddd"; + private static final String EXTRA_UID_FOREGROUND_DURATION = "uf"; + + private int mDisplayCount; + private int mDeviceScreenOnDurationPosition; + private int[] mDeviceBrightnessDurationPositions; + private int mDeviceScreenDozeDurationPosition; + private int mUidTopActivityTimePosition; + + void addDeviceScreenUsageDurationSection(int displayCount) { + mDisplayCount = displayCount; + mDeviceScreenOnDurationPosition = addDeviceSection(displayCount, "on"); + mDeviceBrightnessDurationPositions = new int[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS]; + for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) { + mDeviceBrightnessDurationPositions[level] = + addDeviceSection(displayCount, BatteryStats.SCREEN_BRIGHTNESS_NAMES[level]); + } + mDeviceScreenDozeDurationPosition = addDeviceSection(displayCount, "doze"); + } + + public int getDisplayCount() { + return mDisplayCount; + } + + /** + * Stores screen-on time for the specified display. + */ + public void setScreenOnDuration(long[] stats, int display, long durationMs) { + stats[mDeviceScreenOnDurationPosition + display] = durationMs; + } + + /** + * Returns screen-on time for the specified display. + */ + public long getScreenOnDuration(long[] stats, int display) { + return stats[mDeviceScreenOnDurationPosition + display]; + } + + /** + * Stores time at the specified brightness level for the specified display. + */ + public void setBrightnessLevelDuration(long[] stats, int display, int brightnessLevel, + long durationMs) { + stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display] = durationMs; + } + + /** + * Returns time at the specified brightness level for the specified display. + */ + public long getBrightnessLevelDuration(long[] stats, int display, int brightnessLevel) { + return stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display]; + } + + /** + * Stores time in the doze (ambient) state for the specified display. + */ + public void setScreenDozeDuration(long[] stats, int display, long durationMs) { + stats[mDeviceScreenDozeDurationPosition + display] = durationMs; + } + + /** + * Retrieves time in the doze (ambient) state for the specified display. + */ + public long getScreenDozeDuration(long[] stats, int display) { + return stats[mDeviceScreenDozeDurationPosition + display]; + } + + void addUidTopActivitiyDuration() { + mUidTopActivityTimePosition = addUidSection(1, "top"); + } + + /** + * Stores time the UID spent in the TOP state. + */ + public void setUidTopActivityDuration(long[] stats, long durationMs) { + stats[mUidTopActivityTimePosition] = durationMs; + } + + /** + * Returns time the UID spent in the TOP state. + */ + public long getUidTopActivityDuration(long[] stats) { + return stats[mUidTopActivityTimePosition]; + } + + @Override + public void toExtras(PersistableBundle extras) { + super.toExtras(extras); + extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount); + extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition); + extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS, + mDeviceBrightnessDurationPositions); + extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition); + extras.putInt(EXTRA_UID_FOREGROUND_DURATION, mUidTopActivityTimePosition); + } + + @Override + public void fromExtras(PersistableBundle extras) { + super.fromExtras(extras); + mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1); + mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION); + mDeviceBrightnessDurationPositions = extras.getIntArray( + EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS); + mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION); + mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION); + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java new file mode 100644 index 000000000000..817fdcb10577 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2024 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.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.Handler; +import android.platform.test.ravenwood.RavenwoodRule; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; +import com.android.server.power.stats.ScreenPowerStatsCollector.Injector; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.IntSupplier; + +public class ScreenPowerStatsCollectorTest { + private static final int APP_UID1 = 42; + private static final int APP_UID2 = 24; + private static final int ISOLATED_UID = 99123; + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, 1000); + + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + @Mock + private PowerStatsUidResolver mPowerStatsUidResolver; + @Mock + private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever; + + private final Injector mInjector = new Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> 3500; + } + + @Override + public int getDisplayCount() { + return 2; + } + + @Override + public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() { + return mScreenUsageTimeRetriever; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> { + int uid = invocation.getArgument(0); + if (uid == ISOLATED_UID) { + return APP_UID2; + } else { + return uid; + } + }); + } + + @Test + public void collectStats() { + ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector); + collector.setEnabled(true); + + // Establish a baseline + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY)) + .thenReturn(new int[]{77}); + when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77})) + .thenReturn(new long[]{10_000}); + + doAnswer(inv -> { + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback = + inv.getArgument(0); + callback.onUidTopActivityTime(APP_UID1, 1000); + callback.onUidTopActivityTime(APP_UID2, 2000); + return null; + }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any( + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class)); + + collector.collectStats(); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77})) + .thenReturn(new long[]{45_000}); + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0)) + .thenReturn(60_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_DARK)) + .thenReturn(10_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_MEDIUM)) + .thenReturn(20_000L); + when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0, + BatteryStats.SCREEN_BRIGHTNESS_BRIGHT)) + .thenReturn(30_000L); + when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1)) + .thenReturn(120_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0)) + .thenReturn(180_000L); + when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1)) + .thenReturn(240_000L); + doAnswer(inv -> { + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback = + inv.getArgument(0); + callback.onUidTopActivityTime(APP_UID1, 3000); + callback.onUidTopActivityTime(APP_UID2, 5000); + callback.onUidTopActivityTime(ISOLATED_UID, 7000); + return null; + }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any( + ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class)); + + + PowerStats powerStats = collector.collectStats(); + + ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(); + layout.fromExtras(powerStats.descriptor.extras); + + // (45000 - 10000) / 3500 + assertThat(layout.getConsumedEnergy(powerStats.stats, 0)) + .isEqualTo(10_000); + + assertThat(layout.getScreenOnDuration(powerStats.stats, 0)) + .isEqualTo(60_000); + assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0, + BatteryStats.SCREEN_BRIGHTNESS_DARK)) + .isEqualTo(10_000); + assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0, + BatteryStats.SCREEN_BRIGHTNESS_MEDIUM)) + .isEqualTo(20_000); + assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0, + BatteryStats.SCREEN_BRIGHTNESS_BRIGHT)) + .isEqualTo(30_000); + assertThat(layout.getScreenOnDuration(powerStats.stats, 1)) + .isEqualTo(120_000); + assertThat(layout.getScreenDozeDuration(powerStats.stats, 0)) + .isEqualTo(180_000); + assertThat(layout.getScreenDozeDuration(powerStats.stats, 1)) + .isEqualTo(240_000); + + assertThat(powerStats.uidStats.size()).isEqualTo(2); + // 3000 - 1000 + assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID1))) + .isEqualTo(2000); + // (5000 - 2000) + 7000 + assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID2))) + .isEqualTo(10000); + } +} |