summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Dmitri Plotnikov <dplotnikov@google.com> 2025-01-10 11:29:34 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-01-10 11:29:34 -0800
commitd953ec98d50eb7c1e56e42faefd10be8ce695c5a (patch)
tree260a2e9de83517308bfb861da96bd1726e10af04
parentec1183fed1c3c0d5311c571304168f1acb6e39d7 (diff)
parentfabf8e4c3d66e1e7de16169b1d21c434e23ef410 (diff)
Merge "Add support for Fine Granularity PowerMonitors" into main
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/java/android/os/IPowerStatsService.aidl2
-rw-r--r--core/java/android/os/PowerMonitorReadings.java52
-rw-r--r--core/java/android/os/health/SystemHealthManager.java3
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsService.java43
-rw-r--r--services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java26
8 files changed, 123 insertions, 11 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 93f311969c1e..16c70174d5c2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11462,6 +11462,12 @@ package android.os {
method @RequiresPermission(android.Manifest.permission.SET_LOW_POWER_STANDBY_PORTS) public void release();
}
+ @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings {
+ method @FlaggedApi("android.permission.flags.fine_power_monitor_permission") public int getGranularity();
+ field @FlaggedApi("android.permission.flags.fine_power_monitor_permission") public static final int GRANULARITY_FINE = 1; // 0x1
+ field @FlaggedApi("android.permission.flags.fine_power_monitor_permission") public static final int GRANULARITY_UNSPECIFIED = 0; // 0x0
+ }
+
@Deprecated public class PowerWhitelistManager {
method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
diff --git a/core/java/android/os/IPowerStatsService.aidl b/core/java/android/os/IPowerStatsService.aidl
index a0c226205460..e0e9497cfb5c 100644
--- a/core/java/android/os/IPowerStatsService.aidl
+++ b/core/java/android/os/IPowerStatsService.aidl
@@ -25,6 +25,8 @@ interface IPowerStatsService {
const String KEY_ENERGY = "energy";
/** @hide */
const String KEY_TIMESTAMPS = "timestamps";
+ /** @hide */
+ const String KEY_GRANULARITY = "granularity";
/** @hide */
const int RESULT_SUCCESS = 0;
diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java
index a0ab066ffb75..85ffc4661df3 100644
--- a/core/java/android/os/PowerMonitorReadings.java
+++ b/core/java/android/os/PowerMonitorReadings.java
@@ -18,8 +18,12 @@ package android.os;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Comparator;
@@ -38,6 +42,37 @@ public final class PowerMonitorReadings {
@NonNull
private final long[] mTimestampsMs;
+ /**
+ * PowerMonitorReadings have the default level of granularity, which may be coarse or fine
+ * as determined by the implementation.
+ * @hide
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_FINE_POWER_MONITOR_PERMISSION)
+ @SystemApi
+ public static final int GRANULARITY_UNSPECIFIED = 0;
+
+ /**
+ * PowerMonitorReadings have a high level of granularity. This level of granularity is
+ * provided to applications that have the
+ * {@link android.Manifest.permission#ACCESS_FINE_POWER_MONITORS} permission.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_FINE_POWER_MONITOR_PERMISSION)
+ @SystemApi
+ public static final int GRANULARITY_FINE = 1;
+
+ /** @hide */
+ @IntDef(prefix = {"GRANULARITY_"}, value = {
+ GRANULARITY_UNSPECIFIED,
+ GRANULARITY_FINE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PowerMonitorGranularity {}
+
+ @PowerMonitorGranularity
+ private final int mGranularity;
+
private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =
Comparator.comparingInt(pm -> pm.index);
@@ -46,10 +81,12 @@ public final class PowerMonitorReadings {
* @hide
*/
public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors,
- @NonNull long[] energyUws, @NonNull long[] timestampsMs) {
+ @NonNull long[] energyUws, @NonNull long[] timestampsMs,
+ @PowerMonitorGranularity int granularity) {
mPowerMonitors = powerMonitors;
mEnergyUws = energyUws;
mTimestampsMs = timestampsMs;
+ mGranularity = granularity;
}
/**
@@ -79,6 +116,19 @@ public final class PowerMonitorReadings {
return 0;
}
+ /**
+ * Returns the granularity level of the results, which refers to the maximum age of the
+ * power monitor readings, {@link #GRANULARITY_FINE} indicating the highest level
+ * of freshness supported by the service implementation.
+ * @hide
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_FINE_POWER_MONITOR_PERMISSION)
+ @SystemApi
+ @PowerMonitorGranularity
+ public int getGranularity() {
+ return mGranularity;
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index a1e9cf25e3e1..febbfca56bfb 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -588,7 +588,8 @@ public class SystemHealthManager {
if (resultCode == IPowerStatsService.RESULT_SUCCESS) {
PowerMonitorReadings result = new PowerMonitorReadings(powerMonitorsArray,
resultData.getLongArray(IPowerStatsService.KEY_ENERGY),
- resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS));
+ resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS),
+ resultData.getInt(IPowerStatsService.KEY_GRANULARITY));
if (executor != null) {
executor.execute(() -> onResult.onResult(result));
} else {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f136e065a405..a30570a4cce5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -264,6 +264,7 @@ applications that come with the platform
<!-- Needed for test only -->
<permission name="android.permission.BATTERY_PREDICTION"/>
<permission name="android.permission.BATTERY_STATS"/>
+ <permission name="android.permission.ACCESS_FINE_POWER_MONITORS" />
<!-- BLUETOOTH_PRIVILEGED is needed for test only -->
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.BIND_APPWIDGET"/>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46bd88fcdc93..4448000324d8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -169,6 +169,7 @@
<!-- Internal permissions granted to the shell. -->
<uses-permission android:name="android.permission.FORCE_BACK" />
<uses-permission android:name="android.permission.BATTERY_STATS" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_POWER_MONITORS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.REPORT_USAGE_STATS" />
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 89fa9b61b745..b723da3c7769 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -19,6 +19,7 @@ package com.android.server.powerstats;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.power.stats.Channel;
import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerResult;
@@ -37,6 +38,7 @@ import android.os.IPowerStatsService;
import android.os.Looper;
import android.os.PowerMonitor;
import android.os.PowerMonitorReadings;
+import android.os.Process;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.power.PowerStatsInternal;
@@ -83,7 +85,8 @@ public class PowerStatsService extends SystemService {
private static final String METER_CACHE_FILENAME = "meterCache";
private static final String MODEL_CACHE_FILENAME = "modelCache";
private static final String RESIDENCY_CACHE_FILENAME = "residencyCache";
- private static final long MAX_POWER_MONITOR_AGE_MILLIS = 30_000;
+ private static final long MAX_POWER_MONITOR_AGE_MILLIS = 20_000;
+ private static final long MAX_FINE_POWER_MONITOR_AGE_MILLIS = 250;
static final String KEY_POWER_MONITOR_API_ENABLED = "power_monitor_api_enabled";
@@ -203,6 +206,11 @@ public class PowerStatsService extends SystemService {
IntervalRandomNoiseGenerator createIntervalRandomNoiseGenerator() {
return new IntervalRandomNoiseGenerator(INTERVAL_RANDOM_NOISE_GENERATION_ALPHA);
}
+
+ boolean checkFinePowerMonitorsPermission(Context context, int callingUid) {
+ return context.checkPermission(android.Manifest.permission.ACCESS_FINE_POWER_MONITORS,
+ Process.INVALID_PID, callingUid) == PackageManager.PERMISSION_GRANTED;
+ }
}
private final IBinder mService = new IPowerStatsService.Stub() {
@@ -571,6 +579,7 @@ public class PowerStatsService extends SystemService {
private boolean mPowerMonitorApiEnabled = true;
private volatile PowerMonitor[] mPowerMonitors;
private PowerMonitorState[] mPowerMonitorStates;
+ private PowerMonitorState[] mFinePowerMonitorStates;
private IntervalRandomNoiseGenerator mIntervalRandomNoiseGenerator;
private void setPowerMonitorApiEnabled(boolean powerMonitorApiEnabled) {
@@ -578,6 +587,7 @@ public class PowerStatsService extends SystemService {
mPowerMonitorApiEnabled = powerMonitorApiEnabled;
mPowerMonitors = null;
mPowerMonitorStates = null;
+ mFinePowerMonitorStates = null;
}
}
@@ -598,6 +608,7 @@ public class PowerStatsService extends SystemService {
if (!mPowerMonitorApiEnabled) {
mPowerMonitors = new PowerMonitor[0];
mPowerMonitorStates = new PowerMonitorState[0];
+ mFinePowerMonitorStates = new PowerMonitorState[0];
return;
}
@@ -628,6 +639,7 @@ public class PowerStatsService extends SystemService {
}
mPowerMonitors = monitors.toArray(new PowerMonitor[monitors.size()]);
mPowerMonitorStates = states.toArray(new PowerMonitorState[monitors.size()]);
+ mFinePowerMonitorStates = states.toArray(new PowerMonitorState[monitors.size()]);
}
}
@@ -710,24 +722,38 @@ public class PowerStatsService extends SystemService {
ResultReceiver resultReceiver, int callingUid) {
ensurePowerMonitors();
+ @PowerMonitorReadings.PowerMonitorGranularity int granularity =
+ mInjector.checkFinePowerMonitorsPermission(mContext, callingUid)
+ ? PowerMonitorReadings.GRANULARITY_FINE
+ : PowerMonitorReadings.GRANULARITY_UNSPECIFIED;
+
+ PowerMonitorState[] allPowerMonitorStates;
+ long maxAge;
+ if (granularity == PowerMonitorReadings.GRANULARITY_FINE) {
+ allPowerMonitorStates = mFinePowerMonitorStates;
+ maxAge = MAX_FINE_POWER_MONITOR_AGE_MILLIS;
+ } else {
+ allPowerMonitorStates = mPowerMonitorStates;
+ maxAge = MAX_POWER_MONITOR_AGE_MILLIS;
+ }
+
long earliestTimestamp = Long.MAX_VALUE;
PowerMonitorState[] powerMonitorStates = new PowerMonitorState[powerMonitorIndices.length];
for (int i = 0; i < powerMonitorIndices.length; i++) {
int index = powerMonitorIndices[i];
- if (index < 0 || index >= mPowerMonitorStates.length) {
+ if (index < 0 || index >= allPowerMonitorStates.length) {
resultReceiver.send(IPowerStatsService.RESULT_UNSUPPORTED_POWER_MONITOR, null);
return;
}
- powerMonitorStates[i] = mPowerMonitorStates[index];
- if (mPowerMonitorStates[index] != null
- && mPowerMonitorStates[index].timestampMs < earliestTimestamp) {
- earliestTimestamp = mPowerMonitorStates[index].timestampMs;
+ powerMonitorStates[i] = allPowerMonitorStates[index];
+ if (allPowerMonitorStates[index] != null
+ && allPowerMonitorStates[index].timestampMs < earliestTimestamp) {
+ earliestTimestamp = allPowerMonitorStates[index].timestampMs;
}
}
- if (earliestTimestamp == 0
- || mClock.elapsedRealtime() - earliestTimestamp > MAX_POWER_MONITOR_AGE_MILLIS) {
+ if (earliestTimestamp == 0 || mClock.elapsedRealtime() - earliestTimestamp > maxAge) {
updateEnergyConsumers(powerMonitorStates);
updateEnergyMeasurements(powerMonitorStates);
mIntervalRandomNoiseGenerator.refresh();
@@ -765,6 +791,7 @@ public class PowerStatsService extends SystemService {
Bundle result = new Bundle();
result.putLongArray(IPowerStatsService.KEY_ENERGY, energy);
result.putLongArray(IPowerStatsService.KEY_TIMESTAMPS, timestamps);
+ result.putInt(IPowerStatsService.KEY_GRANULARITY, granularity);
resultReceiver.send(IPowerStatsService.RESULT_SUCCESS, result);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 115cdf6cee63..e654b40af1b5 100644
--- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -37,11 +37,13 @@ import android.os.Bundle;
import android.os.IPowerStatsService;
import android.os.Looper;
import android.os.PowerMonitor;
+import android.os.PowerMonitorReadings;
import android.os.ResultReceiver;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
+import android.util.IntArray;
import androidx.test.InstrumentationRegistry;
@@ -129,6 +131,8 @@ public class PowerStatsServiceTest {
}
}
+ private final IntArray mFinePowerMonitorsPermissionGranted = new IntArray();
+
private final PowerStatsService.Injector mInjector = new PowerStatsService.Injector() {
@Override
@@ -220,6 +224,11 @@ public class PowerStatsServiceTest {
IntervalRandomNoiseGenerator createIntervalRandomNoiseGenerator() {
return mMockNoiseGenerator;
}
+
+ @Override
+ boolean checkFinePowerMonitorsPermission(Context context, int callingUid) {
+ return mFinePowerMonitorsPermissionGranted.contains(callingUid);
+ }
};
public static final class TestPowerStatsHALWrapper implements IPowerStatsHALWrapper {
@@ -1109,6 +1118,8 @@ public class PowerStatsServiceTest {
public int resultCode;
public long[] energyUws;
public long[] timestamps;
+ @PowerMonitorReadings.PowerMonitorGranularity
+ public int granularity;
GetPowerMonitorsResult() {
super(null);
@@ -1120,12 +1131,23 @@ public class PowerStatsServiceTest {
if (resultData != null) {
energyUws = resultData.getLongArray(IPowerStatsService.KEY_ENERGY);
timestamps = resultData.getLongArray(IPowerStatsService.KEY_TIMESTAMPS);
+ granularity = resultData.getInt(IPowerStatsService.KEY_GRANULARITY);
}
}
}
@Test
public void getPowerMonitors() {
+ testGetPowerMonitors(PowerMonitorReadings.GRANULARITY_UNSPECIFIED);
+ }
+
+ @Test
+ public void getPowerMonitors_finePowerMonitorPermissionGranted() {
+ mFinePowerMonitorsPermissionGranted.add(APP_UID);
+ testGetPowerMonitors(PowerMonitorReadings.GRANULARITY_FINE);
+ }
+
+ private void testGetPowerMonitors(int expectedGranularity) {
mMockClock.realtime = 10 * 60_000;
mMockNoiseGenerator.reseed(314);
@@ -1161,6 +1183,7 @@ public class PowerStatsServiceTest {
assertThat(result.energyUws).isEqualTo(new long[]{42, 142, 314, 514});
assertThat(result.timestamps).isEqualTo(new long[]{600_000, 600_100, 600_000, 600_200});
+ assertThat(result.granularity).isEqualTo(expectedGranularity);
// Test caching/throttling
mMockClock.realtime += 1;
@@ -1180,6 +1203,7 @@ public class PowerStatsServiceTest {
assertThat(result.energyUws).isEqualTo(new long[]{42, 314});
assertThat(result.timestamps).isEqualTo(new long[]{600_000, 600_000});
+ assertThat(result.granularity).isEqualTo(expectedGranularity);
mMockClock.realtime += 10 * 60000;
@@ -1189,6 +1213,7 @@ public class PowerStatsServiceTest {
// This time, random noise is added
assertThat(result.energyUws).isEqualTo(new long[]{298, 399});
assertThat(result.timestamps).isEqualTo(new long[]{600_301, 600_401});
+ assertThat(result.granularity).isEqualTo(expectedGranularity);
}
@Test
@@ -1234,7 +1259,6 @@ public class PowerStatsServiceTest {
assertThrows(NullPointerException.class, () -> iPowerStatsService.getPowerMonitorReadings(
new int[] {0}, null));
}
-
@Test
public void getEnergyConsumedAsync_halException() {
mPowerStatsHALWrapper.exception = new IllegalArgumentException();