diff options
| author | 2025-01-10 11:29:34 -0800 | |
|---|---|---|
| committer | 2025-01-10 11:29:34 -0800 | |
| commit | d953ec98d50eb7c1e56e42faefd10be8ce695c5a (patch) | |
| tree | 260a2e9de83517308bfb861da96bd1726e10af04 | |
| parent | ec1183fed1c3c0d5311c571304168f1acb6e39d7 (diff) | |
| parent | fabf8e4c3d66e1e7de16169b1d21c434e23ef410 (diff) | |
Merge "Add support for Fine Granularity PowerMonitors" into main
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(); |