diff --git a/core/api/current.txt b/core/api/current.txt index bec2559bb4610..d5be3228f6539 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32831,9 +32831,12 @@ package android.os { method public int getCurrentThermalStatus(); method public int getLocationPowerSaveMode(); method public float getThermalHeadroom(@IntRange(from=0, to=60) int); + method public boolean isAllowedInLowPowerStandby(int); + method public boolean isAllowedInLowPowerStandby(@NonNull String); method public boolean isBatteryDischargePredictionPersonalized(); method public boolean isDeviceIdleMode(); method public boolean isDeviceLightIdleMode(); + method public boolean isExemptFromLowPowerStandby(); method public boolean isIgnoringBatteryOptimizations(String); method public boolean isInteractive(); method public boolean isLowPowerStandbyEnabled(); @@ -32856,6 +32859,8 @@ package android.os { field public static final int LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF = 1; // 0x1 field public static final int LOCATION_MODE_NO_CHANGE = 0; // 0x0 field public static final int LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF = 4; // 0x4 + field public static final int LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION = 1; // 0x1 + field public static final String LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN = "com.android.lowpowerstandby.WAKE_ON_LAN"; field public static final int ON_AFTER_RELEASE = 536870912; // 0x20000000 field public static final int PARTIAL_WAKE_LOCK = 1; // 0x1 field public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; // 0x20 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0fce784b61665..ba9a9751cdfe7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10159,6 +10159,7 @@ package android.os { method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend(); method @NonNull public android.os.BatterySaverPolicyConfig getFullPowerSavePolicy(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public android.os.PowerManager.LowPowerStandbyPolicy getLowPowerStandbyPolicy(); method public int getPowerSaveModeTrigger(); method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable(); method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed(); @@ -10171,9 +10172,11 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyActiveDuringMaintenance(boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyEnabled(boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyPolicy(@Nullable android.os.PowerManager.LowPowerStandbyPolicy); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int); + field @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY) public static final String ACTION_LOW_POWER_STANDBY_POLICY_CHANGED = "android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED"; field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1 field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0 field public static final String REBOOT_USERSPACE = "userspace"; @@ -10188,6 +10191,14 @@ package android.os { field public static final int USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS = 1; // 0x1 } + public static final class PowerManager.LowPowerStandbyPolicy { + ctor public PowerManager.LowPowerStandbyPolicy(@NonNull String, @NonNull java.util.Set, int, @NonNull java.util.Set); + method @NonNull public java.util.Set getAllowedFeatures(); + method public int getAllowedReasons(); + method @NonNull public java.util.Set getExemptPackages(); + method @NonNull public String getIdentifier(); + } + @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); diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index f1e3ab07686b7..25a27dff9e348 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -68,11 +68,29 @@ interface IPowerManager boolean isBatteryDischargePredictionPersonalized(); boolean isDeviceIdleMode(); boolean isLightDeviceIdleMode(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER })") boolean isLowPowerStandbySupported(); boolean isLowPowerStandbyEnabled(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER })") void setLowPowerStandbyEnabled(boolean enabled); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER })") void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER })") void forceLowPowerStandbyActive(boolean active); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER })") + void setLowPowerStandbyPolicy(in @nullable LowPowerStandbyPolicy policy); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER })") + LowPowerStandbyPolicy getLowPowerStandbyPolicy(); + boolean isExemptFromLowPowerStandby(); + boolean isReasonAllowedInLowPowerStandby(int reason); + boolean isFeatureAllowedInLowPowerStandby(String feature); + + parcelable LowPowerStandbyPolicy { + String identifier; + List exemptPackages; + int allowedReasons; + List allowedFeatures; + } @UnsupportedAppUsage void reboot(boolean confirm, String reason, boolean wait); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 90502af40eb88..b675587e4d004 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -35,6 +35,7 @@ import android.content.Context; import android.service.dreams.Sandman; import android.sysprop.InitProperties; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -44,7 +45,10 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; @@ -2343,6 +2347,93 @@ public final class PowerManager { } } + /** + * Sets the current Low Power Standby policy. + * + * When the policy changes {@link #ACTION_LOW_POWER_STANDBY_POLICY_CHANGED} is broadcast to + * registered receivers. + * + * @param policy The policy to set. If null, resets to the default policy. + * @see #getLowPowerStandbyPolicy + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void setLowPowerStandbyPolicy(@Nullable LowPowerStandbyPolicy policy) { + try { + mService.setLowPowerStandbyPolicy(LowPowerStandbyPolicy.toParcelable(policy)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the current Low Power Standby policy. + * + * When the policy changes {@link #ACTION_LOW_POWER_STANDBY_POLICY_CHANGED} is broadcast to + * registered receivers. + * + * @see #setLowPowerStandbyPolicy + * @hide + */ + @SystemApi + @Nullable + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public LowPowerStandbyPolicy getLowPowerStandbyPolicy() { + try { + return LowPowerStandbyPolicy.fromParcelable(mService.getLowPowerStandbyPolicy()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if the calling package is exempt from Low Power Standby restrictions or + * Low Power Standby is disabled (so Low Power Standby does not restrict apps), + * false otherwise. + */ + public boolean isExemptFromLowPowerStandby() { + try { + return mService.isExemptFromLowPowerStandby(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if Low Power Standby is disabled (so Low Power Standby does not restrict apps), + * or apps may be automatically exempt from Low Power Standby restrictions for the given reason. + * + * The system may exempt apps from Low Power Standby restrictions when using allowed features. + * For example, if {@link #LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION} is allowed, + * then apps with active voice interaction sessions are exempt from restrictions. + */ + public boolean isAllowedInLowPowerStandby(@LowPowerStandbyAllowedReason int reason) { + try { + return mService.isReasonAllowedInLowPowerStandby(reason); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if Low Power Standby is disabled (so Low Power Standby does not restrict apps), + * or apps are allowed to use a given feature during Low Power Standby. + */ + public boolean isAllowedInLowPowerStandby(@NonNull String feature) { + try { + return mService.isFeatureAllowedInLowPowerStandby(feature); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Return whether the given application package name is on the device's power allowlist. * Apps can be placed on the allowlist through the settings UI invoked by @@ -2839,6 +2930,176 @@ public final class PowerManager { public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED"; + /** + * Intent that is broadcast when Low Power Standby is enabled or disabled. + * This broadcast is only sent to registered receivers. + * + * @see #getLowPowerStandbyPolicy + * @see #setLowPowerStandbyPolicy + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY) + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOW_POWER_STANDBY_POLICY_CHANGED = + "android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED"; + + /** + * Signals that wake-on-lan/wake-on-wlan is allowed in Low Power Standby. + * + * @see #isAllowedInLowPowerStandby(String) + */ + public static final String LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN = + "com.android.lowpowerstandby.WAKE_ON_LAN"; + + /** + * @hide + */ + @IntDef(prefix = { "LOW_POWER_STANDBY_ALLOWED_REASON_" }, flag = true, value = { + LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LowPowerStandbyAllowedReason { + } + + /** + * Exempts active Voice Interaction Sessions in Low Power Standby. + * + * @see #isAllowedInLowPowerStandby(int) + */ + public static final int LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION = 1 << 0; + + /** @hide */ + public static String lowPowerStandbyAllowedReasonsToString( + @LowPowerStandbyAllowedReason int allowedReasons) { + ArrayList allowedStrings = new ArrayList<>(); + if ((allowedReasons & LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION) != 0) { + allowedStrings.add("ALLOWED_REASON_VOICE_INTERACTION"); + allowedReasons &= ~LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION; + } + if (allowedReasons != 0) { + allowedStrings.add(String.valueOf(allowedReasons)); + } + return String.join(",", allowedStrings); + } + + /** + * Policy that defines the restrictions enforced by Low Power Standby. + * + * @hide + */ + @SystemApi + public static final class LowPowerStandbyPolicy { + /** Name of the policy, used for debugging & metrics */ + @NonNull + private final String mIdentifier; + + /** Packages that are exempt from Low Power Standby restrictions. */ + @NonNull + private final Set mExemptPackages; + + /** + * Reasons that this policy allows apps to be automatically exempted + * from Low Power Standby restrictions for. + */ + @LowPowerStandbyAllowedReason + private final int mAllowedReasons; + + /** Features that are allowed to be used in Low Power Standby. */ + @NonNull + private final Set mAllowedFeatures; + + public LowPowerStandbyPolicy(@NonNull String identifier, + @NonNull Set exemptPackages, + @LowPowerStandbyAllowedReason int allowedReasons, + @NonNull Set allowedFeatures) { + Objects.requireNonNull(identifier); + Objects.requireNonNull(exemptPackages); + Objects.requireNonNull(allowedFeatures); + + mIdentifier = identifier; + mExemptPackages = Collections.unmodifiableSet(exemptPackages); + mAllowedReasons = allowedReasons; + mAllowedFeatures = Collections.unmodifiableSet(allowedFeatures); + } + + @NonNull + public String getIdentifier() { + return mIdentifier; + } + + @NonNull + public Set getExemptPackages() { + return mExemptPackages; + } + + @LowPowerStandbyAllowedReason + public int getAllowedReasons() { + return mAllowedReasons; + } + + @NonNull + public Set getAllowedFeatures() { + return mAllowedFeatures; + } + + @Override + public String toString() { + return "Policy{" + + "mIdentifier='" + mIdentifier + '\'' + + ", mExemptPackages=" + String.join(",", mExemptPackages) + + ", mAllowedReasons=" + lowPowerStandbyAllowedReasonsToString(mAllowedReasons) + + ", mAllowedFeatures=" + String.join(",", mAllowedFeatures) + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LowPowerStandbyPolicy)) return false; + LowPowerStandbyPolicy that = (LowPowerStandbyPolicy) o; + return mAllowedReasons == that.mAllowedReasons && Objects.equals(mIdentifier, + that.mIdentifier) && Objects.equals(mExemptPackages, that.mExemptPackages) + && Objects.equals(mAllowedFeatures, that.mAllowedFeatures); + } + + @Override + public int hashCode() { + return Objects.hash(mIdentifier, mExemptPackages, mAllowedReasons, + mAllowedFeatures); + } + + /** @hide */ + public static IPowerManager.LowPowerStandbyPolicy toParcelable( + LowPowerStandbyPolicy policy) { + if (policy == null) { + return null; + } + + IPowerManager.LowPowerStandbyPolicy parcelablePolicy = + new IPowerManager.LowPowerStandbyPolicy(); + parcelablePolicy.identifier = policy.mIdentifier; + parcelablePolicy.exemptPackages = new ArrayList<>(policy.mExemptPackages); + parcelablePolicy.allowedReasons = policy.mAllowedReasons; + parcelablePolicy.allowedFeatures = new ArrayList<>(policy.mAllowedFeatures); + return parcelablePolicy; + } + + /** @hide */ + public static LowPowerStandbyPolicy fromParcelable( + IPowerManager.LowPowerStandbyPolicy parcelablePolicy) { + if (parcelablePolicy == null) { + return null; + } + + return new LowPowerStandbyPolicy( + parcelablePolicy.identifier, + new ArraySet<>(parcelablePolicy.exemptPackages), + parcelablePolicy.allowedReasons, + new ArraySet<>(parcelablePolicy.allowedFeatures)); + } + } + /** * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) . * @hide diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index 004d5d699a31d..2f865afd28c7c 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -469,5 +469,26 @@ message LowPowerStandbyControllerDumpProto { // Set of app ids that are exempt form low power standby repeated int32 allowlist = 10; - // Next tag: 11 + // The active policy specifying exemptions + optional LowPowerStandbyPolicyProto policy = 11; + + // Next tag: 12 +} + +message LowPowerStandbyPolicyProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // Name of the policy + optional string identifier = 1; + + // Packages that are exempt from Low Power Standby restrictions + repeated string exempt_packages = 2; + + // Exemption reasons that this policy allows + optional int32 allowed_reasons = 3; + + // Features that this policy allows to be used + repeated string allowed_features = 4; + + // Next tag: 5 } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c052ccaf59b63..5a1b92384e0b5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -108,6 +108,7 @@ + diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java index ebeb145526a40..c9d69b1839175 100644 --- a/services/core/java/com/android/server/power/LowPowerStandbyController.java +++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java @@ -16,36 +16,66 @@ package com.android.server.power; +import static android.os.PowerManager.lowPowerStandbyAllowedReasonsToString; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AlarmManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.net.Uri; +import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.PowerManager.LowPowerStandbyAllowedReason; +import android.os.PowerManager.LowPowerStandbyPolicy; import android.os.PowerManagerInternal; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Slog; -import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; /** * Controls Low Power Standby state. @@ -56,11 +86,13 @@ import java.util.Arrays; *
    *
  • Low Power Standby is enabled *
  • The device is not interactive, and has been non-interactive for a given timeout - *
  • The device is not in a doze maintenance window + *
  • The device is not in a doze maintenance window (devices may be configured to also + * apply restrictions during doze maintenance windows, see {@link #setActiveDuringMaintenance}) *
* *

When Low Power Standby is active, the following restrictions are applied to applications - * with procstate less important than {@link android.app.ActivityManager#PROCESS_STATE_BOUND_TOP}: + * with procstate less important than {@link android.app.ActivityManager#PROCESS_STATE_BOUND_TOP} + * unless they are exempted (see {@link LowPowerStandbyPolicy}): *

    *
  • Network access is blocked *
  • Wakelocks are disabled @@ -76,9 +108,19 @@ public class LowPowerStandbyController { private static final int MSG_STANDBY_TIMEOUT = 0; private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1; private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2; + private static final int MSG_NOTIFY_POLICY_CHANGED = 3; + + private static final String TAG_ROOT = "low-power-standby-policy"; + private static final String TAG_IDENTIFIER = "identifier"; + private static final String TAG_EXEMPT_PACKAGE = "exempt-package"; + private static final String TAG_ALLOWED_REASONS = "allowed-reasons"; + private static final String TAG_ALLOWED_FEATURES = "allowed-features"; + private static final String ATTR_VALUE = "value"; private final Handler mHandler; private final SettingsObserver mSettingsObserver; + private final DeviceConfigWrapper mDeviceConfig; + private final File mPolicyFile; private final Object mLock = new Object(); private final Context mContext; @@ -86,7 +128,10 @@ public class LowPowerStandbyController { private final AlarmManager.OnAlarmListener mOnStandbyTimeoutExpired = this::onStandbyTimeoutExpired; private final LowPowerStandbyControllerInternal mLocalService = new LocalService(); - private final SparseBooleanArray mAllowlistUids = new SparseBooleanArray(); + private final SparseIntArray mUidAllowedReasons = new SparseIntArray(); + + @GuardedBy("mLock") + private boolean mEnableCustomPolicy; private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -159,6 +204,18 @@ public class LowPowerStandbyController { @GuardedBy("mLock") private boolean mForceActive; + /** Current Low Power Standby policy. */ + @GuardedBy("mLock") + @Nullable + private LowPowerStandbyPolicy mPolicy; + + @VisibleForTesting + static final LowPowerStandbyPolicy DEFAULT_POLICY = new LowPowerStandbyPolicy( + "DEFAULT_POLICY", + Collections.emptySet(), + PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION, + Collections.emptySet()); + /** Functional interface for providing time. */ @VisibleForTesting interface Clock { @@ -166,11 +223,21 @@ public class LowPowerStandbyController { long elapsedRealtime(); } - public LowPowerStandbyController(Context context, Looper looper, Clock clock) { + public LowPowerStandbyController(Context context, Looper looper) { + this(context, looper, SystemClock::elapsedRealtime, + new DeviceConfigWrapper(), + new File(Environment.getDataSystemDirectory(), "low_power_standby_policy.xml")); + } + + @VisibleForTesting + LowPowerStandbyController(Context context, Looper looper, Clock clock, + DeviceConfigWrapper deviceConfig, File policyFile) { mContext = context; mHandler = new LowPowerStandbyHandler(looper); mClock = clock; mSettingsObserver = new SettingsObserver(mHandler); + mDeviceConfig = deviceConfig; + mPolicyFile = policyFile; } /** Call when system services are ready */ @@ -201,6 +268,16 @@ public class LowPowerStandbyController { mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE), false, mSettingsObserver, UserHandle.USER_ALL); + + mDeviceConfig.registerPropertyUpdateListener(mContext.getMainExecutor(), + properties -> onDeviceConfigFlagsChanged()); + mEnableCustomPolicy = mDeviceConfig.enableCustomPolicy(); + + if (mEnableCustomPolicy) { + mPolicy = loadPolicy(); + } else { + mPolicy = DEFAULT_POLICY; + } initSettingsLocked(); updateSettingsLocked(); @@ -212,6 +289,17 @@ public class LowPowerStandbyController { LocalServices.addService(LowPowerStandbyControllerInternal.class, mLocalService); } + private void onDeviceConfigFlagsChanged() { + synchronized (mLock) { + boolean enableCustomPolicy = mDeviceConfig.enableCustomPolicy(); + if (mEnableCustomPolicy != enableCustomPolicy) { + enqueueNotifyPolicyChangedLocked(); + enqueueNotifyAllowlistChangedLocked(); + mEnableCustomPolicy = enableCustomPolicy; + } + } + } + @GuardedBy("mLock") private void initSettingsLocked() { final ContentResolver resolver = mContext.getContentResolver(); @@ -242,6 +330,139 @@ public class LowPowerStandbyController { updateActiveLocked(); } + @Nullable + private LowPowerStandbyPolicy loadPolicy() { + final AtomicFile file = getPolicyFile(); + if (!file.exists()) { + return null; + } + if (DEBUG) { + Slog.d(TAG, "Loading policy from " + file.getBaseFile()); + } + + try (FileInputStream in = file.openRead()) { + String identifier = null; + Set exemptPackages = new ArraySet<>(); + int allowedReasons = 0; + Set allowedFeatures = new ArraySet<>(); + + TypedXmlPullParser parser = Xml.resolvePullParser(in); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + // Check the root tag + final String tag = parser.getName(); + if (depth == 1) { + if (!TAG_ROOT.equals(tag)) { + Slog.e(TAG, "Invalid root tag: " + tag); + return null; + } + continue; + } + // Assume depth == 2 + switch (tag) { + case TAG_IDENTIFIER: + identifier = parser.getAttributeValue(null, ATTR_VALUE); + break; + case TAG_EXEMPT_PACKAGE: + exemptPackages.add(parser.getAttributeValue(null, ATTR_VALUE)); + break; + case TAG_ALLOWED_REASONS: + allowedReasons = parser.getAttributeInt(null, ATTR_VALUE); + break; + case TAG_ALLOWED_FEATURES: + allowedFeatures.add(parser.getAttributeValue(null, ATTR_VALUE)); + break; + default: + Slog.e(TAG, "Invalid tag: " + tag); + break; + } + } + + final LowPowerStandbyPolicy policy = new LowPowerStandbyPolicy(identifier, + exemptPackages, allowedReasons, allowedFeatures); + if (DEBUG) { + Slog.d(TAG, "Loaded policy: " + policy); + } + return policy; + } catch (FileNotFoundException e) { + // Use the default + return null; + } catch (IOException | NullPointerException | IllegalArgumentException + | XmlPullParserException e) { + Slog.e(TAG, "Failed to read policy file " + file.getBaseFile(), e); + return null; + } + } + + static void writeTagValue(TypedXmlSerializer out, String tag, String value) throws IOException { + if (TextUtils.isEmpty(value)) return; + + out.startTag(null, tag); + out.attribute(null, ATTR_VALUE, value); + out.endTag(null, tag); + } + + static void writeTagValue(TypedXmlSerializer out, String tag, int value) throws IOException { + out.startTag(null, tag); + out.attributeInt(null, ATTR_VALUE, value); + out.endTag(null, tag); + } + + private void savePolicy(@Nullable LowPowerStandbyPolicy policy) { + final AtomicFile file = getPolicyFile(); + if (DEBUG) { + Slog.d(TAG, "Saving policy to " + file.getBaseFile()); + } + if (policy == null) { + file.delete(); + return; + } + + FileOutputStream outs = null; + try { + file.getBaseFile().mkdirs(); + outs = file.startWrite(); + + // Write to XML + TypedXmlSerializer out = Xml.resolveSerializer(outs); + out.startDocument(null, true); + out.startTag(null, TAG_ROOT); + + // Body. + writeTagValue(out, TAG_IDENTIFIER, policy.getIdentifier()); + for (String exemptPackage : policy.getExemptPackages()) { + writeTagValue(out, TAG_EXEMPT_PACKAGE, exemptPackage); + } + writeTagValue(out, TAG_ALLOWED_REASONS, policy.getAllowedReasons()); + for (String allowedFeature : policy.getAllowedFeatures()) { + writeTagValue(out, TAG_ALLOWED_FEATURES, allowedFeature); + } + + // Epilogue. + out.endTag(null, TAG_ROOT); + out.endDocument(); + + // Close. + file.finishWrite(outs); + } catch (IOException e) { + Slog.e(TAG, "Failed to write policy to file " + file.getBaseFile(), e); + file.failWrite(outs); + } + } + + private void enqueueSavePolicy(@Nullable LowPowerStandbyPolicy policy) { + mHandler.post(() -> savePolicy(policy)); + } + + private AtomicFile getPolicyFile() { + return new AtomicFile(mPolicyFile); + } + @GuardedBy("mLock") private void updateActiveLocked() { final long now = mClock.elapsedRealtime(); @@ -396,6 +617,25 @@ public class LowPowerStandbyController { mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } + @GuardedBy("mLock") + private void enqueueNotifyPolicyChangedLocked() { + final long now = mClock.elapsedRealtime(); + final Message msg = mHandler.obtainMessage(MSG_NOTIFY_POLICY_CHANGED, getPolicy()); + mHandler.sendMessageAtTime(msg, now); + } + + private void notifyPolicyChanged(LowPowerStandbyPolicy policy) { + if (DEBUG) { + Slog.d(TAG, "notifyPolicyChanged, policy=" + policy); + } + + final Intent intent = new Intent( + PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.MANAGE_LOW_POWER_STANDBY); + } + private void onStandbyTimeoutExpired() { if (DEBUG) { Slog.d(TAG, "onStandbyTimeoutExpired"); @@ -414,6 +654,9 @@ public class LowPowerStandbyController { /** Notify other system components about the updated Low Power Standby active state */ private void notifyActiveChanged(boolean active) { + if (DEBUG) { + Slog.d(TAG, "notifyActiveChanged, active=" + active); + } final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); final NetworkPolicyManagerInternal npmi = LocalServices.getService( NetworkPolicyManagerInternal.class); @@ -479,6 +722,101 @@ public class LowPowerStandbyController { } } + void setPolicy(@Nullable LowPowerStandbyPolicy policy) { + synchronized (mLock) { + if (!mSupportedConfig) { + Slog.w(TAG, "Low Power Standby policy cannot be changed " + + "because it is not supported on this device"); + return; + } + + if (!mEnableCustomPolicy) { + Slog.d(TAG, "Custom policies are not enabled."); + return; + } + + if (DEBUG) { + Slog.d(TAG, "setPolicy: policy=" + policy); + } + if (Objects.equals(mPolicy, policy)) { + return; + } + + boolean allowlistChanged = policyChangeAffectsAllowlistLocked(mPolicy, policy); + mPolicy = policy; + enqueueSavePolicy(mPolicy); + if (allowlistChanged) { + enqueueNotifyAllowlistChangedLocked(); + } + enqueueNotifyPolicyChangedLocked(); + } + } + + @NonNull + LowPowerStandbyPolicy getPolicy() { + synchronized (mLock) { + if (!mSupportedConfig) { + return null; + } else if (mEnableCustomPolicy) { + return policyOrDefault(mPolicy); + } else { + return DEFAULT_POLICY; + } + } + } + + @NonNull + private LowPowerStandbyPolicy policyOrDefault(@Nullable LowPowerStandbyPolicy policy) { + if (policy == null) { + return DEFAULT_POLICY; + } + return policy; + } + + boolean isPackageExempt(int uid) { + synchronized (mLock) { + if (!isEnabled()) { + return true; + } + + return getExemptPackageAppIdsLocked().contains(UserHandle.getAppId(uid)); + } + } + + boolean isAllowed(@LowPowerStandbyAllowedReason int reason) { + synchronized (mLock) { + if (!isEnabled()) { + return true; + } + + return (getPolicy().getAllowedReasons() & reason) != 0; + } + } + + boolean isAllowed(String feature) { + synchronized (mLock) { + if (!mSupportedConfig) { + return true; + } + + return !isEnabled() || getPolicy().getAllowedFeatures().contains(feature); + } + } + + private boolean policyChangeAffectsAllowlistLocked( + @Nullable LowPowerStandbyPolicy oldPolicy, @Nullable LowPowerStandbyPolicy newPolicy) { + final LowPowerStandbyPolicy policyA = policyOrDefault(oldPolicy); + final LowPowerStandbyPolicy policyB = policyOrDefault(newPolicy); + int allowedReasonsInUse = 0; + for (int i = 0; i < mUidAllowedReasons.size(); i++) { + allowedReasonsInUse |= mUidAllowedReasons.valueAt(i); + } + + int policyAllowedReasonsChanged = policyA.getAllowedReasons() ^ policyB.getAllowedReasons(); + + return (policyAllowedReasonsChanged & allowedReasonsInUse) != 0; + } + void dump(PrintWriter pw) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); @@ -496,6 +834,8 @@ public class LowPowerStandbyController { ipw.println(mEnabledByDefaultConfig); ipw.print("mStandbyTimeoutConfig="); ipw.println(mStandbyTimeoutConfig); + ipw.print("mEnableCustomPolicy="); + ipw.println(mEnableCustomPolicy); if (mIsActive || mIsEnabled) { ipw.print("mIsInteractive="); @@ -509,8 +849,35 @@ public class LowPowerStandbyController { } final int[] allowlistUids = getAllowlistUidsLocked(); - ipw.print("mAllowlistUids="); + ipw.print("Allowed UIDs="); ipw.println(Arrays.toString(allowlistUids)); + ipw.println(); + + final LowPowerStandbyPolicy policy = getPolicy(); + ipw.println("mPolicy:"); + ipw.increaseIndent(); + ipw.print("mIdentifier="); + ipw.println(policy.getIdentifier()); + ipw.print("mExemptPackages="); + ipw.println(String.join(",", policy.getExemptPackages())); + ipw.print("mAllowedReasons="); + ipw.println(lowPowerStandbyAllowedReasonsToString(policy.getAllowedReasons())); + ipw.print("mAllowedFeatures="); + ipw.println(String.join(",", policy.getAllowedFeatures())); + ipw.decreaseIndent(); + + ipw.println(); + ipw.println("UID allowed reasons:"); + ipw.increaseIndent(); + for (int i = 0; i < mUidAllowedReasons.size(); i++) { + if (mUidAllowedReasons.valueAt(i) > 0) { + ipw.print(mUidAllowedReasons.keyAt(i)); + ipw.print(": "); + ipw.println( + lowPowerStandbyAllowedReasonsToString(mUidAllowedReasons.valueAt(i))); + } + } + ipw.decreaseIndent(); } ipw.decreaseIndent(); } @@ -537,6 +904,17 @@ public class LowPowerStandbyController { proto.write(LowPowerStandbyControllerDumpProto.ALLOWLIST, appId); } + long policyToken = proto.start(LowPowerStandbyControllerDumpProto.POLICY); + final LowPowerStandbyPolicy policy = getPolicy(); + proto.write(LowPowerStandbyPolicyProto.IDENTIFIER, policy.getIdentifier()); + for (String exemptPackage : policy.getExemptPackages()) { + proto.write(LowPowerStandbyPolicyProto.EXEMPT_PACKAGES, exemptPackage); + } + proto.write(LowPowerStandbyPolicyProto.ALLOWED_REASONS, policy.getAllowedReasons()); + for (String feature : policy.getAllowedFeatures()) { + proto.write(LowPowerStandbyPolicyProto.ALLOWED_FEATURES, feature); + } + proto.end(policyToken); proto.end(token); } } @@ -560,52 +938,138 @@ public class LowPowerStandbyController { final int[] allowlistUids = (int[]) msg.obj; notifyAllowlistChanged(allowlistUids); break; - } - } - } - - private void addToAllowlistInternal(int uid) { - if (DEBUG) { - Slog.i(TAG, "Adding to allowlist: " + uid); - } - synchronized (mLock) { - if (mSupportedConfig && !mAllowlistUids.get(uid)) { - mAllowlistUids.append(uid, true); - enqueueNotifyAllowlistChangedLocked(); - } - } - } - - private void removeFromAllowlistInternal(int uid) { - if (DEBUG) { - Slog.i(TAG, "Removing from allowlist: " + uid); - } - synchronized (mLock) { - if (mSupportedConfig && mAllowlistUids.get(uid)) { - mAllowlistUids.delete(uid); - enqueueNotifyAllowlistChangedLocked(); + case MSG_NOTIFY_POLICY_CHANGED: + notifyPolicyChanged((LowPowerStandbyPolicy) msg.obj); + break; } } } @GuardedBy("mLock") - private int[] getAllowlistUidsLocked() { - final int[] uids = new int[mAllowlistUids.size()]; - for (int i = 0; i < mAllowlistUids.size(); i++) { - uids[i] = mAllowlistUids.keyAt(i); + private boolean hasAllowedReasonLocked(int uid, + @LowPowerStandbyAllowedReason int allowedReason) { + int allowedReasons = mUidAllowedReasons.get(uid); + return (allowedReasons & allowedReason) != 0; + } + + @GuardedBy("mLock") + private boolean addAllowedReasonLocked(int uid, + @LowPowerStandbyAllowedReason int allowedReason) { + int allowedReasons = mUidAllowedReasons.get(uid); + final int newAllowReasons = allowedReasons | allowedReason; + mUidAllowedReasons.put(uid, newAllowReasons); + return allowedReasons != newAllowReasons; + } + + @GuardedBy("mLock") + private boolean removeAllowedReasonLocked(int uid, + @LowPowerStandbyAllowedReason int allowedReason) { + int allowedReasons = mUidAllowedReasons.get(uid); + if (allowedReasons == 0) { + return false; } - return uids; + + final int newAllowedReasons = allowedReasons & ~allowedReason; + if (newAllowedReasons == 0) { + mUidAllowedReasons.removeAt(mUidAllowedReasons.indexOfKey(uid)); + } else { + mUidAllowedReasons.put(uid, newAllowedReasons); + } + return allowedReasons != newAllowedReasons; + } + + private void addToAllowlistInternal(int uid, @LowPowerStandbyAllowedReason int allowedReason) { + if (DEBUG) { + Slog.i(TAG, + "Adding to allowlist: uid=" + uid + ", allowedReason=" + allowedReason); + } + synchronized (mLock) { + if (allowedReason != 0 && !hasAllowedReasonLocked(uid, allowedReason)) { + addAllowedReasonLocked(uid, allowedReason); + if ((getPolicy().getAllowedReasons() & allowedReason) != 0) { + enqueueNotifyAllowlistChangedLocked(); + } + } + } + } + + private void removeFromAllowlistInternal(int uid, + @LowPowerStandbyAllowedReason int allowedReason) { + if (DEBUG) { + Slog.i(TAG, "Removing from allowlist: uid=" + uid + ", allowedReason=" + allowedReason); + } + synchronized (mLock) { + if (allowedReason != 0 && hasAllowedReasonLocked(uid, allowedReason)) { + removeAllowedReasonLocked(uid, allowedReason); + if ((getPolicy().getAllowedReasons() & allowedReason) != 0) { + enqueueNotifyAllowlistChangedLocked(); + } + } + } + } + + @GuardedBy("mLock") + @NonNull + private List getExemptPackageAppIdsLocked() { + final PackageManager packageManager = mContext.getPackageManager(); + final LowPowerStandbyPolicy policy = getPolicy(); + final List appIds = new ArrayList<>(); + + for (String packageName : policy.getExemptPackages()) { + try { + int packageUid = packageManager.getPackageUid(packageName, + PackageManager.PackageInfoFlags.of(0)); + int appId = UserHandle.getAppId(packageUid); + appIds.add(appId); + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) { + Slog.d(TAG, "Package UID cannot be resolved: packageName=" + packageName); + } + } + } + + return appIds; + } + + @GuardedBy("mLock") + private int[] getAllowlistUidsLocked() { + final ArraySet uids = new ArraySet<>(mUidAllowedReasons.size()); + + final int policyAllowedReasons = getPolicy().getAllowedReasons(); + for (int i = 0; i < mUidAllowedReasons.size(); i++) { + Integer uid = mUidAllowedReasons.keyAt(i); + if ((mUidAllowedReasons.valueAt(i) & policyAllowedReasons) != 0) { + uids.add(uid); + } + } + + int[] allowlistUids = new int[uids.size()]; + for (int i = 0; i < uids.size(); i++) { + allowlistUids[i] = uids.valueAt(i); + } + Arrays.sort(allowlistUids); + return allowlistUids; } @GuardedBy("mLock") private void enqueueNotifyAllowlistChangedLocked() { final long now = mClock.elapsedRealtime(); final int[] allowlistUids = getAllowlistUidsLocked(); + + if (DEBUG) { + Slog.d(TAG, "enqueueNotifyAllowlistChangedLocked: allowlistUids=" + Arrays.toString( + allowlistUids)); + } + final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ALLOWLIST_CHANGED, allowlistUids); mHandler.sendMessageAtTime(msg, now); } private void notifyAllowlistChanged(int[] allowlistUids) { + if (DEBUG) { + Slog.d(TAG, "notifyAllowlistChanged: " + Arrays.toString(allowlistUids)); + } + final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); final NetworkPolicyManagerInternal npmi = LocalServices.getService( NetworkPolicyManagerInternal.class); @@ -613,15 +1077,42 @@ public class LowPowerStandbyController { npmi.setLowPowerStandbyAllowlist(allowlistUids); } + /** + * Class that is used to read device config for low power standby configuration. + */ + @VisibleForTesting + public static class DeviceConfigWrapper { + public static final String NAMESPACE = "low_power_standby"; + public static final String FEATURE_FLAG_ENABLE_POLICY = "enable_policy"; + + /** + * Returns true if custom policies are enabled. + * Otherwise, returns false, and the default policy will be used. + */ + public boolean enableCustomPolicy() { + return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, false); + } + + /** + * Registers a DeviceConfig update listener. + */ + public void registerPropertyUpdateListener( + @NonNull Executor executor, + @NonNull DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) { + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, executor, + onPropertiesChangedListener); + } + } + private final class LocalService extends LowPowerStandbyControllerInternal { @Override - public void addToAllowlist(int uid) { - addToAllowlistInternal(uid); + public void addToAllowlist(int uid, @LowPowerStandbyAllowedReason int allowedReason) { + addToAllowlistInternal(uid, allowedReason); } @Override - public void removeFromAllowlist(int uid) { - removeFromAllowlistInternal(uid); + public void removeFromAllowlist(int uid, @LowPowerStandbyAllowedReason int allowedReason) { + removeFromAllowlistInternal(uid, allowedReason); } } diff --git a/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java index f6953faa8eec4..4acbe5d6202cd 100644 --- a/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java +++ b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java @@ -16,6 +16,8 @@ package com.android.server.power; +import android.os.PowerManager.LowPowerStandbyAllowedReason; + /** * @hide Only for use within the system server. */ @@ -25,13 +27,16 @@ public abstract class LowPowerStandbyControllerInternal { * exempting it from Low Power Standby restrictions. * * @param uid UID to add to allowlist. + * @param allowedReason reason for this package to be allowed */ - public abstract void addToAllowlist(int uid); + public abstract void addToAllowlist(int uid, @LowPowerStandbyAllowedReason int allowedReason); /** * Removes an application from the Low Power Standby allowlist. * * @param uid UID to remove from allowlist. + * @param allowedReason reason for this package to have been allowed */ - public abstract void removeFromAllowlist(int uid); + public abstract void removeFromAllowlist(int uid, + @LowPowerStandbyAllowedReason int allowedReason); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index c29ab09fa3865..2a0034554491d 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -628,7 +628,7 @@ public final class PowerManagerService extends SystemService // Set of app ids that are temporarily allowed to acquire wakelocks due to high-pri message int[] mDeviceIdleTempWhitelist = new int[0]; - // Set of app ids that are allowed to acquire wakelocks while low power standby is active + // Set of uids that are allowed to acquire wakelocks while low power standby is active int[] mLowPowerStandbyAllowlist = new int[0]; private boolean mLowPowerStandbyActive; @@ -1020,7 +1020,7 @@ public final class PowerManagerService extends SystemService } LowPowerStandbyController createLowPowerStandbyController(Context context, Looper looper) { - return new LowPowerStandbyController(context, looper, SystemClock::elapsedRealtime); + return new LowPowerStandbyController(context, looper); } AppOpsManager createAppOpsManager(Context context) { @@ -3924,9 +3924,9 @@ public final class PowerManagerService extends SystemService } } - void setLowPowerStandbyAllowlistInternal(int[] appids) { + void setLowPowerStandbyAllowlistInternal(int[] uids) { synchronized (mLock) { - mLowPowerStandbyAllowlist = appids; + mLowPowerStandbyAllowlist = uids; if (mLowPowerStandbyActive) { updateWakeLockDisabledStatesLocked(); } @@ -4085,7 +4085,7 @@ public final class PowerManagerService extends SystemService } if (mLowPowerStandbyActive) { final UidState state = wakeLock.mUidState; - if (Arrays.binarySearch(mLowPowerStandbyAllowlist, appid) < 0 + if (Arrays.binarySearch(mLowPowerStandbyAllowlist, wakeLock.mOwnerUid) < 0 && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT && state.mProcState > ActivityManager.PROCESS_STATE_BOUND_TOP) { disabled = true; @@ -6113,6 +6113,82 @@ public final class PowerManagerService extends SystemService } } + @Override // Binder call + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void setLowPowerStandbyPolicy(@Nullable IPowerManager.LowPowerStandbyPolicy policy) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + "setLowPowerStandbyPolicy"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + mLowPowerStandbyController.setPolicy( + PowerManager.LowPowerStandbyPolicy.fromParcelable(policy)); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public IPowerManager.LowPowerStandbyPolicy getLowPowerStandbyPolicy() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + "getLowPowerStandbyPolicy"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + return PowerManager.LowPowerStandbyPolicy.toParcelable( + mLowPowerStandbyController.getPolicy()); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public boolean isExemptFromLowPowerStandby() { + final int callingUid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + return mLowPowerStandbyController.isPackageExempt(callingUid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public boolean isReasonAllowedInLowPowerStandby( + @PowerManager.LowPowerStandbyAllowedReason int reason) { + final long ident = Binder.clearCallingIdentity(); + try { + return mLowPowerStandbyController.isAllowed(reason); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public boolean isFeatureAllowedInLowPowerStandby(String feature) { + final long ident = Binder.clearCallingIdentity(); + try { + return mLowPowerStandbyController.isAllowed(feature); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * Gets the reason for the last time the phone had to reboot. * diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java index 00a7944467424..5e6c2ba9520c6 100644 --- a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java @@ -16,8 +16,14 @@ package com.android.server.power; +import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION; +import static android.os.PowerManager.LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN; + import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -37,10 +43,12 @@ import android.content.Intent; import android.content.res.Resources; import android.os.IPowerManager; import android.os.PowerManager; +import android.os.PowerManager.LowPowerStandbyPolicy; import android.os.PowerManagerInternal; import android.os.test.TestLooper; import android.provider.Settings; import android.test.mock.MockContentResolver; +import android.util.ArraySet; import androidx.test.InstrumentationRegistry; @@ -48,6 +56,7 @@ import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.power.LowPowerStandbyController.DeviceConfigWrapper; import com.android.server.testutils.OffsettableClock; import org.junit.After; @@ -58,6 +67,8 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; +import java.util.Collections; import java.util.concurrent.TimeUnit; /** @@ -69,12 +80,18 @@ import java.util.concurrent.TimeUnit; public class LowPowerStandbyControllerTest { private static final int STANDBY_TIMEOUT = 5000; + private static final LowPowerStandbyPolicy EMPTY_POLICY = new LowPowerStandbyPolicy( + "Test policy", Collections.emptySet(), 0, Collections.emptySet()); + private LowPowerStandbyController mController; private BroadcastInterceptingContext mContextSpy; private Resources mResourcesSpy; private OffsettableClock mClock; private TestLooper mTestLooper; + private File mTestPolicyFile; + @Mock + private DeviceConfigWrapper mDeviceConfigWrapperMock; @Mock private AlarmManager mAlarmManagerMock; @Mock @@ -82,7 +99,7 @@ public class LowPowerStandbyControllerTest { @Mock private PowerManagerInternal mPowerManagerInternalMock; @Mock - private NetworkPolicyManagerInternal mNetworkPolicyManagerInternal; + private NetworkPolicyManagerInternal mNetworkPolicyManagerInternalMock; @Before public void setUp() throws Exception { @@ -93,10 +110,11 @@ public class LowPowerStandbyControllerTest { PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null); when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); - addLocalServiceMock(NetworkPolicyManagerInternal.class, mNetworkPolicyManagerInternal); + addLocalServiceMock(NetworkPolicyManagerInternal.class, mNetworkPolicyManagerInternalMock); when(mIPowerManagerMock.isInteractive()).thenReturn(true); + when(mDeviceConfigWrapperMock.enableCustomPolicy()).thenReturn(true); mResourcesSpy = spy(mContextSpy.getResources()); when(mContextSpy.getResources()).thenReturn(mResourcesSpy); when(mResourcesSpy.getBoolean( @@ -117,8 +135,9 @@ public class LowPowerStandbyControllerTest { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); + mTestPolicyFile = new File(mContextSpy.getCacheDir(), "lps_policy.xml"); mController = new LowPowerStandbyController(mContextSpy, mTestLooper.getLooper(), - () -> mClock.now()); + () -> mClock.now(), mDeviceConfigWrapperMock, mTestPolicyFile); } @After @@ -126,6 +145,7 @@ public class LowPowerStandbyControllerTest { LocalServices.removeServiceForTest(PowerManagerInternal.class); LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class); LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); + mTestPolicyFile.delete(); } @Test @@ -135,7 +155,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); - verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); } @Test @@ -148,7 +168,7 @@ public class LowPowerStandbyControllerTest { awaitStandbyTimeoutAlarm(); assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true); - verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(true); } private void awaitStandbyTimeoutAlarm() { @@ -176,7 +196,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); - verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); } @Test @@ -190,7 +210,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true); - verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(true); } @Test @@ -206,7 +226,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); - verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); } private void verifyStandbyAlarmCancelled() { @@ -231,7 +251,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false); - verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(false); } @Test @@ -249,7 +269,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false); - verify(mNetworkPolicyManagerInternal, times(1)).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternalMock, times(1)).setLowPowerStandbyActive(false); } @Test @@ -267,7 +287,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false); - verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(false); } @Test @@ -286,7 +306,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true); - verify(mNetworkPolicyManagerInternal, times(2)).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternalMock, times(2)).setLowPowerStandbyActive(true); } @Test @@ -299,7 +319,7 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isFalse(); verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any()); verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); - verify(mNetworkPolicyManagerInternal, never()).setLowPowerStandbyActive(anyBoolean()); + verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); } @Test @@ -355,24 +375,6 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isSupported()).isFalse(); } - @Test - public void testAllowlistChange_servicesAreNotified() throws Exception { - setLowPowerStandbySupportedConfig(true); - mController.systemReady(); - - LowPowerStandbyControllerInternal service = LocalServices.getService( - LowPowerStandbyControllerInternal.class); - service.addToAllowlist(10); - mTestLooper.dispatchAll(); - verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10}); - verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {10}); - - service.removeFromAllowlist(10); - mTestLooper.dispatchAll(); - verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {}); - verify(mNetworkPolicyManagerInternal).setLowPowerStandbyAllowlist(new int[] {}); - } - @Test public void testForceActive() throws Exception { setLowPowerStandbySupportedConfig(false); @@ -383,14 +385,14 @@ public class LowPowerStandbyControllerTest { assertThat(mController.isActive()).isTrue(); verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true); - verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(true); + verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyActive(true); mController.forceActive(false); mTestLooper.dispatchAll(); assertThat(mController.isActive()).isFalse(); verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false); - verify(mNetworkPolicyManagerInternal).setLowPowerStandbyActive(false); + verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyActive(false); } private void setLowPowerStandbySupportedConfig(boolean supported) { @@ -399,6 +401,140 @@ public class LowPowerStandbyControllerTest { .thenReturn(supported); } + @Test + public void testSetPolicy() throws Exception { + mController.systemReady(); + mController.setPolicy(EMPTY_POLICY); + assertThat(mController.getPolicy()).isEqualTo(EMPTY_POLICY); + } + + @Test + public void testSetDefaultPolicy() throws Exception { + mController.systemReady(); + mController.setPolicy(EMPTY_POLICY); + mController.setPolicy(null); + assertThat(mController.getPolicy()).isNotNull(); + assertThat(mController.getPolicy()).isEqualTo(LowPowerStandbyController.DEFAULT_POLICY); + } + + @Test + public void testAddToAllowlist_ReasonIsAllowed_servicesAreNotified() throws Exception { + mController.systemReady(); + mController.setPolicy( + policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION)); + + LowPowerStandbyControllerInternal service = LocalServices.getService( + LowPowerStandbyControllerInternal.class); + service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); + mTestLooper.dispatchAll(); + verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10}); + verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10}); + + service.removeFromAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); + mTestLooper.dispatchAll(); + verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{}); + verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{}); + } + + @Test + public void testRemoveFromAllowlist_ReasonIsAllowed_servicesAreNotified() throws Exception { + mController.systemReady(); + mController.setPolicy( + policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION)); + + LowPowerStandbyControllerInternal service = LocalServices.getService( + LowPowerStandbyControllerInternal.class); + service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); + mTestLooper.dispatchAll(); + + service.removeFromAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); + mTestLooper.dispatchAll(); + verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{}); + verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{}); + } + + @Test + public void testSetAllowReasons_ActiveExemptionsNoLongerAllowed_servicesAreNotified() { + mController.systemReady(); + mController.setEnabled(true); + mController.setPolicy( + policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION)); + + LowPowerStandbyControllerInternal service = LocalServices.getService( + LowPowerStandbyControllerInternal.class); + service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); + mTestLooper.dispatchAll(); + + mController.setPolicy(EMPTY_POLICY); + mTestLooper.dispatchAll(); + + verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{}); + verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{}); + } + + @Test + public void testSetAllowReasons_ReasonBecomesAllowed_servicesAreNotified() throws Exception { + mController.systemReady(); + mController.setEnabled(true); + mController.setPolicy(EMPTY_POLICY); + + LowPowerStandbyControllerInternal service = LocalServices.getService( + LowPowerStandbyControllerInternal.class); + service.addToAllowlist(10, LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); + mTestLooper.dispatchAll(); + + verify(mPowerManagerInternalMock, never()).setLowPowerStandbyAllowlist(any()); + verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyAllowlist(any()); + + mController.setPolicy( + policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION)); + mTestLooper.dispatchAll(); + + verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10}); + verify(mNetworkPolicyManagerInternalMock).setLowPowerStandbyAllowlist(new int[]{10}); + } + + @Test + public void testSetAllowReasons_NoActiveExemptions_servicesAreNotNotified() throws Exception { + mController.systemReady(); + mController.setEnabled(true); + mController.setPolicy( + policyWithAllowedReasons(LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION)); + mController.setPolicy(EMPTY_POLICY); + mTestLooper.dispatchAll(); + + verify(mPowerManagerInternalMock, never()).setLowPowerStandbyAllowlist(any()); + verify(mNetworkPolicyManagerInternalMock, never()).setLowPowerStandbyAllowlist(any()); + } + + @Test + public void testSetAllowedFeatures_isAllowedIfDisabled() throws Exception { + mController.systemReady(); + mController.setEnabled(false); + mTestLooper.dispatchAll(); + + assertTrue(mController.isAllowed(LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN)); + } + + @Test + public void testSetAllowedFeatures_isAllowedWhenEnabled() throws Exception { + mController.systemReady(); + mController.setEnabled(true); + mController.setPolicy(policyWithAllowedFeatures(LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN)); + mTestLooper.dispatchAll(); + + assertTrue(mController.isAllowed(LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN)); + } + + @Test + public void testSetAllowedFeatures_isNotAllowed() throws Exception { + mController.systemReady(); + mController.setEnabled(true); + mTestLooper.dispatchAll(); + + assertFalse(mController.isAllowed(LOW_POWER_STANDBY_FEATURE_WAKE_ON_LAN)); + } + private void setInteractive() throws Exception { when(mIPowerManagerMock.isInteractive()).thenReturn(true); mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON)); @@ -414,6 +550,24 @@ public class LowPowerStandbyControllerTest { mContextSpy.sendBroadcast(new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); } + private LowPowerStandbyPolicy policyWithAllowedReasons(int allowedReasons) { + return new LowPowerStandbyPolicy( + "Test policy", + Collections.emptySet(), + allowedReasons, + Collections.emptySet() + ); + } + + private LowPowerStandbyPolicy policyWithAllowedFeatures(String... allowedFeatures) { + return new LowPowerStandbyPolicy( + "Test policy", + Collections.emptySet(), + 0, + new ArraySet<>(allowedFeatures) + ); + } + private void advanceTime(long timeMs) { mClock.fastForward(timeMs); mTestLooper.dispatchAll(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 763024fac838d..216f45acd5bd4 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -51,6 +51,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import android.os.PowerManagerInternal; import android.os.RemoteException; import android.os.ServiceManager; @@ -346,7 +347,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, mFgHandler.post(mSetPowerBoostRunnable); if (mLowPowerStandbyControllerInternal != null) { - mLowPowerStandbyControllerInternal.addToAllowlist(mCallingUid); + mLowPowerStandbyControllerInternal.addToAllowlist(mCallingUid, + PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); mLowPowerStandbyAllowlisted = true; mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable); mFgHandler.postDelayed(mRemoveFromLowPowerStandbyAllowlistRunnable, @@ -861,7 +863,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, synchronized (mLock) { if (mLowPowerStandbyAllowlisted) { mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable); - mLowPowerStandbyControllerInternal.removeFromAllowlist(mCallingUid); + mLowPowerStandbyControllerInternal.removeFromAllowlist(mCallingUid, + PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION); mLowPowerStandbyAllowlisted = false; } }