Merge "Add support for update-on-boot feature." into nyc-dev
This commit is contained in:
@ -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 \
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
28
core/java/android/os/IRecoverySystem.aidl
Normal file
28
core/java/android/os/IRecoverySystem.aidl
Normal 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();
|
||||
}
|
24
core/java/android/os/IRecoverySystemProgressListener.aidl
Normal file
24
core/java/android/os/IRecoverySystemProgressListener.aidl
Normal 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);
|
||||
}
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
214
services/core/java/com/android/server/RecoverySystemService.java
Normal file
214
services/core/java/com/android/server/RecoverySystemService.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user