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