Improve the reboot-to-recovery dialogs

To address the UX comments, this CL modifies the dialogs when the
device is rebooted to install an update or to perform factory data
reset.

Bug: 20949086
(cherry picked from commit 81dce6631396012e8b722bbed6d003f584800ae1)
Change-Id: I6d0cbbbaf184806366c6581c1c2e7dbcc5d89a17
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 60cbf41..11b8f0a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -394,8 +394,14 @@
     <string name="silent_mode_ring">Ringer on</string>
 
     <!-- Reboot to Recovery Progress Dialog. This is shown before it reboots to recovery. -->
-    <string name="reboot_to_recovery_title">Prepare for update</string>
-    <string name="reboot_to_recovery_progress">Processing the update package\u2026</string>
+    <string name="reboot_to_update_title">Android system update</string>
+    <string name="reboot_to_update_prepare">Preparing to update\u2026</string>
+    <string name="reboot_to_update_package">Processing the update package\u2026</string>
+    <string name="reboot_to_update_reboot">Restarting\u2026</string>
+
+    <!-- Reboot to Recovery for factory reset. -->
+    <string name="reboot_to_reset_title">Factory data reset</string>
+    <string name="reboot_to_reset_message">Restarting\u2026</string>
 
     <!-- Shutdown Progress Dialog. This is shown if the user chooses to power off the phone. -->
     <string name="shutdown_progress">Shutting down\u2026</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1bf2f53..8c1f4e2 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -780,8 +780,12 @@
   <java-symbol type="string" name="mobile_provisioning_url" />
   <java-symbol type="string" name="mobile_redirected_provisioning_url" />
   <java-symbol type="string" name="quick_contacts_not_available" />
-  <java-symbol type="string" name="reboot_to_recovery_progress" />
-  <java-symbol type="string" name="reboot_to_recovery_title" />
+  <java-symbol type="string" name="reboot_to_update_package" />
+  <java-symbol type="string" name="reboot_to_update_prepare" />
+  <java-symbol type="string" name="reboot_to_update_title" />
+  <java-symbol type="string" name="reboot_to_update_reboot" />
+  <java-symbol type="string" name="reboot_to_reset_title" />
+  <java-symbol type="string" name="reboot_to_reset_message" />
   <java-symbol type="string" name="reboot_safemode_confirm" />
   <java-symbol type="string" name="reboot_safemode_title" />
   <java-symbol type="string" name="relationTypeAssistant" />
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 927fc45..4c14c47 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -66,6 +66,12 @@
     private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
     private static final int MAX_RADIO_WAIT_TIME = 12*1000;
     private static final int MAX_UNCRYPT_WAIT_TIME = 15*60*1000;
+    // constants for progress bar. the values are roughly estimated based on timeout.
+    private static final int BROADCAST_STOP_PERCENT = 2;
+    private static final int ACTIVITY_MANAGER_STOP_PERCENT = 4;
+    private static final int PACKAGE_MANAGER_STOP_PERCENT = 6;
+    private static final int RADIO_STOP_PERCENT = 18;
+    private static final int MOUNT_SERVICE_STOP_PERCENT = 20;
 
     // length of vibration before shutting down
     private static final int SHUTDOWN_VIBRATE_MS = 500;
@@ -74,11 +80,13 @@
     private static Object sIsStartedGuard = new Object();
     private static boolean sIsStarted = false;
 
-    // uncrypt status file
+    // uncrypt status files
     private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status";
+    private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
 
     private static boolean mReboot;
     private static boolean mRebootSafeMode;
+    private static boolean mRebootUpdate;
     private static String mRebootReason;
 
     // Provides shutdown assurance in case the system_server is killed
@@ -202,6 +210,7 @@
     public static void reboot(final Context context, String reason, boolean confirm) {
         mReboot = true;
         mRebootSafeMode = false;
+        mRebootUpdate = false;
         mRebootReason = reason;
         shutdownInner(context, confirm);
     }
@@ -216,6 +225,7 @@
     public static void rebootSafeMode(final Context context, boolean confirm) {
         mReboot = true;
         mRebootSafeMode = true;
+        mRebootUpdate = false;
         mRebootReason = null;
         shutdownInner(context, confirm);
     }
@@ -229,16 +239,44 @@
             sIsStarted = true;
         }
 
-        // throw up an indeterminate system dialog to indicate radio is
-        // shutting down.
+        // Throw up a system dialog to indicate the device is rebooting / shutting down.
         ProgressDialog pd = new ProgressDialog(context);
+
+        // Path 1: Reboot to recovery and install the update
+        //   Condition: mRebootReason == REBOOT_RECOVERY and mRebootUpdate == True
+        //   (mRebootUpdate is set by checking if /cache/recovery/uncrypt_file exists.)
+        //   UI: progress bar
+        //
+        // Path 2: Reboot to recovery for factory reset
+        //   Condition: mRebootReason == REBOOT_RECOVERY
+        //   UI: spinning circle only (no progress bar)
+        //
+        // Path 3: Regular reboot / shutdown
+        //   Condition: Otherwise
+        //   UI: spinning circle only (no progress bar)
         if (PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) {
-            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_recovery_title));
+            mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists();
+            if (mRebootUpdate) {
+                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
+                pd.setMessage(context.getText(
+                        com.android.internal.R.string.reboot_to_update_prepare));
+                pd.setMax(100);
+                pd.setProgressNumberFormat(null);
+                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+                pd.setProgress(0);
+                pd.setIndeterminate(false);
+            } else {
+                // Factory reset path. Set the dialog message accordingly.
+                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
+                pd.setMessage(context.getText(
+                        com.android.internal.R.string.reboot_to_reset_message));
+                pd.setIndeterminate(true);
+            }
         } else {
             pd.setTitle(context.getText(com.android.internal.R.string.power_off));
+            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
+            pd.setIndeterminate(true);
         }
-        pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
-        pd.setIndeterminate(true);
         pd.setCancelable(false);
         pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
 
@@ -333,13 +371,20 @@
                 if (delay <= 0) {
                     Log.w(TAG, "Shutdown broadcast timed out");
                     break;
+                } else if (mRebootUpdate) {
+                    int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
+                            BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
+                    sInstance.setRebootProgress(status, null);
                 }
                 try {
-                    mActionDoneSync.wait(delay);
+                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
                 } catch (InterruptedException e) {
                 }
             }
         }
+        if (mRebootUpdate) {
+            sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
+        }
 
         Log.i(TAG, "Shutting down activity manager...");
 
@@ -351,6 +396,9 @@
             } catch (RemoteException e) {
             }
         }
+        if (mRebootUpdate) {
+            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
+        }
 
         Log.i(TAG, "Shutting down package manager...");
 
@@ -359,9 +407,15 @@
         if (pm != null) {
             pm.shutdown();
         }
+        if (mRebootUpdate) {
+            sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
+        }
 
         // Shutdown radios.
         shutdownRadios(MAX_RADIO_WAIT_TIME);
+        if (mRebootUpdate) {
+            sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
+        }
 
         // Shutdown MountService to ensure media is in a safe state
         IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
@@ -393,64 +447,44 @@
                 if (delay <= 0) {
                     Log.w(TAG, "Shutdown wait timed out");
                     break;
+                } else if (mRebootUpdate) {
+                    int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
+                            (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
+                            MAX_SHUTDOWN_WAIT_TIME);
+                    status += RADIO_STOP_PERCENT;
+                    sInstance.setRebootProgress(status, null);
                 }
                 try {
-                    mActionDoneSync.wait(delay);
+                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
                 } catch (InterruptedException e) {
                 }
             }
         }
+        if (mRebootUpdate) {
+            sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
 
-        // If it's to reboot into recovery, invoke uncrypt via init service.
-        if (PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) {
+            // If it's to reboot to install update, invoke uncrypt via init service.
             uncrypt();
         }
 
         rebootOrShutdown(mContext, mReboot, mRebootReason);
     }
 
-    private void prepareUncryptProgress() {
-        // Reset the dialog message to show the decrypt process.
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mProgressDialog != null) {
-                    mProgressDialog.dismiss();
-                }
-                // It doesn't work to change the style of the existing
-                // one. Have to create a new one.
-                ProgressDialog pd = new ProgressDialog(mContext);
-
-                pd.setTitle(mContext.getText(
-                        com.android.internal.R.string.reboot_to_recovery_title));
-                pd.setMessage(mContext.getText(
-                        com.android.internal.R.string.reboot_to_recovery_progress));
-                pd.setIndeterminate(false);
-                pd.setMax(100);
-                pd.setCancelable(false);
-                pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
-                pd.setProgressNumberFormat(null);
-                pd.setProgress(0);
-
-                mProgressDialog = pd;
-                mProgressDialog.show();
-            }
-        });
-    }
-
-    private void setUncryptProgress(final int progress) {
+    private void setRebootProgress(final int progress, final CharSequence message) {
         mHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (mProgressDialog != null) {
                     mProgressDialog.setProgress(progress);
+                    if (message != null) {
+                        mProgressDialog.setMessage(message);
+                    }
                 }
             }
         });
     }
 
-    private void shutdownRadios(int timeout) {
+    private void shutdownRadios(final int timeout) {
         // If a radio is wedged, disabling it may hang so we do this work in another thread,
         // just in case.
         final long endTime = SystemClock.elapsedRealtime() + timeout;
@@ -505,7 +539,15 @@
 
                 Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
 
-                while (SystemClock.elapsedRealtime() < endTime) {
+                long delay = endTime - SystemClock.elapsedRealtime();
+                while (delay > 0) {
+                    if (mRebootUpdate) {
+                        int status = (int)((timeout - delay) * 1.0 *
+                                (RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
+                        status += PACKAGE_MANAGER_STOP_PERCENT;
+                        sInstance.setRebootProgress(status, null);
+                    }
+
                     if (!bluetoothOff) {
                         try {
                             bluetoothOff = !bluetooth.isEnabled();
@@ -546,6 +588,8 @@
                         break;
                     }
                     SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
+
+                    delay = endTime - SystemClock.elapsedRealtime();
                 }
             }
         };
@@ -598,9 +642,6 @@
     private void uncrypt() {
         Log.i(TAG, "Calling uncrypt and monitoring the progress...");
 
-        // Update the ProcessDialog message and style.
-        sInstance.prepareUncryptProgress();
-
         final boolean[] done = new boolean[1];
         done[0] = false;
         Thread t = new Thread() {
@@ -621,25 +662,32 @@
                 try (BufferedReader reader = new BufferedReader(
                         new FileReader(UNCRYPT_STATUS_FILE))) {
 
-                    int last_status = Integer.MIN_VALUE;
+                    int lastStatus = Integer.MIN_VALUE;
                     while (true) {
                         String str = reader.readLine();
                         try {
                             int status = Integer.parseInt(str);
 
                             // Avoid flooding the log with the same message.
-                            if (status == last_status && last_status != Integer.MIN_VALUE) {
+                            if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
                                 continue;
                             }
-                            last_status = status;
+                            lastStatus = status;
 
                             if (status >= 0 && status < 100) {
                                 // Update status
                                 Log.d(TAG, "uncrypt read status: " + status);
-                                sInstance.setUncryptProgress(status);
+                                // Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
+                                status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
+                                status += MOUNT_SERVICE_STOP_PERCENT;
+                                CharSequence msg = mContext.getText(
+                                        com.android.internal.R.string.reboot_to_update_package);
+                                sInstance.setRebootProgress(status, msg);
                             } else if (status == 100) {
                                 Log.d(TAG, "uncrypt successfully finished.");
-                                sInstance.setUncryptProgress(status);
+                                CharSequence msg = mContext.getText(
+                                        com.android.internal.R.string.reboot_to_update_reboot);
+                                sInstance.setRebootProgress(status, msg);
                                 break;
                             } else {
                                 // Error in /system/bin/uncrypt. Or it's rebooting to recovery