diff options
| -rw-r--r-- | Android.mk | 2 | ||||
| -rw-r--r-- | api/system-current.txt | 5 | ||||
| -rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 14 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 10 | ||||
| -rw-r--r-- | core/java/android/os/IRecoverySystem.aidl | 28 | ||||
| -rw-r--r-- | core/java/android/os/IRecoverySystemProgressListener.aidl | 24 | ||||
| -rw-r--r-- | core/java/android/os/PowerManager.java | 18 | ||||
| -rw-r--r-- | core/java/android/os/RecoverySystem.java | 333 | ||||
| -rw-r--r-- | services/core/java/com/android/server/RecoverySystemService.java | 214 | ||||
| -rw-r--r-- | services/core/java/com/android/server/power/PowerManagerService.java | 12 | ||||
| -rw-r--r-- | services/core/java/com/android/server/power/ShutdownThread.java | 161 | ||||
| -rw-r--r-- | services/java/com/android/server/SystemServer.java | 8 |
12 files changed, 686 insertions, 143 deletions
diff --git a/Android.mk b/Android.mk index 918377178aac..97dfc1d0ca25 100644 --- a/Android.mk +++ b/Android.mk @@ -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 \ diff --git a/api/system-current.txt b/api/system-current.txt index b36d7e89cb8d..4e7157d0ff4e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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; } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 52fba3b7b840..307c3eb18362 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -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 diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 0cdbef0b770b..b935b256b880 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2863,6 +2863,16 @@ public abstract class Context { /** * 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 * manager. * diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl new file mode 100644 index 000000000000..12830a4996aa --- /dev/null +++ b/core/java/android/os/IRecoverySystem.aidl @@ -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(); +} diff --git a/core/java/android/os/IRecoverySystemProgressListener.aidl b/core/java/android/os/IRecoverySystemProgressListener.aidl new file mode 100644 index 000000000000..d6f712ea3240 --- /dev/null +++ b/core/java/android/os/IRecoverySystemProgressListener.aidl @@ -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); +} diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 314b7d5491ff..dcc28d6f01a9 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -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 @@ -398,6 +398,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. * @hide diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 154c9bbab312..ddcd63520549 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -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 @@ -287,6 +307,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. * Requires the {@link android.Manifest.permission#REBOOT} permission. @@ -301,30 +404,127 @@ public class RecoverySystem { * fails, or if the reboot itself fails. */ public static void installPackage(Context context, File packageFile) - throws IOException { - String filename = packageFile.getCanonicalPath(); + throws IOException { + installPackage(context, packageFile, false); + } - 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()); + /** + * 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?)"); } - Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); + } - // If the package is on the /data partition, write the block map file - // into COMMAND_FILE instead. + /** + * 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(); + + // 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(); - } - // Having written the command file, go ahead and reboot - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - pm.reboot(PowerManager.REBOOT_RECOVERY); + // Write the command into BCB (bootloader control block). + RecoverySystem rs = (RecoverySystem) context.getSystemService( + Context.RECOVERY_SERVICE); + rs.setupBcb(command.toString()); - throw new IOException("Reboot failed (no permissions?)"); + // 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?)"); + } } /** @@ -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])); } @@ -533,6 +734,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; + } } diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java new file mode 100644 index 000000000000..d237fe7b4c3e --- /dev/null +++ b/services/core/java/com/android/server/RecoverySystemService.java @@ -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; + } + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index dbaa598fb1b2..f901f9565fcc 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -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); } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index ac6a28e91ca8..26f9ffd8fa74 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -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; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index c8545732864b..c75f98f63c79 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -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 |