Introduce PowerStatsProcessor for WiFi
Bug: 323970018
Test: atest PowerStatsTestsRavenwood && atest PowerStatsTests
Flag: com.android.server.power.optimization.streamlined_connectivity_battery_stats
Change-Id: I1b1bf824aeebcbd9a4a83c969a68b202002d6dca
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 8d620b6..58e851a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -133,6 +133,7 @@
import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.PowerStatsUidResolver;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
+import com.android.server.power.stats.WifiPowerStatsProcessor;
import com.android.server.power.stats.wakeups.CpuWakeupStats;
import java.io.File;
@@ -485,6 +486,7 @@
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessor(
new CpuPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
+
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
.trackDeviceStates(
AggregatedPowerStatsConfig.STATE_POWER,
@@ -495,9 +497,21 @@
AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
.setProcessor(
new MobileRadioPowerStatsProcessor(mPowerProfile));
+
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE,
BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
.setProcessor(new PhoneCallPowerStatsProcessor());
+
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_WIFI)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN,
+ AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+ .setProcessor(
+ new WifiPowerStatsProcessor(mPowerProfile));
return config;
}
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
new file mode 100644
index 0000000..5e9cc40
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
@@ -0,0 +1,425 @@
+/*
+ * 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.util.Slog;
+
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class WifiPowerStatsProcessor extends PowerStatsProcessor {
+ private static final String TAG = "WifiPowerStatsProcessor";
+ private static final boolean DEBUG = false;
+
+ private final UsageBasedPowerEstimator mRxPowerEstimator;
+ private final UsageBasedPowerEstimator mTxPowerEstimator;
+ private final UsageBasedPowerEstimator mIdlePowerEstimator;
+
+ private final UsageBasedPowerEstimator mActivePowerEstimator;
+ private final UsageBasedPowerEstimator mScanPowerEstimator;
+ private final UsageBasedPowerEstimator mBatchedScanPowerEstimator;
+
+ private PowerStats.Descriptor mLastUsedDescriptor;
+ private WifiPowerStatsLayout mStatsLayout;
+ // Sequence of steps for power estimation and intermediate results.
+ private PowerEstimationPlan mPlan;
+
+ private long[] mTmpDeviceStatsArray;
+ private long[] mTmpUidStatsArray;
+ private boolean mHasWifiPowerController;
+
+ public WifiPowerStatsProcessor(PowerProfile powerProfile) {
+ mRxPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX));
+ mTxPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX));
+ mIdlePowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE));
+ mActivePowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE));
+ mScanPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN));
+ mBatchedScanPowerEstimator = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN));
+ }
+
+ private static class Intermediates {
+ /**
+ * Estimated power for the RX state.
+ */
+ public double rxPower;
+ /**
+ * Estimated power for the TX state.
+ */
+ public double txPower;
+ /**
+ * Estimated power in the SCAN state
+ */
+ public double scanPower;
+ /**
+ * Estimated power for IDLE, SCAN states.
+ */
+ public double idlePower;
+ /**
+ * Number of received packets
+ */
+ public long rxPackets;
+ /**
+ * Number of transmitted packets
+ */
+ public long txPackets;
+ /**
+ * Total duration of unbatched scans across all UIDs.
+ */
+ public long basicScanDuration;
+ /**
+ * Estimated power in the unbatched SCAN state
+ */
+ public double basicScanPower;
+ /**
+ * Total duration of batched scans across all UIDs.
+ */
+ public long batchedScanDuration;
+ /**
+ * Estimated power in the BATCHED SCAN state
+ */
+ public double batchedScanPower;
+ /**
+ * Estimated total power when active; used only in the absence of WiFiManager power
+ * reporting.
+ */
+ public double activePower;
+ /**
+ * Measured consumed energy from power monitoring hardware (micro-coulombs)
+ */
+ public long consumedEnergy;
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats) {
+ if (stats.getPowerStatsDescriptor() == null) {
+ return;
+ }
+
+ unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor());
+
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ }
+
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ Intermediates intermediates = new Intermediates();
+ estimation.intermediates = intermediates;
+ computeDevicePowerEstimates(stats, estimation.stateValues, intermediates);
+ }
+
+ double ratio = 1.0;
+ if (mStatsLayout.getEnergyConsumerCount() != 0) {
+ ratio = computeEstimateAdjustmentRatioUsingConsumedEnergy();
+ if (ratio != 1) {
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ adjustDevicePowerEstimates(stats, estimation.stateValues,
+ (Intermediates) estimation.intermediates, ratio);
+ }
+ }
+ }
+
+ combineDeviceStateEstimates();
+
+ ArrayList<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+ if (!uids.isEmpty()) {
+ for (int uid : uids) {
+ for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
+ computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(i));
+ }
+ }
+
+ for (int uid : uids) {
+ for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) {
+ computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i));
+ }
+ }
+ }
+ mPlan.resetIntermediates();
+ }
+
+ private void unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) {
+ if (descriptor.equals(mLastUsedDescriptor)) {
+ return;
+ }
+
+ mLastUsedDescriptor = descriptor;
+ mStatsLayout = new WifiPowerStatsLayout(descriptor);
+ mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
+ mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
+ mHasWifiPowerController = mStatsLayout.isPowerReportingSupported();
+ }
+
+ /**
+ * Compute power estimates using the power profile.
+ */
+ private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+ int[] deviceStates, Intermediates intermediates) {
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) {
+ return;
+ }
+
+ for (int i = mStatsLayout.getEnergyConsumerCount() - 1; i >= 0; i--) {
+ intermediates.consumedEnergy += mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, i);
+ }
+
+ intermediates.basicScanDuration =
+ mStatsLayout.getDeviceBasicScanTime(mTmpDeviceStatsArray);
+ intermediates.batchedScanDuration =
+ mStatsLayout.getDeviceBatchedScanTime(mTmpDeviceStatsArray);
+ if (mHasWifiPowerController) {
+ intermediates.rxPower = mRxPowerEstimator.calculatePower(
+ mStatsLayout.getDeviceRxTime(mTmpDeviceStatsArray));
+ intermediates.txPower = mTxPowerEstimator.calculatePower(
+ mStatsLayout.getDeviceTxTime(mTmpDeviceStatsArray));
+ intermediates.scanPower = mScanPowerEstimator.calculatePower(
+ mStatsLayout.getDeviceScanTime(mTmpDeviceStatsArray));
+ intermediates.idlePower = mIdlePowerEstimator.calculatePower(
+ mStatsLayout.getDeviceIdleTime(mTmpDeviceStatsArray));
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray,
+ intermediates.rxPower + intermediates.txPower + intermediates.scanPower
+ + intermediates.idlePower);
+ } else {
+ intermediates.activePower = mActivePowerEstimator.calculatePower(
+ mStatsLayout.getDeviceActiveTime(mTmpDeviceStatsArray));
+ intermediates.basicScanPower =
+ mScanPowerEstimator.calculatePower(intermediates.basicScanDuration);
+ intermediates.batchedScanPower =
+ mBatchedScanPowerEstimator.calculatePower(intermediates.batchedScanDuration);
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray,
+ intermediates.activePower + intermediates.basicScanPower
+ + intermediates.batchedScanPower);
+ }
+
+ stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray);
+ }
+
+ /**
+ * Compute an adjustment ratio using the total power estimated using the power profile
+ * and the total power measured by hardware.
+ */
+ private double computeEstimateAdjustmentRatioUsingConsumedEnergy() {
+ long totalConsumedEnergy = 0;
+ double totalPower = 0;
+
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ Intermediates intermediates =
+ (Intermediates) mPlan.deviceStateEstimations.get(i).intermediates;
+ if (mHasWifiPowerController) {
+ totalPower += intermediates.rxPower + intermediates.txPower
+ + intermediates.scanPower + intermediates.idlePower;
+ } else {
+ totalPower += intermediates.activePower + intermediates.basicScanPower
+ + intermediates.batchedScanPower;
+ }
+ totalConsumedEnergy += intermediates.consumedEnergy;
+ }
+
+ if (totalPower == 0) {
+ return 1;
+ }
+
+ return uCtoMah(totalConsumedEnergy) / totalPower;
+ }
+
+ /**
+ * Uniformly apply the same adjustment to all power estimates in order to ensure that the total
+ * estimated power matches the measured consumed power. We are not claiming that all
+ * averages captured in the power profile have to be off by the same percentage in reality.
+ */
+ private void adjustDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+ int[] deviceStates, Intermediates intermediates, double ratio) {
+ double adjutedPower;
+ if (mHasWifiPowerController) {
+ intermediates.rxPower *= ratio;
+ intermediates.txPower *= ratio;
+ intermediates.scanPower *= ratio;
+ intermediates.idlePower *= ratio;
+ adjutedPower = intermediates.rxPower + intermediates.txPower + intermediates.scanPower
+ + intermediates.idlePower;
+ } else {
+ intermediates.activePower *= ratio;
+ intermediates.basicScanPower *= ratio;
+ intermediates.batchedScanPower *= ratio;
+ adjutedPower = intermediates.activePower + intermediates.basicScanPower
+ + intermediates.batchedScanPower;
+ }
+
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) {
+ return;
+ }
+
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, adjutedPower);
+ stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray);
+ }
+
+ /**
+ * Combine power estimates before distributing them proportionally to UIDs.
+ */
+ private void combineDeviceStateEstimates() {
+ for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i);
+ Intermediates
+ cdseIntermediates = new Intermediates();
+ cdse.intermediates = cdseIntermediates;
+ List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations;
+ for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) {
+ DeviceStateEstimation dse = deviceStateEstimations.get(j);
+ Intermediates intermediates = (Intermediates) dse.intermediates;
+ if (mHasWifiPowerController) {
+ cdseIntermediates.rxPower += intermediates.rxPower;
+ cdseIntermediates.txPower += intermediates.txPower;
+ cdseIntermediates.scanPower += intermediates.scanPower;
+ cdseIntermediates.idlePower += intermediates.idlePower;
+ } else {
+ cdseIntermediates.activePower += intermediates.activePower;
+ cdseIntermediates.basicScanPower += intermediates.basicScanPower;
+ cdseIntermediates.batchedScanPower += intermediates.batchedScanPower;
+ }
+ cdseIntermediates.basicScanDuration += intermediates.basicScanDuration;
+ cdseIntermediates.batchedScanDuration += intermediates.batchedScanDuration;
+ cdseIntermediates.consumedEnergy += intermediates.consumedEnergy;
+ }
+ }
+ }
+
+ private void computeUidActivityTotals(PowerComponentAggregatedPowerStats stats, int uid,
+ UidStateEstimate uidStateEstimate) {
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ for (UidStateProportionalEstimate proportionalEstimate :
+ uidStateEstimate.proportionalEstimates) {
+ if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) {
+ continue;
+ }
+
+ intermediates.rxPackets += mStatsLayout.getUidRxPackets(mTmpUidStatsArray);
+ intermediates.txPackets += mStatsLayout.getUidTxPackets(mTmpUidStatsArray);
+ }
+ }
+
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, int uid,
+ UidStateEstimate uidStateEstimate) {
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ for (UidStateProportionalEstimate proportionalEstimate :
+ uidStateEstimate.proportionalEstimates) {
+ if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) {
+ continue;
+ }
+
+ double power = 0;
+ if (mHasWifiPowerController) {
+ if (intermediates.rxPackets != 0) {
+ power += intermediates.rxPower * mStatsLayout.getUidRxPackets(mTmpUidStatsArray)
+ / intermediates.rxPackets;
+ }
+ if (intermediates.txPackets != 0) {
+ power += intermediates.txPower * mStatsLayout.getUidTxPackets(mTmpUidStatsArray)
+ / intermediates.txPackets;
+ }
+ long totalScanDuration =
+ intermediates.basicScanDuration + intermediates.batchedScanDuration;
+ if (totalScanDuration != 0) {
+ long scanDuration = mStatsLayout.getUidScanTime(mTmpUidStatsArray)
+ + mStatsLayout.getUidBatchedScanTime(mTmpUidStatsArray);
+ power += intermediates.scanPower * scanDuration / totalScanDuration;
+ }
+ } else {
+ long totalPackets = intermediates.rxPackets + intermediates.txPackets;
+ if (totalPackets != 0) {
+ long packets = mStatsLayout.getUidRxPackets(mTmpUidStatsArray)
+ + mStatsLayout.getUidTxPackets(mTmpUidStatsArray);
+ power += intermediates.activePower * packets / totalPackets;
+ }
+
+ if (intermediates.basicScanDuration != 0) {
+ long scanDuration = mStatsLayout.getUidScanTime(mTmpUidStatsArray);
+ power += intermediates.basicScanPower * scanDuration
+ / intermediates.basicScanDuration;
+ }
+
+ if (intermediates.batchedScanDuration != 0) {
+ long batchedScanDuration = mStatsLayout.getUidBatchedScanTime(
+ mTmpUidStatsArray);
+ power += intermediates.batchedScanPower * batchedScanDuration
+ / intermediates.batchedScanDuration;
+ }
+ }
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
+
+ if (DEBUG) {
+ Slog.d(TAG, "UID: " + uid
+ + " states: " + Arrays.toString(proportionalEstimate.stateValues)
+ + " stats: " + Arrays.toString(mTmpUidStatsArray)
+ + " rx: " + mStatsLayout.getUidRxPackets(mTmpUidStatsArray)
+ + " rx-power: " + intermediates.rxPower
+ + " rx-packets: " + intermediates.rxPackets
+ + " tx: " + mStatsLayout.getUidTxPackets(mTmpUidStatsArray)
+ + " tx-power: " + intermediates.txPower
+ + " tx-packets: " + intermediates.txPackets
+ + " power: " + power);
+ }
+ }
+ }
+
+ @Override
+ String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ unpackPowerStatsDescriptor(descriptor);
+ if (mHasWifiPowerController) {
+ return "rx: " + mStatsLayout.getDeviceRxTime(stats)
+ + " tx: " + mStatsLayout.getDeviceTxTime(stats)
+ + " scan: " + mStatsLayout.getDeviceScanTime(stats)
+ + " idle: " + mStatsLayout.getDeviceIdleTime(stats)
+ + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
+ } else {
+ return "active: " + mStatsLayout.getDeviceActiveTime(stats)
+ + " scan: " + mStatsLayout.getDeviceBasicScanTime(stats)
+ + " batched-scan: " + mStatsLayout.getDeviceBatchedScanTime(stats)
+ + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
+ }
+ }
+
+ @Override
+ String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
+ // Unsupported for this power component
+ return null;
+ }
+
+ @Override
+ String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
+ unpackPowerStatsDescriptor(descriptor);
+ return "rx: " + mStatsLayout.getUidRxPackets(stats)
+ + " tx: " + mStatsLayout.getUidTxPackets(stats)
+ + " scan: " + mStatsLayout.getUidScanTime(stats)
+ + " batched-scan: " + mStatsLayout.getUidBatchedScanTime(stats)
+ + " power: " + mStatsLayout.getUidPowerEstimate(stats);
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
new file mode 100644
index 0000000..257a1a6
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
@@ -0,0 +1,592 @@
+/*
+ * 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 android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+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.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.net.NetworkStats;
+import android.net.wifi.WifiManager;
+import android.os.BatteryConsumer;
+import android.os.Handler;
+import android.os.Process;
+import android.os.connectivity.WifiActivityEnergyInfo;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.SparseArray;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerProfile;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
+public class WifiPowerStatsProcessorTest {
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ private static final double PRECISION = 0.00001;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int WIFI_ENERGY_CONSUMER_ID = 1;
+ private static final int VOLTAGE_MV = 3500;
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE, 360.0)
+ .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX, 480.0)
+ .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX, 720.0)
+ .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 360.0)
+ .setAveragePower(PowerProfile.POWER_WIFI_SCAN, 480.0)
+ .setAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, 720.0)
+ .initMeasuredEnergyStatsLocked();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private PowerStatsUidResolver mPowerStatsUidResolver;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private Supplier<NetworkStats> mNetworkStatsSupplier;
+ @Mock
+ private WifiManager mWifiManager;
+
+ private static class ScanTimes {
+ public long scanTimeMs;
+ public long batchScanTimeMs;
+ }
+
+ private final SparseArray<ScanTimes> mScanTimes = new SparseArray<>();
+ private long mWifiActiveDuration;
+
+ private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever =
+ new WifiPowerStatsCollector.WifiStatsRetriever() {
+ @Override
+ public void retrieveWifiScanTimes(Callback callback) {
+ for (int i = 0; i < mScanTimes.size(); i++) {
+ int uid = mScanTimes.keyAt(i);
+ ScanTimes scanTimes = mScanTimes.valueAt(i);
+ callback.onWifiScanTime(uid, scanTimes.scanTimeMs, scanTimes.batchScanTimeMs);
+ }
+ }
+
+ @Override
+ public long getWifiActiveDuration() {
+ return mWifiActiveDuration;
+ }
+ };
+
+ private final WifiPowerStatsCollector.Injector mInjector =
+ new WifiPowerStatsCollector.Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mPowerStatsUidResolver;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+
+ @Override
+ public Supplier<NetworkStats> getWifiNetworkStatsSupplier() {
+ return mNetworkStatsSupplier;
+ }
+
+ @Override
+ public WifiManager getWifiManager() {
+ return mWifiManager;
+ }
+
+ @Override
+ public WifiPowerStatsCollector.WifiStatsRetriever getWifiStatsRetriever() {
+ return mWifiStatsRetriever;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
+ when(mPowerStatsUidResolver.mapUid(anyInt()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ }
+
+ @Test
+ public void powerProfileModel_powerController() {
+ when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true);
+
+ // No power monitoring hardware
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
+ .thenReturn(new int[0]);
+
+ WifiPowerStatsProcessor processor =
+ new WifiPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
+
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ collector.setEnabled(true);
+
+ // Initial empty WifiActivityEnergyInfo.
+ mockWifiActivityEnergyInfo(new WifiActivityEnergyInfo(0L,
+ WifiActivityEnergyInfo.STACK_STATE_INVALID, 0L, 0L, 0L, 0L));
+
+ // Establish a baseline
+ aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ 5000);
+
+ // Note application network activity
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("wifi", APP_UID1, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+ mockNetworkStatsEntry("wifi", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+ when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+ mockWifiScanTimes(APP_UID1, 300, 400);
+ mockWifiScanTimes(APP_UID2, 100, 200);
+
+ mockWifiActivityEnergyInfo(new WifiActivityEnergyInfo(10000,
+ WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 2000, 3000, 100, 600));
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
+
+ processor.finish(aggregatedStats);
+
+ WifiPowerStatsLayout statsLayout =
+ new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
+
+ // RX power = 'rx-duration * PowerProfile[wifi.controller.rx]`
+ // RX power = 3000 * 480 = 1440000 mA-ms = 0.4 mAh
+ // TX power = 'tx-duration * PowerProfile[wifi.controller.tx]`
+ // TX power = 2000 * 720 = 1440000 mA-ms = 0.4 mAh
+ // Scan power = 'scan-duration * PowerProfile[wifi.scan]`
+ // Scan power = 100 * 480 = 48000 mA-ms = 0.013333 mAh
+ // Idle power = 'idle-duration * PowerProfile[wifi.idle]`
+ // Idle power = 600 * 360 = 216000 mA-ms = 0.06 mAh
+ // Total power = RX + TX + Scan + Idle = 0.873333
+ // Screen-on - 25%
+ // Screen-off - 75%
+ double expectedPower = 0.873333;
+ long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength];
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.25);
+
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.75);
+
+ // UID1 =
+ // (1500 / 2000) * 0.4 // rx
+ // + (300 / 400) * 0.4 // tx
+ // + (700 / 1000) * 0.013333 // scan (basic + batched)
+ // = 0.609333 mAh
+ double expectedPower1 = 0.609333;
+ long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength];
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.5);
+
+ // UID2 =
+ // (500 / 2000) * 0.4 // rx
+ // + (100 / 400) * 0.4 // tx
+ // + (300 / 1000) * 0.013333 // scan (basic + batched)
+ // = 0.204 mAh
+ double expectedPower2 = 0.204;
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 0.75);
+ }
+
+ @Test
+ public void consumedEnergyModel_powerController() {
+ when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true);
+
+ // PowerStats hardware is available
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
+ .thenReturn(new int[] {WIFI_ENERGY_CONSUMER_ID});
+
+ WifiPowerStatsProcessor processor =
+ new WifiPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
+
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ collector.setEnabled(true);
+
+ // Initial empty WifiActivityEnergyInfo.
+ mockWifiActivityEnergyInfo(new WifiActivityEnergyInfo(0L,
+ WifiActivityEnergyInfo.STACK_STATE_INVALID, 0L, 0L, 0L, 0L));
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{WIFI_ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{0});
+
+ // Establish a baseline
+ aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ 5000);
+
+ // Note application network activity
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("wifi", APP_UID1, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+ mockNetworkStatsEntry("wifi", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+ when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+ mockWifiScanTimes(APP_UID1, 300, 400);
+ mockWifiScanTimes(APP_UID2, 100, 200);
+
+ mockWifiActivityEnergyInfo(new WifiActivityEnergyInfo(10000,
+ WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 2000, 3000, 100, 600));
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ // 10 mAh represented as microWattSeconds
+ long energyUws = 10 * 3600 * VOLTAGE_MV;
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{WIFI_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+
+ aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
+
+ processor.finish(aggregatedStats);
+
+ WifiPowerStatsLayout statsLayout =
+ new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
+
+ // All estimates are computed as in the #powerProfileModel_powerController test,
+ // except they are all scaled by the same ratio to ensure that the total estimated
+ // energy is equal to the measured energy
+ double expectedPower = 10;
+ long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength];
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.25);
+
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.75);
+
+ // UID1
+ // 0.609333 // power profile model estimate
+ // 0.873333 // power profile model estimate for total power
+ // 10 // total consumed energy
+ // = 0.609333 * (10 / 0.873333) = 6.9771
+ double expectedPower1 = 6.9771;
+ long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength];
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.5);
+
+ // UID2
+ // 0.204 // power profile model estimate
+ // 0.873333 // power profile model estimate for total power
+ // 10 // total consumed energy
+ // = 0.204 * (10 / 0.873333) = 2.33588
+ double expectedPower2 = 2.33588;
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 0.75);
+ }
+
+ @Test
+ public void powerProfileModel_noPowerController() {
+ when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(false);
+
+ // No power monitoring hardware
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
+ .thenReturn(new int[0]);
+
+ WifiPowerStatsProcessor processor =
+ new WifiPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
+
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ collector.setEnabled(true);
+
+ // Establish a baseline
+ aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ 5000);
+
+ // Note application network activity
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("wifi", APP_UID1, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+ mockNetworkStatsEntry("wifi", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+ when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+ mScanTimes.clear();
+ mWifiActiveDuration = 8000;
+ mockWifiScanTimes(APP_UID1, 300, 400);
+ mockWifiScanTimes(APP_UID2, 100, 200);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
+
+ processor.finish(aggregatedStats);
+
+ WifiPowerStatsLayout statsLayout =
+ new WifiPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
+
+ // Total active power = 'active-duration * PowerProfile[wifi.on]`
+ // active = 8000 * 360 = 2880000 mA-ms = 0.8 mAh
+ // UID1 rxPackets + txPackets = 1800
+ // UID2 rxPackets + txPackets = 600
+ // Total rx+tx packets = 2400
+ // Total scan power = `scan-duration * PowerProfile[wifi.scan]`
+ // scan = (100 + 300) * 480 = 192000 mA-ms = 0.05333 mAh
+ // Total batch scan power = `(200 + 400) * PowerProfile[wifi.batchedscan]`
+ // bscan = (200 + 400) * 720 = 432000 mA-ms = 0.12 mAh
+ //
+ // Expected power = active + scan + bscan = 0.97333
+ double expectedPower = 0.97333;
+ long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength];
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.25);
+
+ aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+ assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+ .isWithin(PRECISION).of(expectedPower * 0.75);
+
+ // UID1 =
+ // (1800 / 2400) * 0.8 // active
+ // + (300 / 400) * 0.05333 // scan
+ // + (400 / 600) * 0.12 // batched scan
+ // = 0.72 mAh
+ double expectedPower1 = 0.72;
+ long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength];
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID1,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower1 * 0.5);
+
+ // UID2 =
+ // (600 / 2400) * 0.8 // active
+ // + (100 / 400) * 0.05333 // scan
+ // + (200 / 600) * 0.12 // batched scan
+ // = 0.253333 mAh
+ double expectedPower2 = 0.25333;
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 0.25);
+
+ aggregatedStats.getUidStats(uidStats, APP_UID2,
+ states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+ assertThat(statsLayout.getUidPowerEstimate(uidStats))
+ .isWithin(PRECISION).of(expectedPower2 * 0.75);
+ }
+
+ private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+ WifiPowerStatsProcessor processor) {
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_WIFI)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ PowerComponentAggregatedPowerStats aggregatedStats =
+ new PowerComponentAggregatedPowerStats(
+ new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ return aggregatedStats;
+ }
+
+ private int[] states(int... states) {
+ return states;
+ }
+
+ private void mockWifiActivityEnergyInfo(WifiActivityEnergyInfo waei) {
+ doAnswer(invocation -> {
+ WifiManager.OnWifiActivityEnergyInfoListener
+ listener = invocation.getArgument(1);
+ listener.onWifiActivityEnergyInfo(waei);
+ return null;
+ }).when(mWifiManager).getWifiActivityEnergyInfoAsync(any(), any());
+ }
+
+ private NetworkStats mockNetworkStats(int elapsedTime, int initialSize,
+ NetworkStats.Entry... entries) {
+ NetworkStats stats;
+ if (RavenwoodRule.isOnRavenwood()) {
+ stats = mock(NetworkStats.class);
+ when(stats.iterator()).thenAnswer(inv -> List.of(entries).iterator());
+ } else {
+ stats = new NetworkStats(elapsedTime, initialSize);
+ for (NetworkStats.Entry entry : entries) {
+ stats = stats.addEntry(entry);
+ }
+ }
+ return stats;
+ }
+
+ private static NetworkStats.Entry mockNetworkStatsEntry(@Nullable String iface, int uid,
+ int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes,
+ long rxPackets, long txBytes, long txPackets, long operations) {
+ if (RavenwoodRule.isOnRavenwood()) {
+ NetworkStats.Entry entry = mock(NetworkStats.Entry.class);
+ when(entry.getUid()).thenReturn(uid);
+ when(entry.getMetered()).thenReturn(metered);
+ when(entry.getRoaming()).thenReturn(roaming);
+ when(entry.getDefaultNetwork()).thenReturn(defaultNetwork);
+ when(entry.getRxBytes()).thenReturn(rxBytes);
+ when(entry.getRxPackets()).thenReturn(rxPackets);
+ when(entry.getTxBytes()).thenReturn(txBytes);
+ when(entry.getTxPackets()).thenReturn(txPackets);
+ when(entry.getOperations()).thenReturn(operations);
+ return entry;
+ } else {
+ return new NetworkStats.Entry(iface, uid, set, tag, metered,
+ roaming, defaultNetwork, rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+ }
+
+ private void mockWifiScanTimes(int uid, long scanTimeMs, long batchScanTimeMs) {
+ ScanTimes scanTimes = new ScanTimes();
+ scanTimes.scanTimeMs = scanTimeMs;
+ scanTimes.batchScanTimeMs = batchScanTimeMs;
+ mScanTimes.put(uid, scanTimes);
+ }
+}