Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 90237f7b authored by Tao Bao's avatar Tao Bao
Browse files

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
parent beda8613
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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);
+4 −0
Original line number Diff line number Diff line
@@ -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>

+2 −0
Original line number Diff line number Diff line
@@ -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" />
+3 −10
Original line number Diff line number Diff line
@@ -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
+147 −10
Original line number Diff line number Diff line
@@ -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,6 +66,7 @@ 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;
@@ -67,6 +75,9 @@ public final class ShutdownThread extends Thread {
    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,6 +105,7 @@ public final class ShutdownThread extends Thread {
    private Handler mHandler;

    private static AlertDialog sConfirmDialog;
    private ProgressDialog mProgressDialog;

    private ShutdownThread() {
    }
@@ -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);
        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);

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