Add force suspend API to PowerManager.
Add a hidden system API (protected by DEVICE_POWER) that forces suspend,
ignoring any existing wakelock. Add a shell cmd to trigger the API
to run.
Bug: 111991113
Test: 'adb shell svc power forcesuspend'
Change-Id: I5a258e1b7c8b1391fe1baf3930dd9d9af47235c9
diff --git a/api/system-current.txt b/api/system-current.txt
index fb2b9e1..485974b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5327,6 +5327,7 @@
public final class PowerManager {
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 @RequiresPermission(android.Manifest.permission.POWER_SAVER) public int getPowerSaveMode();
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig);
diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
index d29e68e..3180b77 100644
--- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
@@ -26,6 +26,8 @@
import android.os.SystemProperties;
public class PowerCommand extends Svc.Command {
+ private static final int FORCE_SUSPEND_DELAY_DEFAULT_MILLIS = 0;
+
public PowerCommand() {
super("power");
}
@@ -42,7 +44,17 @@
+ " svc power reboot [reason]\n"
+ " Perform a runtime shutdown and reboot device with specified reason.\n"
+ " svc power shutdown\n"
- + " Perform a runtime shutdown and power off the device.\n";
+ + " Perform a runtime shutdown and power off the device.\n"
+ + " svc power forcesuspend [t]\n"
+ + " Force the system into suspend, ignoring all wakelocks.\n"
+ + " t - Number of milliseconds to wait before issuing force-suspend.\n"
+ + " Helps with devices that can't suspend while plugged in.\n"
+ + " Defaults to " + FORCE_SUSPEND_DELAY_DEFAULT_MILLIS + ".\n"
+ + " When using a delay, you must use the nohup shell modifier:\n"
+ + " 'adb shell nohup svc power forcesuspend [time]'\n"
+ + " Use caution; this is dangerous. It puts the device to sleep\n"
+ + " immediately without giving apps or the system an opportunity to\n"
+ + " save their state.\n";
}
public void run(String[] args) {
@@ -101,6 +113,20 @@
maybeLogRemoteException("Failed to shutdown.");
}
return;
+ } else if ("forcesuspend".equals(args[1])) {
+ int delayMillis = args.length > 2
+ ? Integer.parseInt(args[2]) : FORCE_SUSPEND_DELAY_DEFAULT_MILLIS;
+ try {
+ Thread.sleep(delayMillis);
+ if (!pm.forceSuspend()) {
+ System.err.println("Failed to force suspend.");
+ }
+ } catch (InterruptedException e) {
+ System.err.println("Failed to force suspend: " + e);
+ } catch (RemoteException e) {
+ maybeLogRemoteException("Failed to force-suspend with exception: " + e);
+ }
+ return;
}
}
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index bdef575..483c41a 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -74,4 +74,7 @@
// controls whether PowerManager should doze after the screen turns off or not
void setDozeAfterScreenOff(boolean on);
+
+ // Forces the system to suspend even if there are held wakelocks.
+ boolean forceSuspend();
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index cfe2d28..6bd1f2b 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -363,10 +363,15 @@
public static final int USER_ACTIVITY_FLAG_INDIRECT = 1 << 1;
/**
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_MIN = 0;
+
+ /**
* Go to sleep reason code: Going to sleep due by application request.
* @hide
*/
- public static final int GO_TO_SLEEP_REASON_APPLICATION = 0;
+ public static final int GO_TO_SLEEP_REASON_APPLICATION = GO_TO_SLEEP_REASON_MIN;
/**
* Go to sleep reason code: Going to sleep due by request of the
@@ -412,6 +417,17 @@
public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7;
/**
+ * Go to sleep reason code: Going to sleep due to force-suspend.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_FORCE_SUSPEND = 8;
+
+ /**
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_FORCE_SUSPEND;
+
+ /**
* @hide
*/
public static String sleepReasonToString(int sleepReason) {
@@ -424,6 +440,7 @@
case GO_TO_SLEEP_REASON_HDMI: return "hdmi";
case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button";
case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility";
+ case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend";
default: return Integer.toString(sleepReason);
}
}
@@ -1853,6 +1870,32 @@
}
/**
+ * Forces the device to go to suspend, even if there are currently wakelocks being held.
+ * <b>Caution</b>
+ * This is a very dangerous command as it puts the device to sleep immediately. Apps and parts
+ * of the system will not be notified and will not have an opportunity to save state prior to
+ * the device going to suspend.
+ * This method should only be used in very rare circumstances where the device is intended
+ * to appear as completely off to the user and they have a well understood, reliable way of
+ * re-enabling it.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * @return true on success, false otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public boolean forceSuspend() {
+ try {
+ return mService.forceSuspend();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Intent that is broadcast when the state of {@link #isPowerSaveMode()} changes.
* This broadcast is only sent to registered receivers.
*/
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 1782b6a..176dbbf 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -538,6 +538,9 @@
// True if we are currently in VR Mode.
private boolean mIsVrModeEnabled;
+ // True if we in the process of performing a forceSuspend
+ private boolean mForceSuspendActive;
+
private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver {
@Override
public void onUserSwitching(int newUserId) throws RemoteException {}
@@ -684,6 +687,11 @@
public void nativeSetFeature(int featureId, int data) {
PowerManagerService.nativeSetFeature(featureId, data);
}
+
+ /** Wrapper for PowerManager.nativeForceSuspend */
+ public boolean nativeForceSuspend() {
+ return PowerManagerService.nativeForceSuspend();
+ }
}
@VisibleForTesting
@@ -718,6 +726,7 @@
private static native void nativeSetAutoSuspend(boolean enable);
private static native void nativeSendPowerHint(int hintId, int data);
private static native void nativeSetFeature(int featureId, int data);
+ private static native boolean nativeForceSuspend();
public PowerManagerService(Context context) {
this(context, new Injector());
@@ -1427,7 +1436,7 @@
}
if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE
- || !mBootCompleted || !mSystemReady) {
+ || !mBootCompleted || !mSystemReady || mForceSuspendActive) {
return false;
}
@@ -1463,8 +1472,13 @@
}
}
- // This method is called goToSleep for historical reasons but we actually start
- // dozing before really going to sleep.
+ /**
+ * Puts the system in doze.
+ *
+ * This method is called goToSleep for historical reasons but actually attempts to DOZE,
+ * and only tucks itself in to SLEEP if requested with the flag
+ * {@link PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE}.
+ */
@SuppressWarnings("deprecation")
private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) {
if (DEBUG_SPEW) {
@@ -1481,35 +1495,10 @@
Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep");
try {
- switch (reason) {
- case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
- Slog.i(TAG, "Going to sleep due to device administration policy "
- + "(uid " + uid +")...");
- break;
- case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
- Slog.i(TAG, "Going to sleep due to screen timeout (uid " + uid +")...");
- break;
- case PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH:
- Slog.i(TAG, "Going to sleep due to lid switch (uid " + uid +")...");
- break;
- case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
- Slog.i(TAG, "Going to sleep due to power button (uid " + uid +")...");
- break;
- case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON:
- Slog.i(TAG, "Going to sleep due to sleep button (uid " + uid +")...");
- break;
- case PowerManager.GO_TO_SLEEP_REASON_HDMI:
- Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
- break;
- case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY:
- Slog.i(TAG, "Going to sleep by an accessibility service request (uid "
- + uid +")...");
- break;
- default:
- Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
- reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
- break;
- }
+ reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
+ Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+ Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
+ + " (uid " + uid + ")...");
mLastSleepTime = eventTime;
mLastSleepReason = reason;
@@ -3063,10 +3052,10 @@
if (appid >= Process.FIRST_APPLICATION_UID) {
// Cached inactive processes are never allowed to hold wake locks.
if (mConstants.NO_CACHED_WAKE_LOCKS) {
- disabled = !wakeLock.mUidState.mActive &&
- wakeLock.mUidState.mProcState
+ disabled = mForceSuspendActive
+ || (!wakeLock.mUidState.mActive && wakeLock.mUidState.mProcState
!= ActivityManager.PROCESS_STATE_NONEXISTENT &&
- wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER;
+ wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER);
}
if (mDeviceIdleMode) {
// If we are in idle mode, we will also ignore all partial wake locks that are
@@ -3241,6 +3230,34 @@
}
}
+ private boolean forceSuspendInternal(int uid) {
+ try {
+ synchronized (mLock) {
+ mForceSuspendActive = true;
+ // Place the system in an non-interactive state
+ goToSleepInternal(SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND,
+ PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid);
+
+ // Disable all the partial wake locks as well
+ updateWakeLockDisabledStatesLocked();
+ }
+
+ Slog.i(TAG, "Force-Suspending (uid " + uid + ")...");
+ boolean success = mNativeWrapper.nativeForceSuspend();
+ if (!success) {
+ Slog.i(TAG, "Force-Suspending failed in native.");
+ }
+ return success;
+ } finally {
+ synchronized (mLock) {
+ mForceSuspendActive = false;
+ // Re-enable wake locks once again.
+ updateWakeLockDisabledStatesLocked();
+ }
+ }
+ }
+
/**
* Low-level function turn the device off immediately, without trying
* to be clean. Most people should use {@link ShutdownThread} for a clean shutdown.
@@ -4743,6 +4760,20 @@
}
}
+ @Override // binder call
+ public boolean forceSuspend() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+
+ final int uid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return forceSuspendInternal(uid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
@Override // Binder call
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 9be728b..ec7a78b 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -295,6 +295,12 @@
}
}
+static bool nativeForceSuspend(JNIEnv* /* env */, jclass /* clazz */) {
+ bool retval = false;
+ getSuspendControl()->forceSuspend(&retval);
+ return retval;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gPowerManagerServiceMethods[] = {
@@ -303,6 +309,8 @@
(void*) nativeInit },
{ "nativeAcquireSuspendBlocker", "(Ljava/lang/String;)V",
(void*) nativeAcquireSuspendBlocker },
+ { "nativeForceSuspend", "()Z",
+ (void*) nativeForceSuspend },
{ "nativeReleaseSuspendBlocker", "(Ljava/lang/String;)V",
(void*) nativeReleaseSuspendBlocker },
{ "nativeSetInteractive", "(Z)V",
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 63341b6..911c4a2 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -21,15 +21,21 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.ActivityManagerInternal;
+import android.attention.AttentionManagerInternal;
import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.BatteryManagerInternal;
+import android.os.Binder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerSaveState;
@@ -53,6 +59,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Tests for {@link com.android.server.power.PowerManagerService}
*/
@@ -67,6 +76,7 @@
private @Mock DisplayManagerInternal mDisplayManagerInternalMock;
private @Mock BatteryManagerInternal mBatteryManagerInternalMock;
private @Mock ActivityManagerInternal mActivityManagerInternalMock;
+ private @Mock AttentionManagerInternal mAttentionManagerInternalMock;
private @Mock PowerManagerService.NativeWrapper mNativeWrapperMock;
private @Mock Notifier mNotifierMock;
private PowerManagerService mService;
@@ -93,6 +103,7 @@
addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock);
addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock);
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+ addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
mService = new PowerManagerService(getContext(), new Injector() {
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
@@ -210,4 +221,80 @@
mService.onUserActivity();
assertThat(mService.wasDeviceIdleForInternal(interval)).isFalse();
}
+
+ @SmallTest
+ public void testForceSuspend_putsDeviceToSleep() {
+ mService.systemReady(null);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Verify that we start awake
+ assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Grab the wakefulness value when PowerManager finally calls into the
+ // native component to actually perform the suspend.
+ when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
+ assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
+ return true;
+ });
+
+ boolean retval = mService.getBinderServiceInstance().forceSuspend();
+ assertThat(retval).isTrue();
+
+ // Still asleep when the function returns.
+ assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
+ }
+
+ @SmallTest
+ public void testForceSuspend_pakeLocksDisabled() {
+ final String tag = "TestWakelockTag_098213";
+ final int flags = PowerManager.PARTIAL_WAKE_LOCK;
+ final String pkg = getContext().getOpPackageName();
+
+ // Set up the Notification mock to keep track of the wakelocks that are currently
+ // active or disabled. We'll use this to verify that wakelocks are disabled when
+ // they should be.
+ final Map<String, Integer> wakelockMap = new HashMap<>(1);
+ doAnswer(inv -> {
+ wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]);
+ return null;
+ }).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(),
+ anyInt(), any(), any());
+ doAnswer(inv -> {
+ wakelockMap.remove((String) inv.getArguments()[1]);
+ return null;
+ }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(),
+ anyInt(), any(), any());
+
+ //
+ // TEST STARTS HERE
+ //
+ mService.systemReady(null);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Verify that we start awake
+ assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_AWAKE);
+
+ // Create a wakelock
+ mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg,
+ null /* workSource */, null /* historyTag */);
+ assertThat(wakelockMap.get(tag)).isEqualTo(flags); // Verify wakelock is active.
+
+ // Confirm that the wakelocks have been disabled when the forceSuspend is in flight.
+ when(mNativeWrapperMock.nativeForceSuspend()).then(inv -> {
+ // Verify that the wakelock is disabled by the time we get to the native force
+ // suspend call.
+ assertThat(wakelockMap.containsKey(tag)).isFalse();
+ return true;
+ });
+
+ assertThat(mService.getBinderServiceInstance().forceSuspend()).isTrue();
+ assertThat(wakelockMap.get(tag)).isEqualTo(flags);
+
+ }
+
+ @SmallTest
+ public void testForceSuspend_forceSuspendFailurePropogated() {
+ when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false);
+ assertThat(mService.getBinderServiceInstance().forceSuspend()).isFalse();
+ }
}