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
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -413,6 +413,10 @@
|
||||
<!-- Spoken description for ringer normal option. [CHAR LIMIT=NONE] -->
|
||||
<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>
|
||||
|
||||
<!-- Shutdown Progress Dialog. This is shown if the user chooses to power off the phone. -->
|
||||
<string name="shutdown_progress">Shutting down\u2026</string>
|
||||
|
||||
|
||||
@@ -816,6 +816,8 @@
|
||||
<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_safemode_confirm" />
|
||||
<java-symbol type="string" name="reboot_safemode_title" />
|
||||
<java-symbol type="string" name="relationTypeAssistant" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user