boot into recovery via the pre-recovery service

Change PowerManagerService to start the pre-recovery service rather
than rebooting directly, when requested to reboot into recovery.  Add
a new RECOVERY permission which a caller needs (in addition to REBOOT)
in order to go to recovery.

Bug: 12188746
Change-Id: I39121b701c4724558fe751adfbad79f8567faa43
diff --git a/api/current.txt b/api/current.txt
index 2a90018..3715764 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -109,6 +109,7 @@
     field public static final java.lang.String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
     field public static final java.lang.String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
     field public static final java.lang.String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+    field public static final java.lang.String RECOVERY = "android.permission.RECOVERY";
     field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -18878,6 +18879,7 @@
     field public static final deprecated int FULL_WAKE_LOCK = 26; // 0x1a
     field public static final int ON_AFTER_RELEASE = 536870912; // 0x20000000
     field public static final int PARTIAL_WAKE_LOCK = 1; // 0x1
+    field public static final java.lang.String REBOOT_RECOVERY = "recovery";
     field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
     field public static final deprecated int SCREEN_DIM_WAKE_LOCK = 6; // 0x6
   }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 5e0d489..3a9611e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -290,6 +290,18 @@
      */
     public static final int GO_TO_SLEEP_REASON_TIMEOUT = 2;
 
+    /**
+     * The value to pass as the 'reason' argument to reboot() to
+     * reboot into recovery mode (for applying system updates, doing
+     * factory resets, etc.).
+     * <p>
+     * Requires the {@link android.Manifest.permission#RECOVERY}
+     * permission (in addition to
+     * {@link android.Manifest.permission#REBOOT}).
+     * </p>
+     */
+    public static final String REBOOT_RECOVERY = "recovery";
+    
     final Context mContext;
     final IPowerManager mService;
     final Handler mHandler;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ca74fa4..13f5e1c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1719,6 +1719,14 @@
         android:label="@string/permlab_manageCaCertificates"
         android:description="@string/permdesc_manageCaCertificates" />
 
+    <!-- Allows an application to do certain operations needed for
+         interacting with the recovery (system update) system. -->
+    <permission android:name="android.permission.RECOVERY"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signature|system"
+        android:label="@string/permlab_recovery"
+        android:description="@string/permdesc_recovery" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index bbe39094..abae8636 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3644,6 +3644,11 @@
     <!-- Description of an application permission that lets it control keyguard. -->
     <string name="permdesc_control_keyguard">Allows an application to control keguard.</string>
 
+    <!-- Title of an application permission that lets it interact with recovery. -->
+    <string name="permlab_recovery">Interact with update and recovery system</string>
+    <!-- Description of an application permission that lets it control keyguard. -->
+    <string name="permdesc_recovery">Allows an application to interact with the recovery system and system updates.</string>
+
     <!-- Shown in the tutorial for tap twice for zoom control. -->
     <string name="tutorial_double_tap_to_zoom_message_short">Touch twice for zoom control</string>
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 7f03cc0..3692d76 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1830,9 +1830,10 @@
     }
 
     /**
-     * Low-level function to reboot the device. On success, this function
-     * doesn't return. If more than 5 seconds passes from the time,
-     * a reboot is requested, this method returns.
+     * Low-level function to reboot the device. On success, this
+     * function doesn't return. If more than 20 seconds passes from
+     * the time a reboot is requested (120 seconds for reboot to
+     * recovery), this method returns.
      *
      * @param reason code to pass to the kernel (e.g. "recovery"), or null.
      */
@@ -1840,9 +1841,24 @@
         if (reason == null) {
             reason = "";
         }
-        SystemProperties.set("sys.powerctl", "reboot," + reason);
+        long duration;
+        if (reason.equals(PowerManager.REBOOT_RECOVERY)) {
+            // If we are rebooting to go into recovery, instead of
+            // setting sys.powerctl directly we'll start the
+            // pre-recovery service which will do some preparation for
+            // recovery and then reboot for us.
+            //
+            // This preparation can take more than 20 seconds if
+            // there's a very large update package, so lengthen the
+            // timeout.
+            SystemProperties.set("ctl.start", "pre-recovery");
+            duration = 120 * 1000L;
+        } else {
+            SystemProperties.set("sys.powerctl", "reboot," + reason);
+            duration = 20 * 1000L;
+        }
         try {
-            Thread.sleep(20000);
+            Thread.sleep(duration);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         }
@@ -2524,6 +2540,9 @@
         @Override // Binder call
         public void reboot(boolean confirm, String reason, boolean wait) {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
+            if (PowerManager.REBOOT_RECOVERY.equals(reason)) {
+                mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {