工作中排查到了恢复出厂设置的bug, 有一些细节是需要注意的,于是把这块的代码流程看一下:
代码基于:Android9.0
应用层:
就发送MASTER_CLEAR的广播, 这里没有带参数的
private final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
...
Intent intent = new Intent(ACTION_MASTER_CLEAR);
intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
sendBroadcast(intent);
框架层:
接受广播:
frameworks/base/services/core/java/com/android/server/MasterClearReceiver.java
final boolean shutdown = intent.getBooleanExtra("shutdown", false);
final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false);
final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
|| intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false);
Slog.w(TAG, "!!! FACTORY RESET !!!");
// 子线程中执行,防止blocking
// The reboot call is blocking, so we need to do it on another thread.
Thread thr = new Thread("Reboot") {
@Override
public void run() {
try {
// 这里的四个参数就是默认值了,应用层并没有传进来
// shutdown:false
// reason:null
// forceWipe:false
// mWipeEsims:false
// 重启擦除user data and cache partitions
RecoverySystem
.rebootWipeUserData(context, shutdown, reason, forceWipe, mWipeEsims);
Log.wtf(TAG, "Still running after master clear?!");
} catch (IOException e) {
Slog.e(TAG, "Can't perform master clear/factory reset", e);
} catch (SecurityException e) {
Slog.e(TAG, "Can't perform master clear/factory reset", e);
}
}
};
// mWipeExternalStorage & mWipeEsims都是false,所以这里不走
if (mWipeExternalStorage || mWipeEsims) {
// thr will be started at the end of this task.
new WipeDataTask(context, thr).execute();
} else {
thr.start();
}
frameworks/base/core/java/android/os/RecoverySystem.java
public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
boolean force, boolean wipeEuicc) throws IOException {
...
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);
// 注意这里发了MASTER_CLEAR_NOTIFICATION这个有序广播,而且是block的,
// 也就是接受这个广播的所有接受者处理完成之后才会继续往下执行,
// 所以需要恢复出厂设置进行的一些操作可以接收这个广播进行处理.
// 比如:除了用户数据&缓存数据,还有别的数据需要擦除、
// 比如:车载系统需要通知MCU层做一些操作,也是接收这个广播发命令到MCU.
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 until the ordered broadcast has completed.
condition.block();
...
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
// shutdownArg:--shutdown_after
// reasonArg:--reason=
// localeArg:--locale=zh_CN
// --wipe_data: 擦除data分区,同时擦除cache分区.
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}
...
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");
}
}
// Write the command into BCB (bootloader control block) and boot from
// there. Will not return unless failed.
// 上面的command参数会写进BCB, 重启读BCB的数据从而进入Recovery
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
rs.rebootRecoveryWithCommand(command.toString());
...
}
frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
@Override // Binder call
public void rebootRecoveryWithCommand(String command) {
if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
synchronized (sRequestLock) {
// 设置Bcb,返回true才继续往下走
if (!setupOrClearBcb(true, command)) {
return;
}
// Having set up the BCB, go ahead and reboot.
// 已设置BCB, 重启进Recovery模式
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);
}
}
private boolean setupOrClearBcb(boolean isSetup, String command) {
...
if (isSetup) {
// isSetup=true,走的这里,启动服务:setup-bcb
// uncrypt.rc里面定义了这个服务:
// service setup-bcb /system/bin/uncrypt --setup-bcb
// class main
// socket uncrypt stream 600 system system
// disabled
// oneshot
SystemProperties.set("ctl.start", "setup-bcb");
} else {
SystemProperties.set("ctl.start", "clear-bcb");
}
// Connect to the uncrypt service 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());
// Send the BCB commands if it's to setup BCB.
if (isSetup) {
// 走的这里
// 写入BCB commands
byte[] cmdUtf8 = command.getBytes("UTF-8");
dos.writeInt(cmdUtf8.length);
dos.write(cmdUtf8, 0, cmdUtf8.length);
dos.flush();
}
// Read the status from the socket.
int status = dis.readInt();
// Ack receipt of the status code. uncrypt waits for the ack so
// the socket won't be destroyed before we receive the code.
dos.writeInt(0);
if (status == 100) {
Slog.i(TAG, "uncrypt " + (isSetup ? "setup" : "clear") +
" bcb successfully finished.");
} else {
// Error in /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;
}
...
private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
// The socket at /dev/socket/uncrypt to communicate with uncrypt.
private static final String UNCRYPT_SOCKET = "uncrypt";
...
private LocalSocket connectService() {
LocalSocket socket = new LocalSocket();
boolean done = false;
// The uncrypt socket will be created by init upon receiving the
// service request. It may not be ready by this point. So we will
// keep retrying until success or reaching timeout.
for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
try {
socket.connect(new LocalSocketAddress(UNCRYPT_SOCKET,
LocalSocketAddress.Namespace.RESERVED));
done = true;
break;
} catch (IOException ignored) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Slog.w(TAG, "Interrupted:", e);
}
}
}
if (!done) {
Slog.e(TAG, "Timed out connecting to uncrypt socket");
return null;
}
return socket;
}
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
@Override // Binder call
public void reboot(boolean confirm, String reason, boolean wait) {
...
// confirm:false
// reason:recovery
// wait:true
try {
shutdownOrRebootInternal(HALT_MODE_REBOOT, confirm, reason, wait);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
final String reason, boolean wait) {
...
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) {
ShutdownThread.rebootSafeMode(getUiContext(), confirm);
} else if (haltMode == HALT_MODE_REBOOT) {
// 走的这里
ShutdownThread.reboot(getUiContext(), reason, confirm);
} else {
ShutdownThread.shutdown(getUiContext(), reason, confirm);
}
}
}
};
// ShutdownThread must run on a looper capable of displaying the UI.
Message msg = Message.obtain(UiThread.getHandler(), runnable);
msg.setAsynchronous(true);
UiThread.getHandler().sendMessage(msg);
// PowerManager.reboot() is documented not to return so just wait for the inevitable.
if (wait) {
synchronized (runnable) {
while (true) {
try {
runnable.wait();
} catch (InterruptedException e) {
}
}
}
}
}
frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
public static void reboot(final Context context, String reason, boolean confirm) {
mReboot = true;
mRebootSafeMode = false;
mRebootHasProgressBar = false;
mReason = reason;
shutdownInner(context, confirm);
}
private static void shutdownInner(final Context context, boolean confirm) {
...
final int longPressBehavior = context.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
// mRebootSafeMode:false
// longPressBehavior=1
// <integer name="config_longPressOnPowerBehavior">1</integer>
final int resourceId = mRebootSafeMode
? com.android.internal.R.string.reboot_safemode_confirm
: (longPressBehavior == 2
? com.android.internal.R.string.shutdown_confirm_question
: com.android.internal.R.string.shutdown_confirm);
Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
if (confirm) {
...
} else {
// 走的这里
beginShutdownSequence(context);
}
}
private static void beginShutdownSequence(Context context) {
...
sInstance.mProgressDialog = showShutdownDialog(context);
sInstance.mContext = context;
sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
// make sure we never fall asleep again
// 保持屏幕长亮
sInstance.mCpuWakeLock = null;
try {
sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
sInstance.mCpuWakeLock.setReferenceCounted(false);
sInstance.mCpuWakeLock.acquire();
} catch (SecurityException e) {
Log.w(TAG, "No permission to acquire wake lock", e);
sInstance.mCpuWakeLock = null;
}
// also make sure the screen stays on for better user experience
sInstance.mScreenWakeLock = null;
if (sInstance.mPowerManager.isScreenOn()) {
try {
sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
sInstance.mScreenWakeLock.setReferenceCounted(false);
sInstance.mScreenWakeLock.acquire();
} catch (SecurityException e) {
Log.w(TAG, "No permission to acquire wake lock", e);
sInstance.mScreenWakeLock = null;
}
}
if (SecurityLog.isLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
}
// start the thread that initiates shutdown
sInstance.mHandler = new Handler() {
};
// 启动线程
sInstance.start();
}
public void run() {
// 重启前的准备工作
TimingsTraceLog shutdownTimingLog = newTimingsLog();
shutdownTimingLog.traceBegin("SystemServerShutdown");
metricShutdownStart();
metricStarted(METRIC_SYSTEM_SERVER);
BroadcastReceiver br = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
// We don't allow apps to cancel this, so ignore the result.
actionDone();
}
};
/*
* Write a system property in case the system_server reboots before we
* get to the actual hardware restart. If that happens, we'll retry at
* the beginning of the SystemServer startup.
*/
{
String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");
SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
}
/*
* If we are rebooting into safe mode, write a system property
* indicating so.
*/
if (mRebootSafeMode) {
SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
}
metricStarted(METRIC_SEND_BROADCAST);
shutdownTimingLog.traceBegin("SendShutdownBroadcast");
Log.i(TAG, "Sending shutdown broadcast...");
// First send the high-level shut down broadcast.
mActionDone = false;
Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendOrderedBroadcastAsUser(intent,
UserHandle.ALL, null, br, mHandler, 0, null, null);
final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
synchronized (mActionDoneSync) {
while (!mActionDone) {
long delay = endTime - SystemClock.elapsedRealtime();
if (delay <= 0) {
Log.w(TAG, "Shutdown broadcast timed out");
break;
} else if (mRebootHasProgressBar) {
int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *
BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);
sInstance.setRebootProgress(status, null);
}
try {
mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
} catch (InterruptedException e) {
}
}
}
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
}
shutdownTimingLog.traceEnd(); // SendShutdownBroadcast
metricEnded(METRIC_SEND_BROADCAST);
Log.i(TAG, "Shutting down activity manager...");
shutdownTimingLog.traceBegin("ShutdownActivityManager");
metricStarted(METRIC_AM);
final IActivityManager am =
IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
if (am != null) {
try {
am.shutdown(MAX_BROADCAST_TIME);
} catch (RemoteException e) {
}
}
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
}
shutdownTimingLog.traceEnd();// ShutdownActivityManager
metricEnded(METRIC_AM);
Log.i(TAG, "Shutting down package manager...");
shutdownTimingLog.traceBegin("ShutdownPackageManager");
metricStarted(METRIC_PM);
final PackageManagerService pm = (PackageManagerService)
ServiceManager.getService("package");
if (pm != null) {
pm.shutdown();
}
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
}
shutdownTimingLog.traceEnd(); // ShutdownPackageManager
metricEnded(METRIC_PM);
// Shutdown radios.
shutdownTimingLog.traceBegin("ShutdownRadios");
metricStarted(METRIC_RADIOS);
shutdownRadios(MAX_RADIO_WAIT_TIME);
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
}
shutdownTimingLog.traceEnd(); // ShutdownRadios
metricEnded(METRIC_RADIOS);
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
// If it's to reboot to install an update and uncrypt hasn't been
// done yet, trigger it now.
uncrypt();
}
shutdownTimingLog.traceEnd(); // SystemServerShutdown
metricEnded(METRIC_SYSTEM_SERVER);
saveMetrics(mReboot, mReason);
// Remaining work will be done by init, including vold shutdown
// 重启前的准备工作结束, 开始重启
rebootOrShutdown(mContext, mReboot, mReason);
}
...
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
// reboot = true
// reason = recovery
if (reboot) {
//走的这里
Log.i(TAG, "Rebooting, reason: " + reason);
PowerManagerService.lowLevelReboot(reason);
Log.e(TAG, "Reboot failed, will attempt shutdown instead");
reason = null;
} else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
...
}
// Shutdown power
Log.i(TAG, "Performing low-level shutdown...");
PowerManagerService.lowLevelShutdown(reason);
}
/frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
/**
* Low-level function to reboot the device. On success, this
* function doesn't return. If more than 20 seconds passes from
* the time a reboot is requested, this method returns.
*
* @param reason code to pass to the kernel (e.g. "recovery"), or null.
*/
public static void lowLevelReboot(String reason) {
if (reason == null) {
reason = "";
}
// If the reason is "quiescent", it means that the boot process should proceed
// without turning on the screen/lights.
// The "quiescent" property is sticky, meaning that any number
// of subsequent reboots should honor the property until it is reset.
if (reason.equals(PowerManager.REBOOT_QUIESCENT)) {
sQuiescent = true;
reason = "";
} else if (reason.endsWith("," + PowerManager.REBOOT_QUIESCENT)) {
sQuiescent = true;
reason = reason.substring(0,
reason.length() - PowerManager.REBOOT_QUIESCENT.length() - 1);
}
// reason = recovery
if (reason.equals(PowerManager.REBOOT_RECOVERY)
|| reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) {
// 走的这里
reason = "recovery";
}
if (sQuiescent) {
// Pass the optional "quiescent" argument to the bootloader to let it know
// that it should not turn the screen/lights on.
reason = reason + ",quiescent";
}
// 设置这个prop触发init进程重启进入recovery模式
SystemProperties.set("sys.powerctl", "reboot," + reason);
try {
Thread.sleep(20 * 1000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Slog.wtf(TAG, "Unexpected return from lowLevelReboot!");
}
sys.powerctl这个prop设置后最终走到**/system/core/init/reboot.cpp**::
bool HandlePowerctlMessage(const std::string& command)
void DoReboot(unsigned int cmd, const std::string& reason, const std::string& rebootTarget,bool runFsck)
void attribute((noreturn)) RebootSystem(unsigned int cmd, const std::string& rebootTarget)
到RebootSystem这个方法,重启结束, 进入kernel,调用syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, rebootTarget.c_str())进入内核空间进行重启,重启后进入Recovery模式,进行擦除cache分区、擦除data分区等操作。