From 717dbbf1a48e7aa722591de5044816f854150da5 Mon Sep 17 00:00:00 2001
From: Nikita Ioffe <ioffe@google.com>
Date: Wed, 18 Dec 2019 13:45:15 +0000
Subject: [PATCH] Add an API to tell whenever device supports userspace reboot

If device doesn't support userspace reboot then call to
PowerManager.reboot("userspace") will throw an
UnsupportedOperationException.

For the case of upgrading devices, Treble enforces that previous vendor
image should work with the new system image. Since userspace reboot
requires services to be stopped and restarted cleanly, which upgrading
devices might not be able to comply with, we need to have an API to
distinguish between devices that support userspace reboot and the ones
that do not.

Test: atest PowerManagerTest
Test: atest CtsUserspaceRebootHostSideTestCases
Bug: 138605273
Bug: 135984674
Change-Id: I8303f43ab29499eb2995f0256854c787055d9560
---
 api/current.txt                               |  3 ++-
 api/system-current.txt                        |  1 +
 core/java/android/os/PowerManager.java        | 24 ++++++++++++++++++-
 .../src/android/os/PowerManagerTest.java      | 13 ++++++++++
 4 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/api/current.txt b/api/current.txt
index f3f1cb928934f..f7053b62184d2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35016,11 +35016,12 @@ package android.os {
     method public boolean isIgnoringBatteryOptimizations(String);
     method public boolean isInteractive();
     method public boolean isPowerSaveMode();
+    method public boolean isRebootingUserspaceSupported();
     method @Deprecated public boolean isScreenOn();
     method public boolean isSustainedPerformanceModeSupported();
     method public boolean isWakeLockLevelSupported(int);
     method public android.os.PowerManager.WakeLock newWakeLock(int, String);
-    method public void reboot(String);
+    method public void reboot(@Nullable String);
     method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
     field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
diff --git a/api/system-current.txt b/api/system-current.txt
index e539e6cc60f62..fe7b3999d7b7f 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5986,6 +5986,7 @@ package android.os {
     method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int);
     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";
     field public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3; // 0x3
     field public static final int USER_ACTIVITY_EVENT_BUTTON = 1; // 0x1
     field public static final int USER_ACTIVITY_EVENT_OTHER = 0; // 0x0
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index ab4d424ac0532..c618dbc2b5515 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -20,6 +20,7 @@ import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
@@ -604,6 +605,13 @@ public final class PowerManager {
      */
     public static final String REBOOT_SAFE_MODE = "safemode";
 
+    /**
+     * The 'reason' value used for rebooting userspace.
+     * @hide
+     */
+    @SystemApi
+    public static final String REBOOT_USERSPACE = "userspace";
+
     /**
      * The 'reason' value used when rebooting the device without turning on the screen.
      * @hide
@@ -1382,6 +1390,14 @@ public final class PowerManager {
         }
     }
 
+    /**
+     * Returns {@code true} if this device supports rebooting userspace.
+     */
+    // TODO(b/138605180): add link to documentation once it's ready.
+    public boolean isRebootingUserspaceSupported() {
+        return SystemProperties.getBoolean("ro.init.userspace_reboot.is_supported", false);
+    }
+
     /**
      * Reboot the device.  Will not return if the reboot is successful.
      * <p>
@@ -1390,8 +1406,14 @@ public final class PowerManager {
      *
      * @param reason code to pass to the kernel (e.g., "recovery") to
      *               request special boot modes, or null.
+     * @throws UnsupportedOperationException if userspace reboot was requested on a device that
+     *                                       doesn't support it.
      */
-    public void reboot(String reason) {
+    public void reboot(@Nullable String reason) {
+        if (REBOOT_USERSPACE.equals(reason) && !isRebootingUserspaceSupported()) {
+            throw new UnsupportedOperationException(
+                    "Attempted userspace reboot on a device that doesn't support it");
+        }
         try {
             mService.reboot(false, reason, true);
         } catch (RemoteException e) {
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 576ac73bc1831..5cb7852f7acc7 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -239,4 +239,17 @@ public class PowerManagerTest extends AndroidTestCase {
         verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLI_SEC)
                 .times(1)).onThermalStatusChanged(status);
     }
+
+    @Test
+    public void testUserspaceRebootNotSupported_throwsUnsupportedOperationException() {
+        // Can't use assumption framework with AndroidTestCase :(
+        if (mPm.isRebootingUserspaceSupported()) {
+            return;
+        }
+        try {
+            mPm.reboot(PowerManager.REBOOT_USERSPACE);
+            fail("UnsupportedOperationException not thrown");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
 }
-- 
GitLab