博客目标:
Android进行系统升级一般是调用SystemRecovery.installPackage接口进行升级,该接口一旦调用系统会立即重启进入recovery升级,无法满足下次重启升级需求。该博客目的是参考SysteRecovery接口实现下次重启升级功能。
开发环境:
Android version: Android 12
实现代码:
package com.centerm.recoverytools;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.IRecoverySystem;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Locale;
/**
* Create by ZXC on 2022/10/20
*/
public class CtRecoveryTools {
private static final String TAG = "CtRecoveryTools";
private static final Object sRequestLock = new Object();
static IRecoverySystem mRecoveryServer;
private static final File RECOVERY_DIR = new File("/cache/recovery");
private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
public static final int STAGE_VERIFY = 0;
public static final int STAGE_PROCESS_PACKAGE = 1;
public static final int STAGE_SET_BCB = 3;
/**
* stage:升级所处阶段.0:包验签阶段,1:包解密阶段,3:写bcb阶段
* progress: 所处阶段进度值:0-100
*/
public static interface ProgressListener {
void onProgress(final int stage, final int progress);
}
/**
* ota包验签. 注意:验过一次的包下次再验会失败!
*
* @param ota
* @param listener
* @param handler
* @throws GeneralSecurityException
* @throws IOException
*/
public static void verifyPackage(@NonNull File ota, @Nullable final ProgressListener listener,
@Nullable final Handler handler)
throws GeneralSecurityException, IOException {
RecoverySystem.ProgressListener listenerInternal = null;
if (listener != null) {
listenerInternal = new RecoverySystem.ProgressListener() {
@Override
public void onProgress(final int progress) {
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onProgress(STAGE_VERIFY, progress);
}
});
} else {
listener.onProgress(STAGE_VERIFY, progress);
}
}
};
}
RecoverySystem.verifyPackage(ota, listenerInternal, null);
}
/**
* ota包解密
*
* @param context
* @param ota
* @param listener
* @param handler
* @throws IOException
*/
public static void processPackage(@NonNull Context context, @NonNull File ota,
@Nullable final ProgressListener listener, @Nullable Handler handler)
throws IOException {
RecoverySystem.ProgressListener listenerInternal = null;
if (listener != null) {
listenerInternal = new RecoverySystem.ProgressListener() {
@Override
public void onProgress(int progress) {
listener.onProgress(STAGE_PROCESS_PACKAGE, progress);
}
};
}
RecoverySystem.processPackage(context, ota, listenerInternal, handler);
}
/**
* 写bcb指令
*
* @param context
* @param packageFile
* @param listener :接口回调,为了统一接口加的,可以为空
* @param rebootImmediately :是否立即重启,true:立即重启,false:不立即重启
* @throws IOException
* @throws RemoteException
*/
public static void sendCommand(@NonNull Context context, @NonNull File packageFile,
@Nullable ProgressListener listener, boolean rebootImmediately)
throws IOException, RemoteException {
synchronized (sRequestLock) {
if (listener != null) {
listener.onProgress(STAGE_SET_BCB, 0);
}
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 the package name ends with "_s.zip", it's a security update.
boolean securityUpdate = filename.endsWith("_s.zip");
if (filename.startsWith("/data/")) {
if (!BLOCK_MAP_FILE.exists()) {
Log.e(TAG, "Package claimed to have been processed but failed to find "
+ "the block map file.");
throw new IOException("Failed to find block map file");
}
// If the package is on the /data partition, use the block map
// file as the package name instead.
filename = "@/cache/recovery/block.map";
}
final String filenameArg = "--update_package=" + filename + "\n";
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
final String securityArg = "--security\n";
String command = filenameArg + localeArg;
if (securityUpdate) {
command += securityArg;
}
Log.d(TAG, "setupBcb: " + command);
mRecoveryServer = IRecoverySystem.Stub.asInterface(ServiceManager.getService(Context.RECOVERY_SERVICE));
if (mRecoveryServer == null) {
throw new IOException("Can not get RecoveryService!!!");
}
if (!mRecoveryServer.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
if (listener != null) {
listener.onProgress(STAGE_SET_BCB, 100);
}
if (rebootImmediately) {
Log.d(TAG, "reboot now ...");
// Having set up the BCB (bootloader control block), go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
pm.reboot(reason);
throw new IOException("Reboot failed (no permissions?)");
}
}
}
/**
* 整合完整的升级流程
*
* @param context
* @param packageFile
* @param listener
* @param handler
* @param rebootImmediately
* @throws GeneralSecurityException
* @throws IOException
* @throws RemoteException
*/
public static void installPackage(@NonNull Context context, @NonNull File packageFile,
@Nullable ProgressListener listener, @Nullable Handler handler,
boolean rebootImmediately)
throws GeneralSecurityException, IOException, RemoteException {
// 验证包的签名信息
verifyPackage(packageFile, listener, handler);
// 包解密
processPackage(context, packageFile, listener, handler);
// 写bcb指令
sendCommand(context, packageFile, listener, rebootImmediately);
}
}