diff --git a/core/tests/powertests/PowerStatsLoadTests/Android.bp b/core/tests/powertests/PowerStatsLoadTests/Android.bp
new file mode 100644
index 0000000000000..66c91adc6540b
--- /dev/null
+++ b/core/tests/powertests/PowerStatsLoadTests/Android.bp
@@ -0,0 +1,13 @@
+android_test {
+ name: "PowerStatsLoadTests",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "junit",
+ ],
+ libs: ["android.test.runner"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
new file mode 100644
index 0000000000000..b1c2a639aff4c
--- /dev/null
+++ b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java
new file mode 100644
index 0000000000000..ca2942647f08a
--- /dev/null
+++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.core.powerstatsloadtests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class ConnectivitySetupRule implements TestRule {
+
+ private final boolean mWifiEnabled;
+ private final ConnectivityManager mConnectivityManager;
+ private final WifiManager mWifiManager;
+ private boolean mInitialWifiState;
+
+ public ConnectivitySetupRule(boolean wifiEnabled) {
+ mWifiEnabled = wifiEnabled;
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Context context = instrumentation.getContext();
+ mConnectivityManager = context.getSystemService(ConnectivityManager.class);
+ mWifiManager = context.getSystemService(WifiManager.class);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ mInitialWifiState = isWiFiConnected();
+ setWiFiState(mWifiEnabled);
+ base.evaluate();
+ } finally {
+ setWiFiState(mInitialWifiState);
+ }
+ }
+ };
+ }
+
+ private void setWiFiState(final boolean enable) throws InterruptedException {
+ boolean wiFiConnected = isWiFiConnected();
+ if (enable == wiFiConnected) {
+ return;
+ }
+
+ NetworkTracker tracker = new NetworkTracker(!mWifiEnabled);
+ mConnectivityManager.registerNetworkCallback(
+ new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED).build(),
+ tracker);
+
+ if (enable) {
+ SystemUtil.runShellCommand("svc wifi enable");
+ //noinspection deprecation
+ SystemUtil.runWithShellPermissionIdentity(mWifiManager::reconnect,
+ android.Manifest.permission.NETWORK_SETTINGS);
+ } else {
+ SystemUtil.runShellCommand("svc wifi disable");
+ }
+
+ tracker.waitForExpectedState();
+
+ assertEquals("Wifi must be " + (enable ? "connected to" : "disconnected from")
+ + " an access point for this test.", enable, isWiFiConnected());
+
+ mConnectivityManager.unregisterNetworkCallback(tracker);
+ }
+
+ private boolean isWiFiConnected() {
+ return mWifiManager.isWifiEnabled() && mConnectivityManager.getActiveNetwork() != null
+ && !mConnectivityManager.isActiveNetworkMetered();
+ }
+
+ private class NetworkTracker extends ConnectivityManager.NetworkCallback {
+ private static final int MSG_CHECK_ACTIVE_NETWORK = 1;
+
+ private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
+
+ private final boolean mExpectedMetered;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_CHECK_ACTIVE_NETWORK) {
+ checkActiveNetwork();
+ }
+ }
+ };
+
+ private NetworkTracker(boolean expectedMetered) {
+ mExpectedMetered = expectedMetered;
+ }
+
+ @Override
+ public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+ LinkProperties linkProperties, boolean blocked) {
+ checkActiveNetwork();
+ }
+
+ @Override
+ public void onLost(Network network) {
+ checkActiveNetwork();
+ }
+
+ boolean waitForExpectedState() throws InterruptedException {
+ checkActiveNetwork();
+ return mReceiveLatch.await(60, TimeUnit.SECONDS);
+ }
+
+ private void checkActiveNetwork() {
+ if (mReceiveLatch.getCount() == 0) {
+ return;
+ }
+
+ if (mConnectivityManager.getActiveNetwork() != null
+ && mConnectivityManager.isActiveNetworkMetered() == mExpectedMetered) {
+ mReceiveLatch.countDown();
+ } else {
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000);
+ }
+ }
+ }
+}
diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java
new file mode 100644
index 0000000000000..88cb719add60d
--- /dev/null
+++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.core.powerstatsloadtests;
+
+import android.os.Process;
+
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PowerMetrics {
+ private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
+ private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
+ private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
+ private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
+ PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
+
+ enum MetricKind {
+ POWER,
+ DURATION,
+ }
+
+ public static final String METRIC_APP_POWER = "appPower";
+ public static final String METRIC_APP_POWER_EXCLUDE_SYSTEM_FROM_TOTAL = "appPowerExcludeSystem";
+ public static final String METRIC_APP_POWER_EXCLUDE_SMEARED = "appPowerExcludeSmeared";
+ public static final String METRIC_SCREEN_POWER = "screenPower";
+ public static final String METRIC_WIFI_POWER = "wifiPower";
+ public static final String METRIC_SYSTEM_SERVICE_CPU_POWER = "systemService";
+ public static final String METRIC_OTHER_POWER = "otherPower";
+ public static final String METRIC_CPU_POWER = "cpuPower";
+ public static final String METRIC_RAM_POWER = "ramPower";
+ public static final String METRIC_WAKELOCK_POWER = "wakelockPower";
+ public static final String METRIC_MOBILE_RADIO_POWER = "mobileRadioPower";
+ public static final String METRIC_BLUETOOTH_POWER = "bluetoothPower";
+ public static final String METRIC_GPS_POWER = "gpsPower";
+ public static final String METRIC_CAMERA_POWER = "cameraPower";
+ public static final String METRIC_FLASHLIGHT_POWER = "flashlightPower";
+ public static final String METRIC_SENSORS_POWER = "sensorsPower";
+ public static final String METRIC_AUDIO_POWER = "audioPower";
+ public static final String METRIC_VIDEO_POWER = "videoPower";
+ public static final String METRIC_CPU_TIME = "cpuTime";
+ public static final String METRIC_CPU_FOREGROUND_TIME = "cpuForegroundTime";
+ public static final String METRIC_WAKELOCK_TIME = "wakelockTime";
+ public static final String METRIC_WIFI_RUNNING_TIME = "wifiRunningTime";
+ public static final String METRIC_BLUETOOTH_RUNNING_TIME = "bluetoothRunningTime";
+ public static final String METRIC_GPS_TIME = "gpsTime";
+ public static final String METRIC_CAMERA_TIME = "cameraTime";
+ public static final String METRIC_FLASHLIGHT_TIME = "flashlightTime";
+ public static final String METRIC_AUDIO_TIME = "audioTime";
+ public static final String METRIC_VIDEO_TIME = "videoTime";
+
+ public static class Metric {
+ public String metricType;
+ public MetricKind metricKind;
+ public String title;
+ public double value;
+ public double total;
+ }
+
+ private final double mMinDrainedPower;
+ private final double mMaxDrainedPower;
+
+ private List mMetrics = new ArrayList<>();
+
+ public PowerMetrics(BatteryStatsHelper batteryStatsHelper, int uid) {
+ mMinDrainedPower = batteryStatsHelper.getMinDrainedPower();
+ mMaxDrainedPower = batteryStatsHelper.getMaxDrainedPower();
+
+ List usageList = batteryStatsHelper.getUsageList();
+
+ double totalPowerMah = 0;
+ double totalSmearedPowerMah = 0;
+ double totalPowerExcludeSystemMah = 0;
+ double totalScreenPower = 0;
+ double totalProportionalSmearMah = 0;
+ double totalCpuPowerMah = 0;
+ double totalSystemServiceCpuPowerMah = 0;
+ double totalUsagePowerMah = 0;
+ double totalWakeLockPowerMah = 0;
+ double totalMobileRadioPowerMah = 0;
+ double totalWifiPowerMah = 0;
+ double totalBluetoothPowerMah = 0;
+ double totalGpsPowerMah = 0;
+ double totalCameraPowerMah = 0;
+ double totalFlashlightPowerMah = 0;
+ double totalSensorPowerMah = 0;
+ double totalAudioPowerMah = 0;
+ double totalVideoPowerMah = 0;
+
+ long totalCpuTimeMs = 0;
+ long totalCpuFgTimeMs = 0;
+ long totalWakeLockTimeMs = 0;
+ long totalWifiRunningTimeMs = 0;
+ long totalBluetoothRunningTimeMs = 0;
+ long totalGpsTimeMs = 0;
+ long totalCameraTimeMs = 0;
+ long totalFlashlightTimeMs = 0;
+ long totalAudioTimeMs = 0;
+ long totalVideoTimeMs = 0;
+
+ BatterySipper uidSipper = null;
+ for (BatterySipper sipper : usageList) {
+ if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
+ totalScreenPower = sipper.sumPower();
+ }
+
+ if (isHiddenDrainType(sipper.drainType)) {
+ continue;
+ }
+
+ if (sipper.drainType == BatterySipper.DrainType.APP && sipper.getUid() == uid) {
+ uidSipper = sipper;
+ }
+
+ totalPowerMah += sipper.sumPower();
+ totalSmearedPowerMah += sipper.totalSmearedPowerMah;
+ totalProportionalSmearMah += sipper.proportionalSmearMah;
+
+ if (!isSystemSipper(sipper)) {
+ totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah;
+ }
+
+ totalCpuPowerMah += sipper.cpuPowerMah;
+ totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah;
+ totalUsagePowerMah += sipper.usagePowerMah;
+ totalWakeLockPowerMah += sipper.wakeLockPowerMah;
+ totalMobileRadioPowerMah += sipper.mobileRadioPowerMah;
+ totalWifiPowerMah += sipper.wifiPowerMah;
+ totalBluetoothPowerMah += sipper.bluetoothPowerMah;
+ totalGpsPowerMah += sipper.gpsPowerMah;
+ totalCameraPowerMah += sipper.cameraPowerMah;
+ totalFlashlightPowerMah += sipper.flashlightPowerMah;
+ totalSensorPowerMah += sipper.sensorPowerMah;
+ totalAudioPowerMah += sipper.audioPowerMah;
+ totalVideoPowerMah += sipper.videoPowerMah;
+
+ totalCpuTimeMs += sipper.cpuTimeMs;
+ totalCpuFgTimeMs += sipper.cpuFgTimeMs;
+ totalWakeLockTimeMs += sipper.wakeLockTimeMs;
+ totalWifiRunningTimeMs += sipper.wifiRunningTimeMs;
+ totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs;
+ totalGpsTimeMs += sipper.gpsTimeMs;
+ totalCameraTimeMs += sipper.cameraTimeMs;
+ totalFlashlightTimeMs += sipper.flashlightTimeMs;
+ totalAudioTimeMs += sipper.audioTimeMs;
+ totalVideoTimeMs += sipper.videoTimeMs;
+ }
+
+ if (uidSipper == null) {
+ return;
+ }
+
+ addMetric(METRIC_APP_POWER, MetricKind.POWER, "Total power",
+ uidSipper.totalSmearedPowerMah, totalSmearedPowerMah);
+ addMetric(METRIC_APP_POWER_EXCLUDE_SYSTEM_FROM_TOTAL, MetricKind.POWER,
+ "Total power excluding system",
+ uidSipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
+ addMetric(METRIC_SCREEN_POWER, MetricKind.POWER, "Screen, smeared",
+ uidSipper.screenPowerMah, totalScreenPower);
+ addMetric(METRIC_OTHER_POWER, MetricKind.POWER, "Other, smeared",
+ uidSipper.proportionalSmearMah, totalProportionalSmearMah);
+ addMetric(METRIC_APP_POWER_EXCLUDE_SMEARED, MetricKind.POWER, "Excluding smeared",
+ uidSipper.totalPowerMah, totalPowerMah);
+ addMetric(METRIC_CPU_POWER, MetricKind.POWER, "CPU",
+ uidSipper.cpuPowerMah, totalCpuPowerMah);
+ addMetric(METRIC_SYSTEM_SERVICE_CPU_POWER, MetricKind.POWER, "System services",
+ uidSipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
+ addMetric(METRIC_RAM_POWER, MetricKind.POWER, "RAM",
+ uidSipper.usagePowerMah, totalUsagePowerMah);
+ addMetric(METRIC_WAKELOCK_POWER, MetricKind.POWER, "Wake lock",
+ uidSipper.wakeLockPowerMah, totalWakeLockPowerMah);
+ addMetric(METRIC_MOBILE_RADIO_POWER, MetricKind.POWER, "Mobile radio",
+ uidSipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
+ addMetric(METRIC_WIFI_POWER, MetricKind.POWER, "WiFi",
+ uidSipper.wifiPowerMah, totalWifiPowerMah);
+ addMetric(METRIC_BLUETOOTH_POWER, MetricKind.POWER, "Bluetooth",
+ uidSipper.bluetoothPowerMah, totalBluetoothPowerMah);
+ addMetric(METRIC_GPS_POWER, MetricKind.POWER, "GPS",
+ uidSipper.gpsPowerMah, totalGpsPowerMah);
+ addMetric(METRIC_CAMERA_POWER, MetricKind.POWER, "Camera",
+ uidSipper.cameraPowerMah, totalCameraPowerMah);
+ addMetric(METRIC_FLASHLIGHT_POWER, MetricKind.POWER, "Flashlight",
+ uidSipper.flashlightPowerMah, totalFlashlightPowerMah);
+ addMetric(METRIC_SENSORS_POWER, MetricKind.POWER, "Sensors",
+ uidSipper.sensorPowerMah, totalSensorPowerMah);
+ addMetric(METRIC_AUDIO_POWER, MetricKind.POWER, "Audio",
+ uidSipper.audioPowerMah, totalAudioPowerMah);
+ addMetric(METRIC_VIDEO_POWER, MetricKind.POWER, "Video",
+ uidSipper.videoPowerMah, totalVideoPowerMah);
+
+ addMetric(METRIC_CPU_TIME, MetricKind.DURATION, "CPU time",
+ uidSipper.cpuTimeMs, totalCpuTimeMs);
+ addMetric(METRIC_CPU_FOREGROUND_TIME, MetricKind.DURATION, "CPU foreground time",
+ uidSipper.cpuFgTimeMs, totalCpuFgTimeMs);
+ addMetric(METRIC_WAKELOCK_TIME, MetricKind.DURATION, "Wake lock time",
+ uidSipper.wakeLockTimeMs, totalWakeLockTimeMs);
+ addMetric(METRIC_WIFI_RUNNING_TIME, MetricKind.DURATION, "WiFi running time",
+ uidSipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
+ addMetric(METRIC_BLUETOOTH_RUNNING_TIME, MetricKind.DURATION, "Bluetooth time",
+ uidSipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
+ addMetric(METRIC_GPS_TIME, MetricKind.DURATION, "GPS time",
+ uidSipper.gpsTimeMs, totalGpsTimeMs);
+ addMetric(METRIC_CAMERA_TIME, MetricKind.DURATION, "Camera time",
+ uidSipper.cameraTimeMs, totalCameraTimeMs);
+ addMetric(METRIC_FLASHLIGHT_TIME, MetricKind.DURATION, "Flashlight time",
+ uidSipper.flashlightTimeMs, totalFlashlightTimeMs);
+ addMetric(METRIC_AUDIO_TIME, MetricKind.DURATION, "Audio time",
+ uidSipper.audioTimeMs, totalAudioTimeMs);
+ addMetric(METRIC_VIDEO_TIME, MetricKind.DURATION, "Video time",
+ uidSipper.videoTimeMs, totalVideoTimeMs);
+ }
+
+ public List getMetrics() {
+ return mMetrics;
+ }
+
+ public double getMinDrainedPower() {
+ return mMinDrainedPower;
+ }
+
+ public double getMaxDrainedPower() {
+ return mMaxDrainedPower;
+ }
+
+ protected boolean isHiddenDrainType(BatterySipper.DrainType drainType) {
+ return drainType == BatterySipper.DrainType.IDLE
+ || drainType == BatterySipper.DrainType.CELL
+ || drainType == BatterySipper.DrainType.SCREEN
+ || drainType == BatterySipper.DrainType.UNACCOUNTED
+ || drainType == BatterySipper.DrainType.OVERCOUNTED
+ || drainType == BatterySipper.DrainType.BLUETOOTH
+ || drainType == BatterySipper.DrainType.WIFI;
+ }
+
+ private boolean isSystemSipper(BatterySipper sipper) {
+ final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
+ if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
+ return true;
+ } else if (sipper.mPackages != null) {
+ for (final String packageName : sipper.mPackages) {
+ for (final String systemPackage : PACKAGES_SYSTEM) {
+ if (systemPackage.equals(packageName)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void addMetric(String metricType, MetricKind metricKind, String title, double amount,
+ double totalAmount) {
+ Metric metric = new Metric();
+ metric.metricType = metricType;
+ metric.metricKind = metricKind;
+ metric.title = title;
+ metric.value = amount;
+ metric.total = totalAmount;
+ mMetrics.add(metric);
+ }
+}
diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
new file mode 100644
index 0000000000000..0cdb404f6aaa0
--- /dev/null
+++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.core.powerstatsloadtests;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.TimeUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.internal.os.LoggingPrintStream;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class PowerMetricsCollector implements TestRule {
+ private final String mTag;
+ private final float mBatteryDrainThresholdPct;
+ private final int mTimeoutMillis;
+
+ private final Context mContext;
+ private final UserManager mUserManager;
+ private final int mUid;
+ private final BatteryStatsHelper mStatsHelper;
+
+ private long mStartTime;
+ private volatile float mInitialBatteryLevel;
+ private volatile float mCurrentBatteryLevel;
+ private int mIterations;
+ private PowerMetrics mInitialPowerMetrics;
+ private PowerMetrics mFinalPowerMetrics;
+ private List mPowerMetricsDelta;
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ disableCharger();
+ try {
+ prepareBatteryLevelMonitor();
+ mStartTime = SystemClock.uptimeMillis();
+ base.evaluate();
+ captureFinalPowerStatsData();
+ } finally {
+ enableCharger();
+ }
+ }
+ };
+ }
+
+ public PowerMetricsCollector(String tag, float batteryDrainThresholdPct, int timeoutMillis) {
+ mTag = tag;
+ mBatteryDrainThresholdPct = batteryDrainThresholdPct;
+ mTimeoutMillis = timeoutMillis;
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = instrumentation.getContext();
+ mUid = Process.myUid();
+ mUserManager = mContext.getSystemService(UserManager.class);
+ mStatsHelper = new BatteryStatsHelper(mContext, false /* collectBatteryBroadcast */);
+ mStatsHelper.create((Bundle) null);
+ }
+
+ private void disableCharger() {
+ // TODO(b/167636754): implement this method once the charger suspension API is available
+ }
+
+ private void enableCharger() {
+ // TODO(b/167636754): implement this method once the charger suspension API is available
+ }
+
+ private PowerMetrics readBatteryStatsData() {
+ mStatsHelper.clearStats();
+ mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
+ mUserManager.getUserProfiles());
+ return new PowerMetrics(mStatsHelper, mUid);
+ }
+
+ protected void prepareBatteryLevelMonitor() {
+ Intent batteryStatus = mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleBatteryStatus(intent);
+ }
+ }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+ handleBatteryStatus(batteryStatus);
+ mInitialBatteryLevel = mCurrentBatteryLevel;
+ }
+
+ protected void handleBatteryStatus(Intent intent) {
+ if (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0) {
+ fail("Device must remain disconnected from the power source "
+ + "for the duration of the test");
+ }
+
+ int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+
+ mCurrentBatteryLevel = level * 100 / (float) scale;
+ Log.i(mTag, "Battery level = " + mCurrentBatteryLevel);
+
+ // We delay tracking until the battery level drops. If the resolution of
+ // battery level is 1%, and the initially reported level is 73, we don't know whether
+ // it's 73.1 or 73.7. Once it drops to 72, we can be confident that the real battery
+ // level it is very close to 72.0 and can start tracking.
+ if (mInitialPowerMetrics == null && mCurrentBatteryLevel < mInitialBatteryLevel) {
+ mInitialBatteryLevel = mCurrentBatteryLevel;
+ mInitialPowerMetrics = readBatteryStatsData();
+ }
+ }
+
+ private void captureFinalPowerStatsData() {
+ if (mFinalPowerMetrics != null) {
+ return;
+ }
+
+ mFinalPowerMetrics = readBatteryStatsData();
+
+ mPowerMetricsDelta = new ArrayList<>();
+ List initialPowerMetrics = mInitialPowerMetrics.getMetrics();
+ List finalPowerMetrics = mFinalPowerMetrics.getMetrics();
+ for (PowerMetrics.Metric initialMetric : initialPowerMetrics) {
+ PowerMetrics.Metric finalMetric = null;
+ for (PowerMetrics.Metric metric : finalPowerMetrics) {
+ if (metric.title.equals(initialMetric.title)) {
+ finalMetric = metric;
+ break;
+ }
+ }
+
+ if (finalMetric != null) {
+ PowerMetrics.Metric delta = new PowerMetrics.Metric();
+ delta.metricType = initialMetric.metricType;
+ delta.metricKind = initialMetric.metricKind;
+ delta.title = initialMetric.title;
+ delta.total = finalMetric.total - initialMetric.total;
+ delta.value = finalMetric.value - initialMetric.value;
+ mPowerMetricsDelta.add(delta);
+ }
+ }
+ }
+
+ /**
+ * Returns false if sufficient data has been accumulated.
+ */
+ public boolean checkpoint() {
+ long elapsedTime = SystemClock.uptimeMillis() - mStartTime;
+ if (elapsedTime >= mTimeoutMillis) {
+ Log.i(mTag, "Timeout reached " + TimeUtils.formatDuration(elapsedTime));
+ captureFinalPowerStatsData();
+ return false;
+ }
+
+ if (mInitialPowerMetrics == null) {
+ return true;
+ }
+
+ if (mInitialBatteryLevel - mCurrentBatteryLevel >= mBatteryDrainThresholdPct) {
+ Log.i(mTag,
+ "Battery drain reached " + (mInitialBatteryLevel - mCurrentBatteryLevel) + "%");
+ captureFinalPowerStatsData();
+ return false;
+ }
+
+ mIterations++;
+ return true;
+ }
+
+
+ public int getIterationCount() {
+ return mIterations;
+ }
+
+ public void dumpMetrics() {
+ dumpMetrics(new LoggingPrintStream() {
+ @Override
+ protected void log(String line) {
+ Log.i(mTag, line);
+ }
+ });
+ }
+
+ public void dumpMetrics(PrintStream out) {
+ List initialPowerMetrics = mInitialPowerMetrics.getMetrics();
+ List finalPowerMetrics = mFinalPowerMetrics.getMetrics();
+
+ out.println("== Power metrics at test start");
+ dumpPowerStatsData(out, initialPowerMetrics);
+
+ out.println("== Power metrics at test end");
+ dumpPowerStatsData(out, finalPowerMetrics);
+
+ out.println("== Power metrics delta");
+ dumpPowerStatsData(out, mPowerMetricsDelta);
+ }
+
+ protected void dumpPowerStatsData(PrintStream out, List metrics) {
+ Locale locale = Locale.getDefault();
+ for (PowerMetrics.Metric metric : metrics) {
+ double proportion = metric.total != 0 ? metric.value * 100 / metric.total : 0;
+ switch (metric.metricKind) {
+ case POWER:
+ out.println(
+ String.format(locale, " %-30s %7.1f mAh %4.1f%%", metric.title,
+ metric.value, proportion));
+ break;
+ case DURATION:
+ out.println(
+ String.format(locale, " %-30s %,7d ms %4.1f%%", metric.title,
+ (long) metric.value, proportion));
+ break;
+ }
+ }
+ }
+
+ public void dumpMetricAsPercentageOfDrainedPower(String metricType) {
+ double minDrainedPower =
+ mFinalPowerMetrics.getMinDrainedPower() - mInitialPowerMetrics.getMinDrainedPower();
+ double maxDrainedPower =
+ mFinalPowerMetrics.getMaxDrainedPower() - mInitialPowerMetrics.getMaxDrainedPower();
+
+ PowerMetrics.Metric metric = getMetric(metricType);
+ double metricDelta = metric.value;
+
+ if (maxDrainedPower - minDrainedPower < 0.1f) {
+ Log.i(mTag, String.format(Locale.getDefault(),
+ "%s power consumed by the test: %.1f of %.1f mAh (%.1f%%)",
+ metric.title, metricDelta, maxDrainedPower,
+ metricDelta / maxDrainedPower * 100));
+ } else {
+ Log.i(mTag, String.format(Locale.getDefault(),
+ "%s power consumed by the test: %.1f of %.1f - %.1f mAh (%.1f%% - %.1f%%)",
+ metric.title, metricDelta, minDrainedPower, maxDrainedPower,
+ metricDelta / minDrainedPower * 100, metricDelta / maxDrainedPower * 100));
+ }
+ }
+
+ public PowerMetrics.Metric getMetric(String metricType) {
+ for (PowerMetrics.Metric metric : mPowerMetricsDelta) {
+ if (metric.metricType.equals(metricType)) {
+ return metric;
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java
new file mode 100644
index 0000000000000..911ccba3ac78f
--- /dev/null
+++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.core.powerstatsloadtests;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class SystemServiceCallLoadTest {
+ private static final String TAG = "SystemServiceCallLoadTest";
+ private static final int TIMEOUT_MILLIS = 60 * 60 * 1000;
+ private static final float BATTERY_DRAIN_THRESHOLD_PCT = 2.99f;
+
+ @Rule
+ public PowerMetricsCollector mPowerMetricsCollector = new PowerMetricsCollector(TAG,
+ BATTERY_DRAIN_THRESHOLD_PCT, TIMEOUT_MILLIS);
+
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setup() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mPackageManager = instrumentation.getContext().getPackageManager();
+ }
+
+ @Test
+ public void test() {
+ while (mPowerMetricsCollector.checkpoint()) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.parse("http://example.com/"), "text/plain");
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
+ assertNotNull(resolveInfo);
+ }
+
+ mPowerMetricsCollector.dumpMetrics();
+
+ Log.i(TAG, "==");
+ Log.i(TAG, "Total system server calls made " + mPowerMetricsCollector.getIterationCount());
+
+ mPowerMetricsCollector.dumpMetricAsPercentageOfDrainedPower(
+ PowerMetrics.METRIC_SYSTEM_SERVICE_CPU_POWER);
+ }
+}
diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java
new file mode 100644
index 0000000000000..90627192946df
--- /dev/null
+++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.core.powerstatsloadtests;
+
+import android.util.Log;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+public class WiFiLoadTest {
+ private static final String TAG = "WiFiLoadTest";
+ private static final String DOWNLOAD_TEST_URL =
+ "https://i.ytimg.com/vi/l5mE3Tpjejs/maxresdefault.jpg";
+
+ private static final int TIMEOUT_MILLIS = 60 * 60 * 1000;
+ private static final float BATTERY_DRAIN_THRESHOLD_PCT = 0.99f;
+
+ @Rule
+ public PowerMetricsCollector mPowerMetricsCollector = new PowerMetricsCollector(TAG,
+ BATTERY_DRAIN_THRESHOLD_PCT, TIMEOUT_MILLIS);
+
+ @Rule
+ public ConnectivitySetupRule mConnectivitySetupRule =
+ new ConnectivitySetupRule(/* WiFi enabled */true);
+
+ @Test
+ public void test() throws IOException {
+ long totalBytesRead = 0;
+ URL url = new URL(DOWNLOAD_TEST_URL);
+ byte[] buffer = new byte[131072]; // Large buffer to minimize CPU usage
+
+ while (mPowerMetricsCollector.checkpoint()) {
+ try (InputStream inputStream = url.openStream()) {
+ while (true) {
+ int count = inputStream.read(buffer);
+ if (count < 0) {
+ break;
+ }
+ totalBytesRead += count;
+ }
+ }
+ }
+
+ mPowerMetricsCollector.dumpMetrics();
+
+ Log.i(TAG, "==");
+ Log.i(TAG, "WiFi running time: " + (long) mPowerMetricsCollector.getMetric(
+ PowerMetrics.METRIC_WIFI_RUNNING_TIME).value);
+ Log.i(TAG, "Total bytes read over WiFi: " + totalBytesRead);
+
+ mPowerMetricsCollector.dumpMetricAsPercentageOfDrainedPower(
+ PowerMetrics.METRIC_WIFI_POWER);
+ }
+}