add recovery system interface to API
Adds android.os.RecoverySystem (marked as pending) to replace the (hidden) com.android.internal.os.RecoverySystem. RecoverySystem contains methods for: - verifying the signature of an update package - rebooting to install a package - rebooting to wipe user data (The reboot functions require "android.permission.REBOOT" and "android.permission.ACCESS_CACHE_FILESYSTEM".) Providing these simplifies implementation of OTA update for device builders. Change-Id: I63ce743b156e7a1a0327fd395b0e4a82c0eda79a
This commit is contained in:
420
core/java/android/os/RecoverySystem.java
Normal file
420
core/java/android/os/RecoverySystem.java
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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 java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.harmony.security.asn1.BerInputStream;
|
||||
import org.apache.harmony.security.pkcs7.ContentInfo;
|
||||
import org.apache.harmony.security.pkcs7.SignedData;
|
||||
import org.apache.harmony.security.pkcs7.SignerInfo;
|
||||
import org.apache.harmony.security.provider.cert.X509CertImpl;
|
||||
|
||||
/**
|
||||
* RecoverySystem contains methods for interacting with the Android
|
||||
* recovery system (the separate partition that can be used to install
|
||||
* system updates, wipe user data, etc.)
|
||||
*
|
||||
* @pending
|
||||
*/
|
||||
public class RecoverySystem {
|
||||
private static final String TAG = "RecoverySystem";
|
||||
|
||||
/**
|
||||
* Default location of zip file containing public keys (X509
|
||||
* certs) authorized to sign OTA updates.
|
||||
*/
|
||||
private static final File DEFAULT_KEYSTORE =
|
||||
new File("/system/etc/security/otacerts.zip");
|
||||
|
||||
/** Send progress to listeners no more often than this (in ms). */
|
||||
private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
|
||||
|
||||
/** Used to communicate with recovery. See bootable/recovery/recovery.c. */
|
||||
private static File RECOVERY_DIR = new File("/cache/recovery");
|
||||
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
|
||||
private static File LOG_FILE = new File(RECOVERY_DIR, "log");
|
||||
|
||||
// Length limits for reading files.
|
||||
private static int LOG_FILE_MAX_LENGTH = 8 * 1024;
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked regularly as
|
||||
* verification proceeds.
|
||||
*/
|
||||
public interface ProgressListener {
|
||||
/**
|
||||
* Called periodically as the verification progresses.
|
||||
*
|
||||
* @param progress the approximate percentage of the
|
||||
* verification that has been completed, ranging from 0
|
||||
* to 100 (inclusive).
|
||||
*/
|
||||
public void onProgress(int progress);
|
||||
}
|
||||
|
||||
/** @return the set of certs that can be used to sign an OTA package. */
|
||||
private static HashSet<Certificate> getTrustedCerts(File keystore)
|
||||
throws IOException, GeneralSecurityException {
|
||||
HashSet<Certificate> trusted = new HashSet<Certificate>();
|
||||
if (keystore == null) {
|
||||
keystore = DEFAULT_KEYSTORE;
|
||||
}
|
||||
ZipFile zip = new ZipFile(keystore);
|
||||
try {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
trusted.add(cf.generateCertificate(zip.getInputStream(entry)));
|
||||
}
|
||||
} finally {
|
||||
zip.close();
|
||||
}
|
||||
return trusted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the cryptographic signature of a system update package
|
||||
* before installing it. Note that the package is also verified
|
||||
* separately by the installer once the device is rebooted into
|
||||
* the recovery system. This function will return only if the
|
||||
* package was successfully verified; otherwise it will throw an
|
||||
* exception.
|
||||
*
|
||||
* Verification of a package can take significant time, so this
|
||||
* function should not be called from a UI thread.
|
||||
*
|
||||
* @param packageFile the package to be verified
|
||||
* @param listener an object to receive periodic progress
|
||||
* updates as verification proceeds. May be null.
|
||||
* @param deviceCertsZipFile the zip file of certificates whose
|
||||
* public keys we will accept. Verification succeeds if the
|
||||
* package is signed by the private key corresponding to any
|
||||
* public key in this file. May be null to use the system default
|
||||
* file (currently "/system/etc/security/otacerts.zip").
|
||||
*
|
||||
* @throws IOException if there were any errors reading the
|
||||
* package or certs files.
|
||||
* @throws GeneralSecurityException if verification failed
|
||||
*/
|
||||
public static void verifyPackage(File packageFile,
|
||||
ProgressListener listener,
|
||||
File deviceCertsZipFile)
|
||||
throws IOException, GeneralSecurityException {
|
||||
long fileLen = packageFile.length();
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
|
||||
try {
|
||||
int lastPercent = 0;
|
||||
long lastPublishTime = System.currentTimeMillis();
|
||||
if (listener != null) {
|
||||
listener.onProgress(lastPercent);
|
||||
}
|
||||
|
||||
raf.seek(fileLen - 6);
|
||||
byte[] footer = new byte[6];
|
||||
raf.readFully(footer);
|
||||
|
||||
if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
|
||||
throw new SignatureException("no signature in file (no footer)");
|
||||
}
|
||||
|
||||
int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
|
||||
int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
|
||||
Log.v(TAG, String.format("comment size %d; signature start %d",
|
||||
commentSize, signatureStart));
|
||||
|
||||
byte[] eocd = new byte[commentSize + 22];
|
||||
raf.seek(fileLen - (commentSize + 22));
|
||||
raf.readFully(eocd);
|
||||
|
||||
// Check that we have found the start of the
|
||||
// end-of-central-directory record.
|
||||
if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
|
||||
eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
|
||||
throw new SignatureException("no signature in file (bad footer)");
|
||||
}
|
||||
|
||||
for (int i = 4; i < eocd.length-3; ++i) {
|
||||
if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
|
||||
eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
|
||||
throw new SignatureException("EOCD marker found after start of EOCD");
|
||||
}
|
||||
}
|
||||
|
||||
// The following code is largely copied from
|
||||
// JarUtils.verifySignature(). We could just *call* that
|
||||
// method here if that function didn't read the entire
|
||||
// input (ie, the whole OTA package) into memory just to
|
||||
// compute its message digest.
|
||||
|
||||
BerInputStream bis = new BerInputStream(
|
||||
new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
|
||||
ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
|
||||
SignedData signedData = info.getSignedData();
|
||||
if (signedData == null) {
|
||||
throw new IOException("signedData is null");
|
||||
}
|
||||
Collection encCerts = signedData.getCertificates();
|
||||
if (encCerts.isEmpty()) {
|
||||
throw new IOException("encCerts is empty");
|
||||
}
|
||||
// Take the first certificate from the signature (packages
|
||||
// should contain only one).
|
||||
Iterator it = encCerts.iterator();
|
||||
X509Certificate cert = null;
|
||||
if (it.hasNext()) {
|
||||
cert = new X509CertImpl((org.apache.harmony.security.x509.Certificate)it.next());
|
||||
} else {
|
||||
throw new SignatureException("signature contains no certificates");
|
||||
}
|
||||
|
||||
List sigInfos = signedData.getSignerInfos();
|
||||
SignerInfo sigInfo;
|
||||
if (!sigInfos.isEmpty()) {
|
||||
sigInfo = (SignerInfo)sigInfos.get(0);
|
||||
} else {
|
||||
throw new IOException("no signer infos!");
|
||||
}
|
||||
|
||||
// Check that the public key of the certificate contained
|
||||
// in the package equals one of our trusted public keys.
|
||||
|
||||
HashSet<Certificate> trusted = getTrustedCerts(
|
||||
deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
|
||||
|
||||
PublicKey signatureKey = cert.getPublicKey();
|
||||
boolean verified = false;
|
||||
for (Certificate c : trusted) {
|
||||
if (c.getPublicKey().equals(signatureKey)) {
|
||||
verified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!verified) {
|
||||
throw new SignatureException("signature doesn't match any trusted key");
|
||||
}
|
||||
|
||||
// The signature cert matches a trusted key. Now verify that
|
||||
// the digest in the cert matches the actual file data.
|
||||
|
||||
// The verifier in recovery *only* handles SHA1withRSA
|
||||
// signatures. SignApk.java always uses SHA1withRSA, no
|
||||
// matter what the cert says to use. Ignore
|
||||
// cert.getSigAlgName(), and instead use whatever
|
||||
// algorithm is used by the signature (which should be
|
||||
// SHA1withRSA).
|
||||
|
||||
String da = sigInfo.getdigestAlgorithm();
|
||||
String dea = sigInfo.getDigestEncryptionAlgorithm();
|
||||
String alg = null;
|
||||
if (da == null || dea == null) {
|
||||
// fall back to the cert algorithm if the sig one
|
||||
// doesn't look right.
|
||||
alg = cert.getSigAlgName();
|
||||
} else {
|
||||
alg = da + "with" + dea;
|
||||
}
|
||||
Signature sig = Signature.getInstance(alg);
|
||||
sig.initVerify(cert);
|
||||
|
||||
// The signature covers all of the OTA package except the
|
||||
// archive comment and its 2-byte length.
|
||||
long toRead = fileLen - commentSize - 2;
|
||||
long soFar = 0;
|
||||
raf.seek(0);
|
||||
byte[] buffer = new byte[4096];
|
||||
while (soFar < toRead) {
|
||||
int size = buffer.length;
|
||||
if (soFar + size > toRead) {
|
||||
size = (int)(toRead - soFar);
|
||||
}
|
||||
int read = raf.read(buffer, 0, size);
|
||||
sig.update(buffer, 0, read);
|
||||
soFar += read;
|
||||
|
||||
if (listener != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
int p = (int)(soFar * 100 / toRead);
|
||||
if (p > lastPercent &&
|
||||
now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
|
||||
lastPercent = p;
|
||||
lastPublishTime = now;
|
||||
listener.onProgress(lastPercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onProgress(100);
|
||||
}
|
||||
|
||||
if (!sig.verify(sigInfo.getEncryptedDigest())) {
|
||||
throw new SignatureException("signature digest verification failed");
|
||||
}
|
||||
} finally {
|
||||
raf.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboots the device in order to install the given update
|
||||
* package.
|
||||
* Requires the {@link android.Manifest.permission#REBOOT}
|
||||
* and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}
|
||||
* permissions.
|
||||
*
|
||||
* @param context the Context to use
|
||||
* @param packageFile the update package to install. Currently
|
||||
* must be on the /cache or /data partitions.
|
||||
*
|
||||
* @throws IOException if writing the recovery command file
|
||||
* fails, or if the reboot itself fails.
|
||||
*/
|
||||
public static void installPackage(Context context, File packageFile)
|
||||
throws IOException {
|
||||
String filename = packageFile.getCanonicalPath();
|
||||
|
||||
if (filename.startsWith("/cache/")) {
|
||||
filename = "CACHE:" + filename.substring(7);
|
||||
} else if (filename.startsWith("/data/")) {
|
||||
filename = "DATA:" + filename.substring(6);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Must start with /cache or /data: " + filename);
|
||||
}
|
||||
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
|
||||
String arg = "--update_package=" + filename;
|
||||
bootCommand(context, arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboots the device and wipes the user data partition. This is
|
||||
* sometimes called a "factory reset", which is something of a
|
||||
* misnomer because the system partition is not restored to its
|
||||
* factory state.
|
||||
* Requires the {@link android.Manifest.permission#REBOOT}
|
||||
* and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM}
|
||||
* permissions.
|
||||
*
|
||||
* @param context the Context to use
|
||||
*
|
||||
* @throws IOException if writing the recovery command file
|
||||
* fails, or if the reboot itself fails.
|
||||
*/
|
||||
public static void rebootWipeUserData(Context context)
|
||||
throws IOException {
|
||||
bootCommand(context, "--wipe_data");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot into the recovery system to wipe the /data partition and toggle
|
||||
* Encrypted File Systems on/off.
|
||||
* @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
|
||||
* @throws IOException if something goes wrong.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void rebootToggleEFS(Context context, boolean efsEnabled)
|
||||
throws IOException {
|
||||
if (efsEnabled) {
|
||||
bootCommand(context, "--set_encrypted_filesystem=on");
|
||||
} else {
|
||||
bootCommand(context, "--set_encrypted_filesystem=off");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot into the recovery system with the supplied argument.
|
||||
* @param arg to pass to the recovery utility.
|
||||
* @throws IOException if something goes wrong.
|
||||
*/
|
||||
private static void bootCommand(Context context, String arg) throws IOException {
|
||||
RECOVERY_DIR.mkdirs(); // In case we need it
|
||||
COMMAND_FILE.delete(); // In case it's not writable
|
||||
LOG_FILE.delete();
|
||||
|
||||
FileWriter command = new FileWriter(COMMAND_FILE);
|
||||
try {
|
||||
command.write(arg);
|
||||
command.write("\n");
|
||||
} finally {
|
||||
command.close();
|
||||
}
|
||||
|
||||
// Having written the command file, go ahead and reboot
|
||||
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
pm.reboot("recovery");
|
||||
|
||||
throw new IOException("Reboot failed (no permissions?)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after booting to process and remove recovery-related files.
|
||||
* @return the log file from recovery, or null if none was found.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static String handleAftermath() {
|
||||
// Record the tail of the LOG_FILE
|
||||
String log = null;
|
||||
try {
|
||||
log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.i(TAG, "No recovery log file");
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading recovery log", e);
|
||||
}
|
||||
|
||||
// Delete everything in RECOVERY_DIR
|
||||
String[] names = RECOVERY_DIR.list();
|
||||
for (int i = 0; names != null && i < names.length; i++) {
|
||||
File f = new File(RECOVERY_DIR, names[i]);
|
||||
if (!f.delete()) {
|
||||
Log.e(TAG, "Can't delete: " + f);
|
||||
} else {
|
||||
Log.i(TAG, "Deleted: " + f);
|
||||
}
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private void RecoverySystem() { } // Do not instantiate
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.internal.os;
|
||||
|
||||
import android.os.FileUtils;
|
||||
import android.os.Power;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class for interacting with the Android recovery partition.
|
||||
* The recovery partition is a small standalone system which can perform
|
||||
* operations that are difficult while the main system is running, like
|
||||
* upgrading system software or reformatting the data partition.
|
||||
* Note that most of these operations must be run as root.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class RecoverySystem {
|
||||
private static final String TAG = "RecoverySystem"; // for logging
|
||||
|
||||
// Used to communicate with recovery. See commands/recovery/recovery.c.
|
||||
private static File RECOVERY_DIR = new File("/cache/recovery");
|
||||
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
|
||||
private static File LOG_FILE = new File(RECOVERY_DIR, "log");
|
||||
|
||||
// Length limits for reading files.
|
||||
private static int LOG_FILE_MAX_LENGTH = 8 * 1024;
|
||||
|
||||
/**
|
||||
* Reboot into the recovery system to install a system update.
|
||||
* @param update package to install (must be in /cache or /data).
|
||||
* @throws IOException if something goes wrong.
|
||||
*/
|
||||
public static void rebootAndUpdate(File update) throws IOException {
|
||||
String path = update.getCanonicalPath();
|
||||
if (path.startsWith("/cache/")) {
|
||||
path = "CACHE:" + path.substring(7);
|
||||
} else if (path.startsWith("/data/")) {
|
||||
path = "DATA:" + path.substring(6);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Must start with /cache or /data: " + path);
|
||||
}
|
||||
bootCommand("--update_package=" + path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot into the recovery system to wipe the /data partition.
|
||||
* @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
|
||||
* @throws IOException if something goes wrong.
|
||||
*/
|
||||
public static void rebootAndWipe() throws IOException {
|
||||
bootCommand("--wipe_data");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot into the recovery system to wipe the /data partition and toggle
|
||||
* Encrypted File Systems on/off.
|
||||
* @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
|
||||
* @throws IOException if something goes wrong.
|
||||
* @hide
|
||||
*/
|
||||
public static void rebootAndToggleEFS(boolean efsEnabled) throws IOException {
|
||||
if (efsEnabled) {
|
||||
bootCommand("--set_encrypted_filesystem=on");
|
||||
} else {
|
||||
bootCommand("--set_encrypted_filesystem=off");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reboot into the recovery system with the supplied argument.
|
||||
* @param arg to pass to the recovery utility.
|
||||
* @throws IOException if something goes wrong.
|
||||
*/
|
||||
private static void bootCommand(String arg) throws IOException {
|
||||
RECOVERY_DIR.mkdirs(); // In case we need it
|
||||
COMMAND_FILE.delete(); // In case it's not writable
|
||||
LOG_FILE.delete();
|
||||
|
||||
FileWriter command = new FileWriter(COMMAND_FILE);
|
||||
try {
|
||||
command.write(arg);
|
||||
command.write("\n");
|
||||
} finally {
|
||||
command.close();
|
||||
}
|
||||
|
||||
// Having written the command file, go ahead and reboot
|
||||
Power.reboot("recovery");
|
||||
throw new IOException("Reboot failed (no permissions?)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after booting to process and remove recovery-related files.
|
||||
* @return the log file from recovery, or null if none was found.
|
||||
*/
|
||||
public static String handleAftermath() {
|
||||
// Record the tail of the LOG_FILE
|
||||
String log = null;
|
||||
try {
|
||||
log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.i(TAG, "No recovery log file");
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading recovery log", e);
|
||||
}
|
||||
|
||||
// Delete everything in RECOVERY_DIR
|
||||
String[] names = RECOVERY_DIR.list();
|
||||
for (int i = 0; names != null && i < names.length; i++) {
|
||||
File f = new File(RECOVERY_DIR, names[i]);
|
||||
if (!f.delete()) {
|
||||
Log.e(TAG, "Can't delete: " + f);
|
||||
} else {
|
||||
Log.i(TAG, "Deleted: " + f);
|
||||
}
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,11 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.DropBoxManager;
|
||||
import android.os.FileUtils;
|
||||
import android.os.RecoverySystem;
|
||||
import android.os.SystemProperties;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.os.RecoverySystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.ICheckinService;
|
||||
import android.os.IParentalControlCallback;
|
||||
import android.os.RecoverySystem;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.android.internal.os.RecoverySystem;
|
||||
import com.google.android.net.ParentalControlState;
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ public final class FallbackCheckinService extends ICheckinService.Stub {
|
||||
|
||||
// Save the android ID so the new system can get it erased.
|
||||
try {
|
||||
RecoverySystem.rebootAndWipe();
|
||||
RecoverySystem.rebootWipeUserData(mContext);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Reboot for masterClear() failed", e);
|
||||
}
|
||||
@@ -67,7 +67,7 @@ public final class FallbackCheckinService extends ICheckinService.Stub {
|
||||
|
||||
// Save the android ID so the new system can get it erased.
|
||||
try {
|
||||
RecoverySystem.rebootAndToggleEFS(efsEnabled);
|
||||
RecoverySystem.rebootToggleEFS(mContext, efsEnabled);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Reboot for toggle EFS failed", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user