您的位置:首页 > 移动开发 > Android开发

Android 上层RecoverySystem类

2011-09-30 14:29 330 查看
尝试将imx51使用OTA方式进行recovery,将android_recovery.img放入/cache/分区下然后再下一次重启的时候进行分区更新,但发现放入的文件会被莫名删除,logcat中搜索到recovery相关log,tag为recoverysystem,尝试在android工程framework的os代码中寻找代码,找到相关代码,代码最后有将cache分区进行删除仅保留last_log的操作,这才解开心中的疑惑,而且发现,以前本地升级时写文件的代码其实不需要自己写的,android自己已经做好了类的函数,需要的只是调用。代码很简单,和recovery的代码几乎一样,java话而已,无须一条条解释代码。List
as below:

部分中文解释为下:(参考http://www.elexcon.com/news/54208.html

Android 2.2开始新增RecoverySystem类,可以帮助我们调用系统还原等操作,使用RecoverySystem必须是API
Level最小为为8,该类位于android.os.RecoverySystem,提供了三个静态方法

static void installPackage(Context context, File packageFile) //重启设备,安装一个更新包

static void rebootWipeUserData(Context context) //重启设备,清除用户数据分区类似恢复出厂设置

static void verifyPackage(File packageFile, RecoverySystem.ProgressListener listener, File deviceCertsZipFile) //验证加密签名的系统更新包在安装前,其中第二个数接口的具体定义为 android.os.RecoverySystem.ProgressListener 其中只有一个回调方法 abstract void onProgress(int progress) 来显示效验的进度。
android/frameworks/base/core/java/android/os/RecoverySystem.java

/*
* 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 android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

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 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.)
*/
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");
private static String LAST_LOG_FILENAME = "last_log";

// Length limits for reading files.
private static int LOG_FILE_MAX_LENGTH = 64 * 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.  Interrupting
* the thread while this function is in progress will result in a
* SecurityException being thrown (and the thread's interrupt flag
* will be cleared).
*
* @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];
boolean interrupted = false;
while (soFar < toRead) {
interrupted = Thread.interrupted();
if (interrupted) break;
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 (interrupted) {
throw new SignatureException("verification was interrupted");
}

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} permission.
*
* @param context      the Context to use
* @param packageFile  the update package to install.  Must be on
* a partition mountable by recovery.  (The set of partitions
* known to recovery may vary from device to device.  Generally,
* /cache and /data are safe.)
*
* @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();
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} permission.
*
* @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 {
final ConditionVariable condition = new ConditionVariable();

Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
context.sendOrderedBroadcast(intent, android.Manifest.permission.MASTER_CLEAR,
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
condition.open();
}
}, null, 0, null, null);

// Block until the ordered broadcast has completed.
condition.block();

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 except LAST_LOG_FILENAME
String[] names = RECOVERY_DIR.list();
for (int i = 0; names != null && i < names.length; i++) {
if (names[i].equals(LAST_LOG_FILENAME)) continue;
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
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: