diff options
author | Santos Cordon <santoscordon@google.com> | 2019-02-01 21:28:47 +0000 |
---|---|---|
committer | Santos Cordon <santoscordon@google.com> | 2019-02-15 17:23:18 +0000 |
commit | 12f92eb6cf222b9998c3a8affaa909a44b5c0ad0 (patch) | |
tree | acbbf0a68c368ec7346bb07c93e2207120c644d8 | |
parent | b68b8a84831d480c1c254dcb271f0f31f95f315f (diff) |
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
7 files changed, 236 insertions, 37 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index fb2b9e1e05fb..485974be35f6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5327,6 +5327,7 @@ package android.os { 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 d29e68e5a187..3180b77f5700 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.SystemClock; 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 @@ public class PowerCommand extends Svc.Command { + " 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 @@ public class PowerCommand extends Svc.Command { 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 bdef57518ac9..483c41a8e48a 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -74,4 +74,7 @@ interface IPowerManager // 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 cfe2d28f1024..6bd1f2b59fad 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -363,10 +363,15 @@ public final class PowerManager { 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 final class PowerManager { 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 @@ public final class PowerManager { 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 @@ public final class PowerManager { } /** + * 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 1782b6a8aa78..176dbbf6a965 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 @@ public final class PowerManagerService extends SystemService // 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 final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService } if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE - || !mBootCompleted || !mSystemReady) { + || !mBootCompleted || !mSystemReady || mForceSuspendActive) { return false; } @@ -1463,8 +1472,13 @@ public final class PowerManagerService extends SystemService } } - // 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService 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 @@ public final class PowerManagerService extends SystemService } } + 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 @@ public final class PowerManagerService extends SystemService } } + @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 9be728bac532..ec7a78beb122 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 void nativeSetFeature(JNIEnv* /* env */, jclass /* clazz */, jint feature } } +static bool nativeForceSuspend(JNIEnv* /* env */, jclass /* clazz */) { + bool retval = false; + getSuspendControl()->forceSuspend(&retval); + return retval; +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gPowerManagerServiceMethods[] = { @@ -303,6 +309,8 @@ static const JNINativeMethod gPowerManagerServiceMethods[] = { (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 63341b6ea38a..911c4a2f4122 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 android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; 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.junit.Rule; 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 @@ public class PowerManagerServiceTest extends AndroidTestCase { 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 @@ public class PowerManagerServiceTest extends AndroidTestCase { 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 @@ public class PowerManagerServiceTest extends AndroidTestCase { 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(); + } } |