Merge "Add support for update-on-boot feature." into nyc-dev

This commit is contained in:
Tao Bao
2016-02-22 19:24:23 +00:00
committed by Android (Google) Code Review
12 changed files with 688 additions and 145 deletions

View File

@ -223,6 +223,8 @@ LOCAL_SRC_FILES += \
core/java/android/os/IPermissionController.aidl \
core/java/android/os/IProcessInfoService.aidl \
core/java/android/os/IPowerManager.aidl \
core/java/android/os/IRecoverySystem.aidl \
core/java/android/os/IRecoverySystemProgressListener.aidl \
core/java/android/os/IRemoteCallback.aidl \
core/java/android/os/ISchedulingPolicyService.aidl \
core/java/android/os/IUpdateLock.aidl \

View File

@ -31418,9 +31418,14 @@ package android.os {
}
public class RecoverySystem {
method public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
method public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException;
method public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException;
method public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
method public static void rebootWipeUserData(android.content.Context) throws java.io.IOException;
method public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
method public static void verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File) throws java.security.GeneralSecurityException, java.io.IOException;
}

View File

@ -91,9 +91,11 @@ import android.os.HardwarePropertiesManager;
import android.os.IBinder;
import android.os.IHardwarePropertiesManager;
import android.os.IPowerManager;
import android.os.IRecoverySystem;
import android.os.IUserManager;
import android.os.PowerManager;
import android.os.Process;
import android.os.RecoverySystem;
import android.os.ServiceManager;
import android.os.SystemVibrator;
import android.os.UserHandle;
@ -380,6 +382,18 @@ final class SystemServiceRegistry {
service, ctx.mMainThread.getHandler());
}});
registerService(Context.RECOVERY_SERVICE, RecoverySystem.class,
new CachedServiceFetcher<RecoverySystem>() {
@Override
public RecoverySystem createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(Context.RECOVERY_SERVICE);
IRecoverySystem service = IRecoverySystem.Stub.asInterface(b);
if (service == null) {
Log.wtf(TAG, "Failed to get recovery service.");
}
return new RecoverySystem(service);
}});
registerService(Context.SEARCH_SERVICE, SearchManager.class,
new CachedServiceFetcher<SearchManager>() {
@Override

View File

@ -2861,6 +2861,16 @@ public abstract class Context {
*/
public static final String POWER_SERVICE = "power";
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.os.RecoverySystem} for accessing the recovery system
* service.
*
* @see #getSystemService
* @hide
*/
public static final String RECOVERY_SERVICE = "recovery";
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.WindowManager} for accessing the system's window

View File

@ -0,0 +1,28 @@
/* //device/java/android/android/os/IRecoverySystem.aidl
**
** Copyright 2016, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.os;
import android.os.IRecoverySystemProgressListener;
/** @hide */
interface IRecoverySystem {
boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
boolean setupBcb(in String command);
boolean clearBcb();
}

View File

@ -0,0 +1,24 @@
/* //device/java/android/android/os/IRecoverySystemProgressListener.aidl
**
** Copyright 2016, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.os;
/** @hide */
oneway interface IRecoverySystemProgressListener {
void onProgress(int progress);
}

View File

@ -385,9 +385,9 @@ public final class PowerManager {
public static final int GO_TO_SLEEP_FLAG_NO_DOZE = 1 << 0;
/**
* The value to pass as the 'reason' argument to reboot() to
* reboot into recovery mode (for applying system updates, doing
* factory resets, etc.).
* The value to pass as the 'reason' argument to reboot() to reboot into
* recovery mode for tasks other than applying system updates, such as
* doing factory resets.
* <p>
* Requires the {@link android.Manifest.permission#RECOVERY}
* permission (in addition to
@ -397,6 +397,18 @@ public final class PowerManager {
*/
public static final String REBOOT_RECOVERY = "recovery";
/**
* The value to pass as the 'reason' argument to reboot() to reboot into
* recovery mode for applying system updates.
* <p>
* Requires the {@link android.Manifest.permission#RECOVERY}
* permission (in addition to
* {@link android.Manifest.permission#REBOOT}).
* </p>
* @hide
*/
public static final String REBOOT_RECOVERY_UPDATE = "recovery-update";
/**
* The value to pass as the 'reason' argument to reboot() when device owner requests a reboot on
* the device.

View File

@ -16,6 +16,7 @@
package android.os;
import android.annotation.SystemApi;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -66,15 +67,34 @@ public class RecoverySystem {
private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
/** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */
private static File RECOVERY_DIR = new File("/cache/recovery");
private static File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
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_";
private static final File RECOVERY_DIR = new File("/cache/recovery");
private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
private static final String LAST_PREFIX = "last_";
/**
* The recovery image uses this file to identify the location (i.e. blocks)
* of an OTA package on the /data partition. The block map file is
* generated by uncrypt.
*
* @hide
*/
public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
/**
* UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be
* read by uncrypt.
*
* @hide
*/
public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
// Length limits for reading files.
private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
private static final int LOG_FILE_MAX_LENGTH = 64 * 1024;
// Prevent concurrent execution of requests.
private static final Object sRequestLock = new Object();
private final IRecoverySystem mService;
/**
* Interface definition for a callback to be invoked regularly as
@ -286,6 +306,89 @@ public class RecoverySystem {
}
}
/**
* Process a given package with uncrypt. No-op if the package is not on the
* /data partition.
*
* @param Context the Context to use
* @param packageFile the package to be processed
* @param listener an object to receive periodic progress updates as
* processing proceeds. May be null.
* @param handler the Handler upon which the callbacks will be
* executed.
*
* @throws IOException if there were any errors processing the package file.
*
* @hide
*/
@SystemApi
public static void processPackage(Context context,
File packageFile,
final ProgressListener listener,
final Handler handler)
throws IOException {
String filename = packageFile.getCanonicalPath();
if (!filename.startsWith("/data/")) {
return;
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
IRecoverySystemProgressListener progressListener = null;
if (listener != null) {
final Handler progressHandler;
if (handler != null) {
progressHandler = handler;
} else {
progressHandler = new Handler(context.getMainLooper());
}
progressListener = new IRecoverySystemProgressListener.Stub() {
int lastProgress = 0;
long lastPublishTime = System.currentTimeMillis();
@Override
public void onProgress(final int progress) {
final long now = System.currentTimeMillis();
progressHandler.post(new Runnable() {
@Override
public void run() {
if (progress > lastProgress &&
now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
lastProgress = progress;
lastPublishTime = now;
listener.onProgress(progress);
}
}
});
}
};
}
if (!rs.uncrypt(filename, progressListener)) {
throw new IOException("process package failed");
}
}
/**
* Process a given package with uncrypt. No-op if the package is not on the
* /data partition.
*
* @param Context the Context to use
* @param packageFile the package to be processed
* @param listener an object to receive periodic progress updates as
* processing proceeds. May be null.
*
* @throws IOException if there were any errors processing the package file.
*
* @hide
*/
@SystemApi
public static void processPackage(Context context,
File packageFile,
final ProgressListener listener)
throws IOException {
processPackage(context, packageFile, listener, null);
}
/**
* Reboots the device in order to install the given update
* package.
@ -301,30 +404,127 @@ public class RecoverySystem {
* fails, or if the reboot itself fails.
*/
public static void installPackage(Context context, File packageFile)
throws IOException {
throws IOException {
installPackage(context, packageFile, false);
}
/**
* If the package hasn't been processed (i.e. uncrypt'd), set up
* UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the
* reboot.
*
* @param context the Context to use
* @param packageFile the update package to install. Must be on a
* partition mountable by recovery.
* @param processed if the package has been processed (uncrypt'd).
*
* @throws IOException if writing the recovery command file fails, or if
* the reboot itself fails.
*
* @hide
*/
@SystemApi
public static void installPackage(Context context, File packageFile, boolean processed)
throws IOException {
synchronized (sRequestLock) {
LOG_FILE.delete();
// Must delete the file in case it was created by system server.
UNCRYPT_PACKAGE_FILE.delete();
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
if (!processed && filename.startsWith("/data/")) {
FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
try {
uncryptFile.write(filename + "\n");
} finally {
uncryptFile.close();
}
// UNCRYPT_PACKAGE_FILE needs to be readable and writable by system server.
if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
|| !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
}
BLOCK_MAP_FILE.delete();
}
// If the package is on the /data partition, use the block map file as
// the package name instead.
if (filename.startsWith("/data/")) {
filename = "@/cache/recovery/block.map";
}
final String filenameArg = "--update_package=" + filename + "\n";
final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
final String command = filenameArg + localeArg;
RecoverySystem rs = (RecoverySystem) context.getSystemService(
Context.RECOVERY_SERVICE);
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
// Having set up the BCB (bootloader control block), go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
throw new IOException("Reboot failed (no permissions?)");
}
}
/**
* Schedule to install the given package on next boot. The caller needs to
* ensure that the package must have been processed (uncrypt'd) if needed.
* It sets up the command in BCB (bootloader control block), which will
* be read by the bootloader and the recovery image.
*
* @param Context the Context to use.
* @param packageFile the package to be installed.
*
* @throws IOException if there were any errors setting up the BCB.
*
* @hide
*/
@SystemApi
public static void scheduleUpdateOnBoot(Context context, File packageFile)
throws IOException {
String filename = packageFile.getCanonicalPath();
FileWriter uncryptFile = new FileWriter(UNCRYPT_FILE);
try {
uncryptFile.write(filename + "\n");
} finally {
uncryptFile.close();
}
// UNCRYPT_FILE needs to be readable by system server on bootup.
if (!UNCRYPT_FILE.setReadable(true, false)) {
Log.e(TAG, "Error setting readable for " + UNCRYPT_FILE.getCanonicalPath());
}
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 the package is on the /data partition, use the block map file as
// the package name 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);
final String filenameArg = "--update_package=" + filename + "\n";
final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
final String command = filenameArg + localeArg;
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
if (!rs.setupBcb(command)) {
throw new IOException("schedule update on boot failed");
}
}
/**
* Cancel any scheduled update by clearing up the BCB (bootloader control
* block).
*
* @param Context the Context to use.
*
* @throws IOException if there were any errors clearing up the BCB.
*
* @hide
*/
@SystemApi
public static void cancelScheduledUpdate(Context context)
throws IOException {
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
if (!rs.clearBcb()) {
throw new IOException("cancel scheduled update failed");
}
}
/**
@ -434,27 +634,28 @@ public class RecoverySystem {
* @throws IOException if something goes wrong.
*/
private static void bootCommand(Context context, String... args) throws IOException {
RECOVERY_DIR.mkdirs(); // In case we need it
COMMAND_FILE.delete(); // In case it's not writable
LOG_FILE.delete();
synchronized (sRequestLock) {
LOG_FILE.delete();
FileWriter command = new FileWriter(COMMAND_FILE);
try {
StringBuilder command = new StringBuilder();
for (String arg : args) {
if (!TextUtils.isEmpty(arg)) {
command.write(arg);
command.write("\n");
command.append(arg);
command.append("\n");
}
}
} finally {
command.close();
// Write the command into BCB (bootloader control block).
RecoverySystem rs = (RecoverySystem) context.getSystemService(
Context.RECOVERY_SERVICE);
rs.setupBcb(command.toString());
// Having set up the BCB, go ahead and reboot.
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);
throw new IOException("Reboot failed (no permissions?)");
}
// Having written the command file, go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);
throw new IOException("Reboot failed (no permissions?)");
}
/**
@ -476,10 +677,10 @@ public class RecoverySystem {
// Only remove the OTA package if it's partially processed (uncrypt'd).
boolean reservePackage = BLOCK_MAP_FILE.exists();
if (!reservePackage && UNCRYPT_FILE.exists()) {
if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) {
String filename = null;
try {
filename = FileUtils.readTextFile(UNCRYPT_FILE, 0, null);
filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null);
} catch (IOException e) {
Log.e(TAG, "Error reading uncrypt file", e);
}
@ -487,7 +688,7 @@ public class RecoverySystem {
// Remove the OTA package on /data that has been (possibly
// partially) processed. (Bug: 24973532)
if (filename != null && filename.startsWith("/data")) {
if (UNCRYPT_FILE.delete()) {
if (UNCRYPT_PACKAGE_FILE.delete()) {
Log.i(TAG, "Deleted: " + filename);
} else {
Log.e(TAG, "Can't delete: " + filename);
@ -499,13 +700,13 @@ public class RecoverySystem {
// the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE
// will be created at the end of a successful uncrypt. If seeing this
// file, we keep the block map file and the file that contains the
// package name (UNCRYPT_FILE). This is to reduce the work for GmsCore
// to avoid re-downloading everything again.
// package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for
// GmsCore to avoid re-downloading everything again.
String[] names = RECOVERY_DIR.list();
for (int i = 0; names != null && i < names.length; i++) {
if (names[i].startsWith(LAST_PREFIX)) continue;
if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
if (reservePackage && names[i].equals(UNCRYPT_FILE.getName())) continue;
if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue;
recursiveDelete(new File(RECOVERY_DIR, names[i]));
}
@ -532,6 +733,39 @@ public class RecoverySystem {
}
}
/**
* Talks to RecoverySystemService via Binder to trigger uncrypt.
*/
private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) {
try {
return mService.uncrypt(packageFile, listener);
} catch (RemoteException unused) {
}
return false;
}
/**
* Talks to RecoverySystemService via Binder to set up the BCB.
*/
private boolean setupBcb(String command) {
try {
return mService.setupBcb(command);
} catch (RemoteException unused) {
}
return false;
}
/**
* Talks to RecoverySystemService via Binder to clear up the BCB.
*/
private boolean clearBcb() {
try {
return mService.clearBcb();
} catch (RemoteException unused) {
}
return false;
}
/**
* Internally, recovery treats each line of the command file as a separate
* argv, so we only need to protect against newlines and nulls.
@ -546,5 +780,14 @@ public class RecoverySystem {
/**
* @removed Was previously made visible by accident.
*/
public RecoverySystem() { }
public RecoverySystem() {
mService = null;
}
/**
* @hide
*/
public RecoverySystem(IRecoverySystem service) {
mService = service;
}
}

View File

@ -0,0 +1,214 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server;
import android.content.Context;
import android.os.IRecoverySystem;
import android.os.IRecoverySystemProgressListener;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Slog;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* The recovery system service is responsible for coordinating recovery related
* functions on the device. It sets up (or clears) the bootloader control block
* (BCB), which will be read by the bootloader and the recovery image. It also
* triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the
* /data partition so that it can be accessed under the recovery image.
*/
public final class RecoverySystemService extends SystemService {
private static final String TAG = "RecoverySystemService";
private static final boolean DEBUG = false;
// A pipe file to monitor the uncrypt progress.
private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status";
// Temporary command file to communicate between the system server and uncrypt.
private static final String COMMAND_FILE = "/cache/recovery/command";
private Context mContext;
public RecoverySystemService(Context context) {
super(context);
mContext = context;
}
@Override
public void onStart() {
publishBinderService(Context.RECOVERY_SERVICE, new BinderService());
}
private final class BinderService extends IRecoverySystem.Stub {
@Override // Binder call
public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
// Write the filename into UNCRYPT_PACKAGE_FILE to be read by
// uncrypt.
RecoverySystem.UNCRYPT_PACKAGE_FILE.delete();
try (FileWriter uncryptFile = new FileWriter(RecoverySystem.UNCRYPT_PACKAGE_FILE)) {
uncryptFile.write(filename + "\n");
} catch (IOException e) {
Slog.e(TAG, "IOException when writing \"" + RecoverySystem.UNCRYPT_PACKAGE_FILE +
"\": " + e.getMessage());
return false;
}
// Create the status pipe file to communicate with uncrypt.
new File(UNCRYPT_STATUS_FILE).delete();
try {
Os.mkfifo(UNCRYPT_STATUS_FILE, 0600);
} catch (ErrnoException e) {
Slog.e(TAG, "ErrnoException when creating named pipe \"" + UNCRYPT_STATUS_FILE +
"\": " + e.getMessage());
return false;
}
// Trigger uncrypt via init.
SystemProperties.set("ctl.start", "uncrypt");
// Read the status from the pipe.
try (BufferedReader reader = new BufferedReader(new FileReader(UNCRYPT_STATUS_FILE))) {
int lastStatus = 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 == lastStatus && lastStatus != Integer.MIN_VALUE) {
continue;
}
lastStatus = status;
if (status >= 0 && status <= 100) {
// Update status
Slog.i(TAG, "uncrypt read status: " + status);
if (listener != null) {
try {
listener.onProgress(status);
} catch (RemoteException unused) {
Slog.w(TAG, "RemoteException when posting progress");
}
}
if (status == 100) {
Slog.i(TAG, "uncrypt successfully finished.");
break;
}
} else {
// Error in /system/bin/uncrypt.
Slog.e(TAG, "uncrypt failed with status: " + status);
return false;
}
} catch (NumberFormatException unused) {
Slog.e(TAG, "uncrypt invalid status received: " + str);
return false;
}
}
} catch (IOException unused) {
Slog.e(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\".");
return false;
}
return true;
}
@Override // Binder call
public boolean clearBcb() {
if (DEBUG) Slog.d(TAG, "clearBcb");
return setupOrClearBcb(false, null);
}
@Override // Binder call
public boolean setupBcb(String command) {
if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
return setupOrClearBcb(true, command);
}
private boolean setupOrClearBcb(boolean isSetup, String command) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
if (isSetup) {
// Set up the command file to be read by uncrypt.
try (FileWriter commandFile = new FileWriter(COMMAND_FILE)) {
commandFile.write(command + "\n");
} catch (IOException e) {
Slog.e(TAG, "IOException when writing \"" + COMMAND_FILE +
"\": " + e.getMessage());
return false;
}
}
// Create the status pipe file to communicate with uncrypt.
new File(UNCRYPT_STATUS_FILE).delete();
try {
Os.mkfifo(UNCRYPT_STATUS_FILE, 0600);
} catch (ErrnoException e) {
Slog.e(TAG, "ErrnoException when creating named pipe \"" +
UNCRYPT_STATUS_FILE + "\": " + e.getMessage());
return false;
}
if (isSetup) {
SystemProperties.set("ctl.start", "setup-bcb");
} else {
SystemProperties.set("ctl.start", "clear-bcb");
}
// Read the status from the pipe.
try (BufferedReader reader = new BufferedReader(new FileReader(UNCRYPT_STATUS_FILE))) {
while (true) {
String str = reader.readLine();
try {
int status = Integer.parseInt(str);
if (status == 100) {
Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +
" bcb successfully finished.");
break;
} else {
// Error in /system/bin/uncrypt.
Slog.e(TAG, "uncrypt failed with status: " + status);
return false;
}
} catch (NumberFormatException unused) {
Slog.e(TAG, "uncrypt invalid status received: " + str);
return false;
}
}
} catch (IOException unused) {
Slog.e(TAG, "IOException when reading \"" + UNCRYPT_STATUS_FILE + "\".");
return false;
}
// Delete the command file as we don't need it anymore.
new File(COMMAND_FILE).delete();
return true;
}
}
}

View File

@ -2703,12 +2703,9 @@ public final class PowerManagerService extends SystemService
if (reason == null) {
reason = "";
}
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.
SystemProperties.set("ctl.start", "pre-recovery");
if (reason.equals(PowerManager.REBOOT_RECOVERY)
|| reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) {
SystemProperties.set("sys.powerctl", "reboot,recovery");
} else {
SystemProperties.set("sys.powerctl", "reboot," + reason);
}
@ -3421,7 +3418,8 @@ public final class PowerManagerService extends SystemService
@Override // Binder call
public void reboot(boolean confirm, String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
if (PowerManager.REBOOT_RECOVERY.equals(reason)) {
if (PowerManager.REBOOT_RECOVERY.equals(reason)
|| PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
}

View File

@ -32,8 +32,10 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.FileUtils;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@ -81,13 +83,9 @@ public final class ShutdownThread extends Thread {
private static Object sIsStartedGuard = new Object();
private static boolean sIsStarted = false;
// uncrypt status files
private static final String UNCRYPT_STATUS_FILE = "/cache/recovery/uncrypt_status";
private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
private static boolean mReboot;
private static boolean mRebootSafeMode;
private static boolean mRebootUpdate;
private static boolean mRebootHasProgressBar;
private static String mReason;
// Provides shutdown assurance in case the system_server is killed
@ -213,7 +211,7 @@ public final class ShutdownThread extends Thread {
public static void reboot(final Context context, String reason, boolean confirm) {
mReboot = true;
mRebootSafeMode = false;
mRebootUpdate = false;
mRebootHasProgressBar = false;
mReason = reason;
shutdownInner(context, confirm);
}
@ -233,7 +231,7 @@ public final class ShutdownThread extends Thread {
mReboot = true;
mRebootSafeMode = true;
mRebootUpdate = false;
mRebootHasProgressBar = false;
mReason = null;
shutdownInner(context, confirm);
}
@ -250,10 +248,19 @@ public final class ShutdownThread extends Thread {
// Throw up a system dialog to indicate the device is rebooting / shutting down.
ProgressDialog pd = new ProgressDialog(context);
// Path 1: Reboot to recovery and install the update
// Condition: mReason == REBOOT_RECOVERY and mRebootUpdate == True
// (mRebootUpdate is set by checking if /cache/recovery/uncrypt_file exists.)
// UI: progress bar
// Path 1: Reboot to recovery for update
// Condition: mReason == REBOOT_RECOVERY_UPDATE
//
// Path 1a: uncrypt needed
// Condition: if /cache/recovery/uncrypt_file exists but
// /cache/recovery/block.map doesn't.
// UI: determinate progress bar (mRebootHasProgressBar == True)
//
// * Path 1a is expected to be removed once the GmsCore shipped on
// device always calls uncrypt prior to reboot.
//
// Path 1b: uncrypt already done
// UI: spinning circle only (no progress bar)
//
// Path 2: Reboot to recovery for factory reset
// Condition: mReason == REBOOT_RECOVERY
@ -262,24 +269,31 @@ public final class ShutdownThread extends Thread {
// Path 3: Regular reboot / shutdown
// Condition: Otherwise
// UI: spinning circle only (no progress bar)
if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists();
if (mRebootUpdate) {
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_update_prepare));
if (PowerManager.REBOOT_RECOVERY_UPDATE.equals(mReason)) {
// We need the progress bar if uncrypt will be invoked during the
// reboot, which might be time-consuming.
mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
&& !(RecoverySystem.BLOCK_MAP_FILE.exists());
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
if (mRebootHasProgressBar) {
pd.setMax(100);
pd.setProgressNumberFormat(null);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setProgress(0);
pd.setIndeterminate(false);
} else {
// Factory reset path. Set the dialog message accordingly.
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
pd.setProgressNumberFormat(null);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_reset_message));
com.android.internal.R.string.reboot_to_update_prepare));
} else {
pd.setIndeterminate(true);
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_update_reboot));
}
} else if (PowerManager.REBOOT_RECOVERY.equals(mReason)) {
// Factory reset path. Set the dialog message accordingly.
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_reset_message));
pd.setIndeterminate(true);
} else {
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
@ -379,7 +393,7 @@ public final class ShutdownThread extends Thread {
if (delay <= 0) {
Log.w(TAG, "Shutdown broadcast timed out");
break;
} else if (mRebootUpdate) {
} else if (mRebootHasProgressBar) {
int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
sInstance.setRebootProgress(status, null);
@ -390,7 +404,7 @@ public final class ShutdownThread extends Thread {
}
}
}
if (mRebootUpdate) {
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
}
@ -404,7 +418,7 @@ public final class ShutdownThread extends Thread {
} catch (RemoteException e) {
}
}
if (mRebootUpdate) {
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
}
@ -415,13 +429,13 @@ public final class ShutdownThread extends Thread {
if (pm != null) {
pm.shutdown();
}
if (mRebootUpdate) {
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
}
// Shutdown radios.
shutdownRadios(MAX_RADIO_WAIT_TIME);
if (mRebootUpdate) {
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
}
@ -455,7 +469,7 @@ public final class ShutdownThread extends Thread {
if (delay <= 0) {
Log.w(TAG, "Shutdown wait timed out");
break;
} else if (mRebootUpdate) {
} else if (mRebootHasProgressBar) {
int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
(MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
MAX_SHUTDOWN_WAIT_TIME);
@ -468,10 +482,11 @@ public final class ShutdownThread extends Thread {
}
}
}
if (mRebootUpdate) {
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
// If it's to reboot to install update, invoke uncrypt via init service.
// If it's to reboot to install an update and uncrypt hasn't been
// done yet, trigger it now.
uncrypt();
}
@ -549,7 +564,7 @@ public final class ShutdownThread extends Thread {
long delay = endTime - SystemClock.elapsedRealtime();
while (delay > 0) {
if (mRebootUpdate) {
if (mRebootHasProgressBar) {
int status = (int)((timeout - delay) * 1.0 *
(RADIO_STOP_PERCENT - PACKAGE_MANAGER_STOP_PERCENT) / timeout);
status += PACKAGE_MANAGER_STOP_PERCENT;
@ -651,66 +666,40 @@ public final class ShutdownThread extends Thread {
private void uncrypt() {
Log.i(TAG, "Calling uncrypt and monitoring the progress...");
final RecoverySystem.ProgressListener progressListener =
new RecoverySystem.ProgressListener() {
@Override
public void onProgress(int status) {
if (status >= 0 && status < 100) {
// Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
status += MOUNT_SERVICE_STOP_PERCENT;
CharSequence msg = mContext.getText(
com.android.internal.R.string.reboot_to_update_package);
sInstance.setRebootProgress(status, msg);
} else if (status == 100) {
CharSequence msg = mContext.getText(
com.android.internal.R.string.reboot_to_update_reboot);
sInstance.setRebootProgress(status, msg);
} else {
// Ignored
}
}
};
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();
RecoverySystem rs = (RecoverySystem) mContext.getSystemService(
Context.RECOVERY_SERVICE);
String filename = null;
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 lastStatus = 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 == lastStatus && lastStatus != Integer.MIN_VALUE) {
continue;
}
lastStatus = status;
if (status >= 0 && status < 100) {
// Update status
Log.d(TAG, "uncrypt read status: " + status);
// Scale down to [MOUNT_SERVICE_STOP_PERCENT, 100).
status = (int)(status * (100.0 - MOUNT_SERVICE_STOP_PERCENT) / 100);
status += MOUNT_SERVICE_STOP_PERCENT;
CharSequence msg = mContext.getText(
com.android.internal.R.string.reboot_to_update_package);
sInstance.setRebootProgress(status, msg);
} else if (status == 100) {
Log.d(TAG, "uncrypt successfully finished.");
CharSequence msg = mContext.getText(
com.android.internal.R.string.reboot_to_update_reboot);
sInstance.setRebootProgress(status, msg);
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 + "\".");
filename = FileUtils.readTextFile(RecoverySystem.UNCRYPT_PACKAGE_FILE, 0, null);
rs.processPackage(mContext, new File(filename), progressListener);
} catch (IOException e) {
Log.e(TAG, "Error uncrypting file", e);
}
done[0] = true;
}

View File

@ -341,8 +341,8 @@ public final class SystemServer {
// always make sure uncrypt gets executed properly when needed.
// If '/cache/recovery/block.map' hasn't been created, stop the
// reboot which will fail for sure, and get a chance to capture a
// bugreport when that's still feasible. (Bug; 26444951)
if (PowerManager.REBOOT_RECOVERY.equals(reason)) {
// bugreport when that's still feasible. (Bug: 26444951)
if (PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
File packageFile = new File(UNCRYPT_PACKAGE_FILE);
if (packageFile.exists()) {
String filename = null;
@ -833,6 +833,10 @@ public final class SystemServer {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
if (!disableNonCoreServices) {
mSystemServiceManager.startService(RecoverySystemService.class);
}
/*
* MountService has a few dependencies: Notification Manager and
* AppWidget Provider. Make sure MountService is completely started