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();
+    }
 }