Framework 层 Recovery 浅析

本文主要介绍Framework层Recovery相关内容,包括Recovery服务启动,升级包验证、解密、安装,恢复出厂等流程。

1.启动RecoverySystemService

1.1 SystemServer启动RecoverySystemService

作为系统启动关键服务,需要在SystemServer的startBootstrapServices中通过SystemServiceManager来启动RecoverySystemService。

1
2
3
4
5
6
//frameworks/base/services/java/com/android/server/SystemServer.java
private void startBootstrapServices() {
...
mSystemServiceManager.startService(RecoverySystemService.class);
...
}

1.2 RecoverySystemService的启动

RecoverySystemService继承自SystemService,将通过ServiceManagerService回调进入onStart方法。

1
2
3
4
5
//frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
public RecoverySystemService(Context context) {
super(context);
mContext = context;
}
1
2
3
4
5
//frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
@Override
public void onStart() {
publishBinderService(Context.RECOVERY_SERVICE, new BinderService());
}

在onStart方法中调用publishBinderService方法来发布RecoverySystemService这一Binder服务。即将该服务加入到ServiceManager列表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//frameworks/base/services/core/java/com/android/server/SystemService.java
// 发布服务,以便其他服务和应用程序可以访问它。
protected final void publishBinderService(String name, IBinder service) {
publishBinderService(name, service, false);
}
// allowIsolated:设置为true以允许隔离的沙盒进程访问此服务
protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated) {
publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT);
}
// dumpPriority:支持转储优先级作为位掩码
protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated, int dumpPriority) {
ServiceManager.addService(name, service, allowIsolated, dumpPriority);
}

2. RecoverySystem中系统升级相关API

2.1 调用VerifyPackage方法验证升级包签名

调用verifyPackage验证升级包签名。

一旦设备重启到recovery,安装程序也会单独验证程序包。仅当包成功验证后,此函数才会返回,否则会引发异常。验证包可能需要很长时间,故不应从UI线程调用此函数。在此函数正在进行时中断线程将导致抛出SecurityException。

deviceCertsZipFile:/system/etc/security/otacerts.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//frameworks/base/core/java/android/os/RecoverySystem.java
public static void verifyPackage(File packageFile,
ProgressListener listener,
File deviceCertsZipFile)
throws IOException, GeneralSecurityException {
final long fileLen = packageFile.length();
final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
try {
final long startTimeMillis = System.currentTimeMillis();
if (listener != null) {
listener.onProgress(0);
}
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)");
}
final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
byte[] eocd = new byte[commentSize + 22];
raf.seek(fileLen - (commentSize + 22));
raf.readFully(eocd);
// 检查是否找到了end-of-central-directory记录的头。
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");
}
}
// 解析签名。
PKCS7 block =
new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
// 从签名中获取第一个证书。
X509Certificate[] certificates = block.getCertificates();
if (certificates == null || certificates.length == 0) {
throw new SignatureException("signature contains no certificates");
}
X509Certificate cert = certificates[0];
PublicKey signatureKey = cert.getPublicKey();
SignerInfo[] signerInfos = block.getSignerInfos();
if (signerInfos == null || signerInfos.length == 0) {
throw new SignatureException("signature contains no signedData");
}
SignerInfo signerInfo = signerInfos[0];
// 检查升级包中包含的证书的公钥是否与我们的可信公钥匹配。
boolean verified = false;
HashSet<X509Certificate> trusted = getTrustedCerts(
deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
for (X509Certificate c : trusted) {
if (c.getPublicKey().equals(signatureKey)) {
verified = true;
break;
}
}
if (!verified) {
throw new SignatureException("signature doesn't match any trusted key");
}
// 签名证书与可信密钥匹配。验证证书中的摘要是否与实际文件数据匹配。
raf.seek(0);
final ProgressListener listenerForInner = listener;
SignerInfo verifyResult = block.verify(signerInfo, new InputStream() {
// 签名涵盖了除压缩包注释及其2字节长度之外的所有OTA包。
long toRead = fileLen - commentSize - 2;
long soFar = 0;
int lastPercent = 0;
long lastPublishTime = startTimeMillis;
@Override
public int read() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (soFar >= toRead) {
return -1;
}
if (Thread.currentThread().isInterrupted()) {
return -1;
}
int size = len;
if (soFar + size > toRead) {
size = (int)(toRead - soFar);
}
int read = raf.read(b, off, size);
soFar += read;
if (listenerForInner != null) {
long now = System.currentTimeMillis();
int p = (int)(soFar * 100 / toRead);
if (p > lastPercent &&
now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
lastPercent = p;
lastPublishTime = now;
listenerForInner.onProgress(lastPercent);
}
}
return read;
}
});
final boolean interrupted = Thread.interrupted();
if (listener != null) {
listener.onProgress(100);
}
if (interrupted) {
throw new SignatureException("verification was interrupted");
}
if (verifyResult == null) {
throw new SignatureException("signature digest verification failed");
}
} finally {
raf.close();
}
// 验证升级包的兼容性。
if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
throw new SignatureException("package compatibility verification failed");
}
}

2.2 调用processPackage方法解密升级包

调用processPackage,使用uncrypt处理给定的升级包。如果升级包不在data分区上,则为no-op。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//frameworks/base/core/java/android/os/RecoverySystem.java
@SystemApi
@RequiresPermission(android.Manifest.permission.RECOVERY)
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");
}
}

调用uncrypt,通过Binder与RecoverySystemService通信以触发解密。

1
2
3
4
5
6
7
8
9
//frameworks/base/core/java/android/os/RecoverySystem.java
//通过Binder与RecoverySystemService通信以触发uncrypt。
private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) {
try {
return mService.uncrypt(packageFile, listener);
} catch (RemoteException unused) {
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
private final class BinderService extends IRecoverySystem.Stub {
@Override
public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
synchronized (sRequestLock) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
final boolean available = checkAndWaitForUncryptService();
if (!available) {
Slog.e(TAG, "uncrypt service is unavailable.");
return false;
}
// 删除旧的/cache/recovery/uncrypt文件
RecoverySystem.UNCRYPT_PACKAGE_FILE.delete();
// 创建新的uncrypt文件并准备uncrypt
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);
return false;
}
// 在init中将"ctl.start"设置为"uncrypt"来触发uncrypt
SystemProperties.set("ctl.start", "uncrypt");
// 通过socket与uncrypt服务建立通信
LocalSocket socket = connectService();
if (socket == null) {
Slog.e(TAG, "Failed to connect to uncrypt socket");
return false;
}
// 从socket中读取状态
DataInputStream dis = null;
DataOutputStream dos = null;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
int lastStatus = Integer.MIN_VALUE;
while (true) {
int status = dis.readInt();
// 避免使用相同的消息淹没日志。
if (status == lastStatus && lastStatus != Integer.MIN_VALUE) {
continue;
}
lastStatus = status;
if (status >= 0 && status <= 100) {
// 更新状态
Slog.i(TAG, "uncrypt read status: " + status);
if (listener != null) {
try {
listener.onProgress(status);
} catch (RemoteException ignored) {
Slog.w(TAG, "RemoteException when posting progress");
}
}
if (status == 100) {
Slog.i(TAG, "uncrypt successfully finished.");
// ack收到最终状态码。uncrypt等待ack,所以socket在接收到该码之前不会被销毁。
dos.writeInt(0);
break;
}
} else {
// /system/bin/uncrypt出错。
Slog.e(TAG, "uncrypt failed with status: " + status);
// ack收到最终状态码。uncrypt等待ack,所以socket在接收到该码之前不会被销毁。
dos.writeInt(0);
return false;
}
}
} catch (IOException e) {
Slog.e(TAG, "IOException when reading status: ", e);
return false;
} finally {
IoUtils.closeQuietly(dis);
IoUtils.closeQuietly(dos);
IoUtils.closeQuietly(socket);
}
return true;
}
}
...
}

调用checkAndWaitForUncryptService,检查是否有init服务一直在运行。若有则不能立马启动一个uncrypt/setup-bcb/clear-bcb服务。否则它可能会造成socket通信异常,因为init正在服务启动、退出时创建、删除这些socket。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
private final class BinderService extends IRecoverySystem.Stub {
...
private boolean checkAndWaitForUncryptService() {
for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
final String uncryptService = SystemProperties.get(INIT_SERVICE_UNCRYPT);
final String setupBcbService = SystemProperties.get(INIT_SERVICE_SETUP_BCB);
final String clearBcbService = SystemProperties.get(INIT_SERVICE_CLEAR_BCB);
final boolean busy = "running".equals(uncryptService) ||
"running".equals(setupBcbService) || "running".equals(clearBcbService);
if (DEBUG) {
Slog.i(TAG, "retry: " + retry + " busy: " + busy +
" uncrypt: [" + uncryptService + "]" +
" setupBcb: [" + setupBcbService + "]" +
" clearBcb: [" + clearBcbService + "]");
}
if (!busy) {
return true;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Slog.w(TAG, "Interrupted:", e);
}
}
return false;
}
...
}

2.3 调用installPackage方法安装升级包

调用installPackage,重启设备以安装给定的升级包。其中升级包必须存于recovery中可以挂载的分区中,如cache、data分区。如果升级包尚未处理(即未加密),则需要设置UNCRYPT_PACKAGE_FILE并删除BLOCK_MAP_FILE以便重启期间触发解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//frameworks/base/core/java/android/os/RecoverySystem.java
@SystemApi
@RequiresPermission(android.Manifest.permission.RECOVERY)
public static void installPackage(Context context, File packageFile, boolean processed)
throws IOException {
synchronized (sRequestLock) {
// 删除/cache/recovery/log文件。
LOG_FILE.delete();
// 删除/cache/recovery/uncrypt_file文件。
UNCRYPT_PACKAGE_FILE.delete();
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
// 若升级包名称中包含"_s.zip",则该升级包是个security升级包。
boolean securityUpdate = filename.endsWith("_s.zip");
// 如果升级包在data分区,则该它需要被处理(如解密)。调用者指定是否已在'processed'参数中完成。
if (filename.startsWith("/data/")) {
if (processed) {
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");
}
} else {
FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
try {
uncryptFile.write(filename + "\n");
} finally {
uncryptFile.close();
}
// 设置UNCRYPT_PACKAGE_FILE读写权限:可由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();
}
// 如果升级包在data分区,需要用block.map文件作为升级包名称。
filename = "@/cache/recovery/block.map";
}
// 设置升级参数
// "--update_package=":设定升级包路径、名称。
// "--locale=":设定当前区域、语言信息。
// "--security\n":设定为安全更新。
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;
}
// 设置引导程序控制块BCB(bootloader control block)
RecoverySystem rs = (RecoverySystem) context.getSystemService(
Context.RECOVERY_SERVICE);
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
// 设置BCB后,继续并重启。
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
// 对于电视而言,如果屏幕是关的,则静静重启
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
reason += ",quiescent";
}
}
pm.reboot(reason);
throw new IOException("Reboot failed (no permissions?)");
}
}

调用setupBcb,通过Binder与RecoverySystemService通信以设置BCB。

1
2
3
4
5
6
7
8
//frameworks/base/core/java/android/os/RecoverySystem.java
private boolean setupBcb(String command) {
try {
return mService.setupBcb(command);
} catch (RemoteException unused) {
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
private final class BinderService extends IRecoverySystem.Stub {
...
@Override
public boolean setupBcb(String command) {
if (DEBUG) Slog.d(TAG, "setupBcb: [" + command + "]");
synchronized (sRequestLock) {
return setupOrClearBcb(true, command);
}
}
...
private boolean setupOrClearBcb(boolean isSetup, String command) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
final boolean available = checkAndWaitForUncryptService();
if (!available) {
Slog.e(TAG, "uncrypt service is unavailable.");
return false;
}
// 根据需要将"ctl.start"设置为"setup-bcb"或"clear-bcb"。
if (isSetup) {
SystemProperties.set("ctl.start", "setup-bcb");
} else {
SystemProperties.set("ctl.start", "clear-bcb");
}
// 通过socket与uncrypt服务建立通信
LocalSocket socket = connectService();
if (socket == null) {
Slog.e(TAG, "Failed to connect to uncrypt socket");
return false;
}
DataInputStream dis = null;
DataOutputStream dos = null;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
// 发送BCB指令
if (isSetup) {
byte[] cmdUtf8 = command.getBytes("UTF-8");
dos.writeInt(cmdUtf8.length);
dos.write(cmdUtf8, 0, cmdUtf8.length);
dos.flush();
}
// 从socket中读取状态
int status = dis.readInt();
// ack确认收到状态码。uncrypt等待ack,所以在收到状态码之前socket不会被销毁。
dos.writeInt(0);
if (status == 100) {
Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +
" bcb successfully finished.");
} else {
// /system/bin/uncrypt出错
Slog.e(TAG, "uncrypt failed with status: " + status);
return false;
}
} catch (IOException e) {
Slog.e(TAG, "IOException when communicating with uncrypt:", e);
return false;
} finally {
IoUtils.closeQuietly(dis);
IoUtils.closeQuietly(dos);
IoUtils.closeQuietly(socket);
}
return true;
}
...
}

2.4 调用cancelScheduledUpdate方法取消计划性升级

调用cancelScheduledUpdate,通过清除BCB来取消任何计划性升级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//frameworks/base/core/java/android/os/RecoverySystem.java
@SystemApi
@RequiresPermission(android.Manifest.permission.RECOVERY)
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");
}
}
...
// 通过Binder与RecoverySystemService通信以清除BCB。
private boolean clearBcb() {
try {
return mService.clearBcb();
} catch (RemoteException unused) {
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
//frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
private final class BinderService extends IRecoverySystem.Stub {
...
@Override
public boolean clearBcb() {
if (DEBUG) Slog.d(TAG, "clearBcb");
synchronized (sRequestLock) {
return setupOrClearBcb(false, null);
}
}
...
}

2.5 调用scheduleUpdateOnBoot方法设定升级计划

调用scheduleUpdateOnBoot,以计划在下次启动时安装给定的升级包。调用者需确保必要时已处理(未加密)升级包。在BCB中设置命令,该命令将由bootloader和recovery image读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//frameworks/base/core/java/android/os/RecoverySystem.java
@SystemApi
@RequiresPermission(android.Manifest.permission.RECOVERY)
public static void scheduleUpdateOnBoot(Context context, File packageFile)
throws IOException {
String filename = packageFile.getCanonicalPath();
boolean securityUpdate = filename.endsWith("_s.zip");
// 如果升级包在data分区,用block.map文件来替代升级包包名
if (filename.startsWith("/data/")) {
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;
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
if (!rs.setupBcb(command)) {
throw new IOException("schedule update on boot failed");
}
}

3.RecoverySystem中恢复出厂相关API

3.1 调用rebootWipeUserData方法清除用户数据

调用rebootWipeUserData,重启设备擦除用户数据(包括data和cache分区)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//frameworks/base/core/java/android/os/RecoverySystem.java
// 接收android.intent.action.MASTER_CLEAR广播。
public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
boolean force, boolean wipeEuicc) throws IOException {
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
throw new SecurityException("Wiping data is not allowed for this user.");
}
final ConditionVariable condition = new ConditionVariable();
Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM,
android.Manifest.permission.MASTER_CLEAR,
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
condition.open();
}
}, null, 0, null, null);
// block直到有序广播完成。
condition.block();
if (wipeEuicc) {
wipeEuiccData(context, PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK);
}
String shutdownArg = null;
if (shutdown) {
shutdownArg = "--shutdown_after";
}
String reasonArg = null;
if (!TextUtils.isEmpty(reason)) {
String timeStamp = DateFormat.format("yyyy-MM-ddTHH:mm:ssZ", System.currentTimeMillis()).toString();
reasonArg = "--reason=" + sanitizeArg(reason + "," + timeStamp);
}
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}

调用wipeEuiccData,擦除Euicc数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//frameworks/base/core/java/android/os/RecoverySystem.java
public static boolean wipeEuiccData(Context context, final String packageName) {
ContentResolver cr = context.getContentResolver();
if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) {
// 如果未配置eUICC,则无理由擦除或保留配置文件。
Log.d(TAG, "Skipping eUICC wipe/retain as it is not provisioned");
return true;
}
EuiccManager euiccManager = (EuiccManager) context.getSystemService(
Context.EUICC_SERVICE);
if (euiccManager != null && euiccManager.isEnabled()) {
CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1);
final AtomicBoolean wipingSucceeded = new AtomicBoolean(false);
BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_EUICC_FACTORY_RESET.equals(intent.getAction())) {
if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
int detailedCode = intent.getIntExtra(
EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
Log.e(TAG, "Error wiping euicc data, Detailed code = "
+ detailedCode);
} else {
Log.d(TAG, "Successfully wiped euicc data.");
wipingSucceeded.set(true /* newValue */);
}
euiccFactoryResetLatch.countDown();
}
}
};
Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET);
intent.setPackage(packageName);
PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
IntentFilter filterConsent = new IntentFilter();
filterConsent.addAction(ACTION_EUICC_FACTORY_RESET);
HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread");
euiccHandlerThread.start();
Handler euiccHandler = new Handler(euiccHandlerThread.getLooper());
context.getApplicationContext()
.registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler);
euiccManager.eraseSubscriptions(callbackIntent);
try {
long waitingTimeMillis = Settings.Global.getLong(
context.getContentResolver(),
Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS);
if (waitingTimeMillis < MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
waitingTimeMillis = MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
} else if (waitingTimeMillis > MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
}
if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) {
Log.e(TAG, "Timeout wiping eUICC data.");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Wiping eUICC data interrupted", e);
return false;
} finally {
context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver);
}
return wipingSucceeded.get();
}
return false;
}

调用bootCommand,以便加入参数重启进入recovery完成相关操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//frameworks/base/core/java/android/os/RecoverySystem.java
private static void bootCommand(Context context, String... args) throws IOException {
// 删除/cache/recovery/log文件。
LOG_FILE.delete();
StringBuilder command = new StringBuilder();
for (String arg : args) {
if (!TextUtils.isEmpty(arg)) {
command.append(arg);
command.append("\n");
}
}
// 将command写入BCB并从那启动,除非失败否则不返回。
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
rs.rebootRecoveryWithCommand(command.toString());
throw new IOException("Reboot failed (no permissions?)");
}
// 通过Binder与RecoverySystemService通信来设置BCB command,重启进入recovery。
private void rebootRecoveryWithCommand(String command) {
try {
mService.rebootRecoveryWithCommand(command);
} catch (RemoteException ignored) {
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
private final class BinderService extends IRecoverySystem.Stub {
...
@Override
public void rebootRecoveryWithCommand(String command) {
if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
synchronized (sRequestLock) {
if (!setupOrClearBcb(true, command)) {
return;
}
// 设置BCB,然后重启
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);
}
}
...
}

3.2 调用rebootPromptAndWipeUserData方法提示、擦除用户数据

1
2
3
4
5
6
7
8
9
10
11
//frameworks/base/core/java/android/os/RecoverySystem.java
// sys.rescue_level为4(即LEVEL_FACTORY_RESET)
public static void rebootPromptAndWipeUserData(Context context, String reason)
throws IOException {
String reasonArg = null;
if (!TextUtils.isEmpty(reason)) {
reasonArg = "--reason=" + sanitizeArg(reason);
}
final String localeArg = "--locale=" + Locale.getDefault().toString();
bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg);
}

3.3 调用rebootWipeCache方法擦除cache分区

1
2
3
4
5
6
7
8
9
//frameworks/base/core/java/android/os/RecoverySystem.java
public static void rebootWipeCache(Context context, String reason) throws IOException {
String reasonArg = null;
if (!TextUtils.isEmpty(reason)) {
reasonArg = "--reason=" + sanitizeArg(reason);
}
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
bootCommand(context, "--wipe_cache", reasonArg, localeArg);
}

3.4 调用rebootWipeAb方法擦除A/B

1
2
3
4
5
6
7
8
9
10
11
12
//frameworks/base/core/java/android/os/RecoverySystem.java
public static void rebootWipeAb(Context context, File packageFile, String reason)
throws IOException {
String reasonArg = null;
if (!TextUtils.isEmpty(reason)) {
reasonArg = "--reason=" + sanitizeArg(reason);
}
final String filename = packageFile.getCanonicalPath();
final String filenameArg = "--wipe_package=" + filename;
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg);
}

3.5 调用handleAftermath清除recovery相关文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//frameworks/base/core/java/android/os/RecoverySystem.java
public static String handleAftermath(Context context) {
// 记录/cache/recovery/log文件的尾部
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);
}
// 仅在部分处理时删除OTA包(uncrypt)
boolean reservePackage = BLOCK_MAP_FILE.exists();
if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) {
String filename = null;
try {
filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null);
} catch (IOException e) {
Log.e(TAG, "Error reading uncrypt file", e);
}
// 删除已经(可能部分)处理的data分区上的OTA包。
if (filename != null && filename.startsWith("/data")) {
if (UNCRYPT_PACKAGE_FILE.delete()) {
Log.i(TAG, "Deleted: " + filename);
} else {
Log.e(TAG, "Can't delete: " + filename);
}
}
}
// 保留升级日志(以LAST_PREFIX开头),以及可选的block.map文件(BLOCK_MAP_FILE)。
// BLOCK_MAP_FILE将在成功解密后创建。
// 如果看到此文件,保留block.map文件和包含升级包名称的文件(UNCRYPT_PACKAGE_FILE)。
//为了减少GmsCore的工作,以避免再次重新下载所有内容。
String[] names = RECOVERY_DIR.list();
for (int i = 0; names != null && i < names.length; i++) {
// 不要删除last_install文件,因为recovery-persist会处理它。
if (names[i].startsWith(LAST_PREFIX) || names[i].equals(LAST_INSTALL_PATH)) continue;
if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue;
recursiveDelete(new File(RECOVERY_DIR, names[i]));
}
return log;
}
// 在内部递归删除给定的文件或目录。
private static void recursiveDelete(File name) {
if (name.isDirectory()) {
String[] files = name.list();
for (int i = 0; files != null && i < files.length; i++) {
File f = new File(name, files[i]);
recursiveDelete(f);
}
}
if (!name.delete()) {
Log.e(TAG, "Can't delete: " + name);
} else {
Log.i(TAG, "Deleted: " + name);
}
}