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