summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/BatteryStats.java2
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java2
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java91
-rw-r--r--services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java227
-rw-r--r--services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java136
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java207
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);
+ }
+}