android N 恢復出廠設定流程簡析
前言:
恢復出廠設定的本質上是先寫入command到BCB中,然後讓手機進入recovery模式,此時會根據BCB中的命令來執行對應的操作,本文主要分析java層的流程。力有不逮,如有錯漏,請多指教。
流程圖:
流程分析:
packages/apps/Settings/src/com/android/settings/MasterClearConfirm.java
恢復出廠設定的功能在設定中,源生版本中使用者通過點選恢復出廠設定的button就可以了
private void doMasterClear() {
Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm" );
//引數的意思是是否擦除SdCard,預設為false
intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard);
//step:[1]
getActivity().sendBroadcast(intent);
// Intent handling is asynchronous -- assume it will happen soon.
}
可以看出設定中只是簡單的傳送了一個廣播,理所當然的需要找到廣播的接受者–MasterClearReceiver.java
frameworks/base/services/core/java/com/android/server/MasterClearReceiver.java
接收到設定發出的恢復出廠設定的廣播之後,將會呼叫RecoverySystem的rebootWipeUserData方法
@Override
public void onReceive(final Context context, final Intent intent) {
//過濾
if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {
if (!"google.com".equals(intent.getStringExtra("from"))) {
Slog.w(TAG, "Ignoring master clear request -- not from trusted server.");
return;
}
}
final boolean shutdown = intent.getBooleanExtra("shutdown", false);
final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
final boolean wipeExternalStorage = intent.getBooleanExtra(
Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false);
Slog.w(TAG, "!!! FACTORY RESET !!!");
// The reboot call is blocking, so we need to do it on another thread.
Thread thr = new Thread("Reboot") {
@Override
public void run() {
try {
//step:[2]
RecoverySystem.rebootWipeUserData(context, shutdown, reason, forceWipe);
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);
}
}
};
if (wipeExternalStorage) {
// thr will be started at the end of this task.
// 繼承AsyncTask的類,發現此處擦除的SdCard指的是android M 新加入的使用外部儲存卡擴容為
// 內建儲存裝置的功能中的儲存位置--SD卡或者USB儲存裝置,不是傳統data分割槽下的SdCard
// 另一方面分析,手機進入recovery模式之後,直接擦除data分割槽,SdCard目錄包含在內,這
// 樣SdCard必然會被擦除,那麼不必在此做多餘擦除操作。
new WipeAdoptableDisksTask(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) 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);
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();
String shutdownArg = null;
if (shutdown) {
shutdownArg = "--shutdown_after";
}
String reasonArg = null;
if (!TextUtils.isEmpty(reason)) {
reasonArg = "--reason=" + sanitizeArg(reason);
}
final String localeArg = "--locale=" + Locale.getDefault().toString();
//step:[3]
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}
判斷是否可以執行恢復出廠設定操作,可以,發系統廣播,設定command引數,隨後進入bootCommand方法
private static void bootCommand(Context context, String... args) throws IOException {
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.
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
rs.rebootRecoveryWithCommand(command.toString());
throw new IOException("Reboot failed (no permissions?)");
}
跨程序呼叫RecoverySystemService.java下的實現介面的私有類BindService中的對應方法rebootRecoveryWithCommand
frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
public void rebootRecoveryWithCommand(String command) {
if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
synchronized (sRequestLock) {
//關鍵點1:此處將command寫入到BCB(bootloader control block)中去
if (!setupOrClearBcb(true, command)) {
return;
}
// Having set up the BCB, go ahead and reboot.
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
//step:[4]
pm.reboot(PowerManager.REBOOT_RECOVERY);
}
}
private boolean setupOrClearBcb(boolean isSetup, String command) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
...
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) {
dos.writeInt(command.length());
dos.writeBytes(command);
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);
...
return true;
}
frameworks/base/core/java/android/os/PowerManager.java
public void reboot(String reason) {
try {
//step:[5]
mService.reboot(false, reason, true);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
@Override // Binder call
public void reboot(boolean confirm, String reason, boolean wait) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
//檢查許可權
if (PowerManager.REBOOT_RECOVERY.equals(reason)
|| PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
}
final long ident = Binder.clearCallingIdentity();
try {
//step:[6]
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) {
if (mHandler == null || !mSystemReady) {
throw new IllegalStateException("Too early to call shutdown() or reboot()");
}
Runnable runnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) {
ShutdownThread.rebootSafeMode(mContext, confirm);
} else if (haltMode == HALT_MODE_REBOOT) {
//step:[7]
ShutdownThread.reboot(mContext, reason, confirm);
} else {
ShutdownThread.shutdown(mContext, reason, confirm);
}
}
}
};
// ShutdownThread must run on a looper capable of displaying the UI.
Message msg = Message.obtain(mHandler, runnable);
msg.setAsynchronous(true);
mHandler.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;
//step:[8]
shutdownInner(context, confirm);
}
static void shutdownInner(final Context context, boolean confirm) {
// ensure that only one thread is trying to power down.
// any additional calls are just returned
synchronized (sIsStartedGuard) {
if (sIsStarted) {
Log.d(TAG, "Request to shutdown already running, returning.");
return;
}
}
boolean showRebootOption = false;
String[] defaultActions = context.getResources().getStringArray(
com.android.internal.R.array.config_globalActionsList);
for (int i = 0; i < defaultActions.length; i++) {
if (defaultActions[i].equals("reboot")) {
showRebootOption = true;
break;
}
}
final int longPressBehavior = context.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
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);
if (showRebootOption && !mRebootSafeMode) {
resourceId = com.android.internal.R.string.reboot_confirm;
}
Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
if (confirm) {
...
} else {
//step:[9]
beginShutdownSequence(context);
}
}
private static void beginShutdownSequence(Context context) {
synchronized (sIsStartedGuard) {
if (sIsStarted) {
Log.d(TAG, "Shutdown sequence already running, returning.");
return;
}
sIsStarted = true;
}
// Throw up a system dialog to indicate the device is rebooting / shutting down.
ProgressDialog pd = new ProgressDialog(context);
// Path 1: Reboot to recovery for update
// Condition: mReason == REBOOT_RECOVERY_UPDATE
//
// Path 1a: uncrypt needed
// Condition: if /cache/recovery/uncrypt_file exists but
// /cache/recovery/block.map doesn't.
// UI: determinate progress bar (mRebootHasProgressBar == True)
//
// * Path 1a is expected to be removed once the GmsCore shipped on
// device always calls uncrypt prior to reboot.
//
// Path 1b: uncrypt already done
// UI: spinning circle only (no progress bar)
//
// Path 2: Reboot to recovery for factory reset
// Condition: mReason == REBOOT_RECOVERY
// UI: spinning circle only (no progress bar)
//
// Path 3: Regular reboot / shutdown
// Condition: Otherwise
// UI: spinning circle only (no progress bar)
...
sInstance.mProgressDialog = pd;
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;
}
}
// start the thread that initiates shutdown
sInstance.mHandler = new Handler() {
};
//step:[10]
sInstance.start();
}
public void run() {
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.
*/
{
// 以恢復出廠設定的reason = "1recovery"
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");
}
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);
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, PHONE_STATE_POLL_SLEEP_MSEC));
} catch (InterruptedException e) {
}
}
}
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);
}
Log.i(TAG, "Shutting down activity manager...");
//shutdown 系統服務
final IActivityManager am =
ActivityManagerNative.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);
}
Log.i(TAG, "Shutting down package manager...");
final PackageManagerService pm = (PackageManagerService)
ServiceManager.getService("package");
if (pm != null) {
pm.shutdown();
}
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);
}
// Shutdown radios.
shutdownRadios(MAX_RADIO_WAIT_TIME);
if (mRebootHasProgressBar) {
sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
}
// Shutdown MountService to ensure media is in a safe state
IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
public void onShutDownComplete(int statusCode) throws RemoteException {
Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
actionDone();
}
};
Log.i(TAG, "Shutting down MountService");
// Set initial variables and time out time.
mActionDone = false;
final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
synchronized (mActionDoneSync) {
try {
final IMountService mount = IMountService.Stub.asInterface(
ServiceManager.checkService("mount"));
if (mount != null) {
mount.shutdown(observer);
} else {
Log.w(TAG, "MountService unavailable for shutdown");
}
} catch (Exception e) {
Log.e(TAG, "Exception during MountService shutdown", e);
}
while (!mActionDone) {
long delay = endShutTime - SystemClock.elapsedRealtime();
if (delay <= 0) {
Log.w(TAG, "Shutdown wait timed out");
break;
} else if (mRebootHasProgressBar) {
int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
(MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
MAX_SHUTDOWN_WAIT_TIME);
status += RADIO_STOP_PERCENT;
sInstance.setRebootProgress(status, null);
}
try {
mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
} catch (InterruptedException e) {
}
}
}
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();
}
// If it is alarm boot and encryption status, power off alarm status will
// be set to handled when device go to shutdown or reboot.
boolean isAlarmBoot = SystemProperties.getBoolean("ro.alarm_boot", false);
String cryptState = SystemProperties.get("vold.decrypt");
if (isAlarmBoot &&
("trigger_restart_min_framework".equals(cryptState) ||
"1".equals(cryptState))) {
AlarmManager.writePowerOffAlarmFile(AlarmManager.POWER_OFF_ALARM_HANDLE_FILE,
AlarmManager.POWER_OFF_ALARM_HANDLED);
}
// If it is factory data reset, value in POWER_OFF_ALARM_TIMEZONE_FILE will be cleared.
if (mReboot && PowerManager.REBOOT_RECOVERY.equals(mReason)) {
AlarmManager.writePowerOffAlarmFile(AlarmManager.POWER_OFF_ALARM_TIMEZONE_FILE, "");
} else {
AlarmManager.writePowerOffAlarmFile(AlarmManager.POWER_OFF_ALARM_TIMEZONE_FILE,
SystemProperties.get("persist.sys.timezone"));
}
//step:[11]
rebootOrShutdown(mContext, mReboot, mReason);
}
public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
// Call oem shutdown handler
deviceRebootOrShutdown(reboot, reason);
if (reboot) {
Log.i(TAG, "Rebooting, reason: " + reason);
//step:[12]
PowerManagerService.lowLevelReboot(reason);
Log.e(TAG, "Reboot failed, will attempt shutdown instead");
reason = null;
} else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
// vibrate before shutting down
Vibrator vibrator = new SystemVibrator(context);
try {
vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
} catch (Exception e) {
// Failure to vibrate shouldn't interrupt shutdown. Just log it.
Log.w(TAG, "Failed to vibrate during shutdown.", e);
}
// vibrator is asynchronous so we need to wait to avoid shutting down too soon.
try {
Thread.sleep(SHUTDOWN_VIBRATE_MS);
} catch (InterruptedException unused) {
}
}
// Shutdown power
Log.i(TAG, "Performing low-level shutdown...");
PowerManagerService.lowLevelShutdown(reason);
}
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
public static void lowLevelReboot(String reason) {
if (reason == null) {
reason = "";
}
if (reason.equals(PowerManager.REBOOT_RECOVERY)
|| reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) {
// 關鍵點2:通過寫入這個系統屬性值,手機將進入recovery模式
// 配合關鍵點1,才能使手機擦除data分割槽並重啟手機,沒有關鍵點1,手機只會進入recovery模式。
// //step:[13]
SystemProperties.set("sys.powerctl", "reboot,recovery");
} else {
SystemProperties.set("sys.powerctl", "reboot," + reason);
}
try {
// 如果沒有reboot成功,例如沒有寫入上述的系統屬性值,手機仍然會在20s之後重啟,
// 以shutdown的方式,同樣也是寫入系統屬性值
Thread.sleep(20 * 1000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Slog.wtf(TAG, "Unexpected return from lowLevelReboot!");
}
public static void lowLevelShutdown(String reason) {
if (reason == null) {
reason = "";
}
SystemProperties.set("sys.powerctl", "shutdown," + reason);
}