From 90237f7beb55dae79cdcba5271f96be778573737 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Thu, 21 May 2015 16:25:19 -0700 Subject: [PATCH] Wait for uncrypt to finish before rebooting /system/bin/uncrypt needs to be triggered to prepare the OTA package before rebooting into the recovery. For larger packages, uncrypt may be killed before it finishes the work after the timeout. Change to monitor the uncrypt status and show the progress to user. Needs matching changes in bootable/recovery/uncrypt, system/core and external/sepolicy. Bug: 20012567 Bug: 20949086 Change-Id: I2348a98312c4dae81f618b45a2ee3b4cf6246ff5 --- core/java/android/os/RecoverySystem.java | 14 ++ core/res/res/values/strings.xml | 4 + core/res/res/values/symbols.xml | 2 + .../server/power/PowerManagerService.java | 13 +- .../android/server/power/ShutdownThread.java | 157 ++++++++++++++++-- 5 files changed, 170 insertions(+), 20 deletions(-) diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 4aeab49e971b5..0c79094acdb18 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -71,6 +71,7 @@ public class RecoverySystem { /** Used to communicate with recovery. See bootable/recovery/recovery.c. */ private static File RECOVERY_DIR = new File("/cache/recovery"); private static File COMMAND_FILE = new File(RECOVERY_DIR, "command"); + private static File UNCRYPT_FILE = new File(RECOVERY_DIR, "uncrypt_file"); private static File LOG_FILE = new File(RECOVERY_DIR, "log"); private static String LAST_PREFIX = "last_"; @@ -333,8 +334,21 @@ public class RecoverySystem { public static void installPackage(Context context, File packageFile) throws IOException { String filename = packageFile.getCanonicalPath(); + + FileWriter uncryptFile = new FileWriter(UNCRYPT_FILE); + try { + uncryptFile.write(filename + "\n"); + } finally { + uncryptFile.close(); + } Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); + // If the package is on the /data partition, write the block map file + // into COMMAND_FILE instead. + if (filename.startsWith("/data/")) { + filename = "@/cache/recovery/block.map"; + } + final String filenameArg = "--update_package=" + filename; final String localeArg = "--locale=" + Locale.getDefault().toString(); bootCommand(context, filenameArg, localeArg); diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 0e6b2dfef5333..187fc1fd70c1b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -413,6 +413,10 @@ Ringer on + + Prepare for update + Processing the update package\u2026 + Shutting down\u2026 diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 64e396468fd5e..d8313d1a1d421 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -816,6 +816,8 @@ + + diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 74df0a0aac6b6..130aa96e5b8b0 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -2493,8 +2493,7 @@ public final class PowerManagerService extends SystemService /** * 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. + * the time a reboot is requested, this method returns. * * @param reason code to pass to the kernel (e.g. "recovery"), or null. */ @@ -2502,27 +2501,21 @@ public final class PowerManagerService extends SystemService if (reason == null) { 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. We have seen 750MB packages take 3-4 minutes SystemProperties.set("ctl.start", "pre-recovery"); - duration = 300 * 1000L; } else { SystemProperties.set("sys.powerctl", "reboot," + reason); - duration = 20 * 1000L; } try { - Thread.sleep(duration); + Thread.sleep(20 * 1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } + Slog.wtf(TAG, "Unexpected return from lowLevelReboot!"); } @Override // Watchdog.Monitor implementation diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index 84eab423bc231..85dd9d4c0b41c 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -14,7 +14,7 @@ * limitations under the License. */ - + package com.android.server.power; import android.app.ActivityManagerNative; @@ -44,6 +44,8 @@ import android.os.Vibrator; import android.os.SystemVibrator; import android.os.storage.IMountService; import android.os.storage.IMountShutdownObserver; +import android.system.ErrnoException; +import android.system.Os; import com.android.internal.telephony.ITelephony; import com.android.server.pm.PackageManagerService; @@ -51,6 +53,11 @@ import com.android.server.pm.PackageManagerService; import android.util.Log; import android.view.WindowManager; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + public final class ShutdownThread extends Thread { // constants private static final String TAG = "ShutdownThread"; @@ -59,14 +66,18 @@ public final class ShutdownThread extends Thread { private static final int MAX_BROADCAST_TIME = 10*1000; 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; // length of vibration before shutting down private static final int SHUTDOWN_VIBRATE_MS = 500; - + // state tracking private static Object sIsStartedGuard = new Object(); private static boolean sIsStarted = false; - + + // uncrypt status file + private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status"; + private static boolean mReboot; private static boolean mRebootSafeMode; private static String mRebootReason; @@ -94,10 +105,11 @@ public final class ShutdownThread extends Thread { private Handler mHandler; private static AlertDialog sConfirmDialog; - + private ProgressDialog mProgressDialog; + private ShutdownThread() { } - + /** * Request a clean shutdown, waiting for subsystems to clean up their * state etc. Must be called from a Looper thread in which its UI @@ -226,7 +238,11 @@ public final class ShutdownThread extends Thread { // throw up an indeterminate system dialog to indicate radio is // shutting down. ProgressDialog pd = new ProgressDialog(context); - pd.setTitle(context.getText(com.android.internal.R.string.power_off)); + if (mRebootReason.equals(PowerManager.REBOOT_RECOVERY)) { + pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_recovery_title)); + } 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.setCancelable(false); @@ -234,6 +250,7 @@ public final class ShutdownThread extends Thread { pd.show(); + sInstance.mProgressDialog = pd; sInstance.mContext = context; sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); @@ -307,14 +324,14 @@ public final class ShutdownThread extends Thread { } Log.i(TAG, "Sending shutdown broadcast..."); - + // First send the high-level shut down broadcast. mActionDone = false; Intent intent = new Intent(Intent.ACTION_SHUTDOWN); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, null, br, mHandler, 0, null, null); - + final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; synchronized (mActionDoneSync) { while (!mActionDone) { @@ -329,9 +346,9 @@ public final class ShutdownThread extends Thread { } } } - + Log.i(TAG, "Shutting down activity manager..."); - + final IActivityManager am = ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); if (am != null) { @@ -390,9 +407,55 @@ public final class ShutdownThread extends Thread { } } + // If it's to reboot into recovery, invoke uncrypt via init service. + if (mRebootReason.equals(PowerManager.REBOOT_RECOVERY)) { + 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) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mProgressDialog != null) { + mProgressDialog.setProgress(progress); + } + } + }); + } + private void shutdownRadios(int timeout) { // If a radio is wedged, disabling it may hang so we do this work in another thread, // just in case. @@ -537,4 +600,78 @@ public final class ShutdownThread extends Thread { Log.i(TAG, "Performing low-level shutdown..."); PowerManagerService.lowLevelShutdown(); } + + 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() { + @Override + public void run() { + // Create the status pipe file to communicate with /system/bin/uncrypt. + new File(UNCRYPT_STATUS_FILE).delete(); + try { + Os.mkfifo(UNCRYPT_STATUS_FILE, 0600); + } catch (ErrnoException e) { + Log.w(TAG, "ErrnoException when creating named pipe \"" + UNCRYPT_STATUS_FILE + + "\": " + e.getMessage()); + } + + SystemProperties.set("ctl.start", "uncrypt"); + + // Read the status from the pipe. + try (BufferedReader reader = new BufferedReader( + new FileReader(UNCRYPT_STATUS_FILE))) { + + int last_status = 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) { + continue; + } + last_status = status; + + if (status >= 0 && status < 100) { + // Update status + Log.d(TAG, "uncrypt read status: " + status); + sInstance.setUncryptProgress(status); + } else if (status == 100) { + Log.d(TAG, "uncrypt successfully finished."); + sInstance.setUncryptProgress(status); + break; + } else { + // Error in /system/bin/uncrypt. Or it's rebooting to recovery + // to perform other operations (e.g. factory reset). + Log.d(TAG, "uncrypt failed with status: " + status); + break; + } + } catch (NumberFormatException unused) { + Log.d(TAG, "uncrypt invalid status received: " + str); + break; + } + } + } catch (IOException unused) { + Log.w(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\"."); + } + done[0] = true; + } + }; + t.start(); + + try { + t.join(MAX_UNCRYPT_WAIT_TIME); + } catch (InterruptedException unused) { + } + if (!done[0]) { + Log.w(TAG, "Timed out waiting for uncrypt."); + } + } }