前言
如果我们想让系统恢复出厂设置,一般有一下三种方式。
1、在【系统设置页面】进入【恢复出厂设置页面】,点击【恢复出厂设置】按钮。
2、直接通过adb发送恢复出厂设置的广播
adb shell am broadcast -a android.intent.action.MASTER_CLEAR
3、使用adb命令修改recovery的command文件,并重启系统进入recovery模式
adb shell 'echo "--wipe_data\n--locale=en_US" > /cache/recovery/command'
adb shell setprop sys.powerctl reboot,recovery
上面提到的第2种和第3种方式都是借助adb命令实现的,这里我们不过多讨论,本篇文章我们主要来分析第1种方式的实现流程。
一、发送恢复出厂设置广播
我们在【系统设置页面】进入【恢复出厂设置页面】,点击【恢复出厂设置】按钮,进入【二次确认页面】,然后再次点击【恢复出厂设置】按钮。在确定【要恢复出厂设置吗】页面点击确定【清除全部内容】按钮,系统会发送恢复出厂设置的广播:
packages/apps/Settings/src/com/android/settings/MasterClearConfirm.java
public class MasterClearConfirm extends OptionsMenuFragment {
private View mContentView;
private boolean mEraseSdCard;
private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
public void onClick(View v) {
...
//发送恢复出厂设置的广播事件
doMasterClear();
...
}
...
};
//发送恢复出厂设置的广播事件
private void doMasterClear() {
//ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"
Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
//该参数表示是否擦除SD卡,默认为False
intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard);
//发送恢复出厂设置的广播事件
getActivity().sendBroadcast(intent);
}
}
点击确定【清除全部内容】按钮,最终会触发doMasterClear方法,然后在该方法中发送了一个恢复出厂设置的广播。
二、恢复出厂设置广播接收者收到广播
1、MasterClearReceiver广播组件的配置信息如下所示。
framework/base/core/res/AndroidManifest.xml
<receiver android:name="com.android.server.MasterClearReceiver"
android:permission="android.permission.MASTER_CLEAR">
<intent-filter
android:priority="100" >
<!-- For Checkin, Settings, etc.: action=MASTER_CLEAR -->
<action android:name="android.intent.action.MASTER_CLEAR" />
<!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="android.intent.category.MASTER_CLEAR" />
</intent-filter>
</receiver>
2、MasterClearReceiver广播组件的所对应的源码信息如下所示。
frameworks/base/services/core/java/com/android/server/MasterClearReceiver.java
public class MasterClearReceiver extends BroadcastReceiver {
private static final String TAG = "MasterClear";
private boolean mWipeExternalStorage;//是否擦除外部存储卡
private boolean mWipeEsims;//是否擦除sim卡
@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;
}
}
if (Intent.ACTION_MASTER_CLEAR.equals(intent.getAction())) {
Slog.w(TAG, "The request uses the deprecated Intent#ACTION_MASTER_CLEAR, "
+ "Intent#ACTION_FACTORY_RESET should be used instead.");
}
if (intent.hasExtra(Intent.EXTRA_FORCE_MASTER_CLEAR)) {
Slog.w(TAG, "The request uses the deprecated Intent#EXTRA_FORCE_MASTER_CLEAR, "
+ "Intent#EXTRA_FORCE_FACTORY_RESET should be used instead.");
}
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 !!!");
//重启将会阻塞线程,所以我们需要另外开启一个线程
Thread thr = new Thread("Reboot") {
@Override
public void run() {
try {
//重启并擦除用户信息
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);
}
}
};
if (mWipeExternalStorage || mWipeEsims) {
//线程将会在下面的任务执行完毕以后开始执行
new WipeDataTask(context, thr).execute();
} else {
thr.start();
}
}
//继承自AsyncTask类,此处擦除的SdCard指的是Android M(6.0)新加入的
//内置存储设备的功能中的存储位置SD卡或者USB存储设备,不是传统data分区下的SdCard
//手机进入Recovery模式后,直接擦除data分区,SdCard目录包含在内,这样SdCard必然会被擦除,无需再做多余操作
private class WipeDataTask extends AsyncTask<Void, Void, Void> {
private final Thread mChainedTask;
private final Context mContext;
private final ProgressDialog mProgressDialog;
public WipeDataTask(Context context, Thread chainedTask) {
mContext = context;
mChainedTask = chainedTask;
mProgressDialog = new ProgressDialog(context);
}
@Override
protected void onPreExecute() {
mProgressDialog.setIndeterminate(true);
mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing));
mProgressDialog.show();
}
@Override
protected Void doInBackground(Void... params) {
Slog.w(TAG, "Wiping adoptable disks");
if (mWipeExternalStorage) {
//调用StorageManager的wipeAdoptableDisks方法
StorageManager sm = (StorageManager) mContext.getSystemService(
Context.STORAGE_SERVICE);
sm.wipeAdoptableDisks();
}
return null;
}
@Override
protected void onPostExecute(Void result) {
mProgressDialog.dismiss();
mChainedTask.start();//启动重启并擦除用户数据的线程
}
}
}
MasterClearReceiver在收到恢复出厂设置广播之后,首先会创建一个新线程thr,该线程内部会重启并擦除用户数据,但是thr线程并不会立刻执行,而是会判断是否需要擦除外置存储卡或者sim卡中的数据,如果需要则会创建WipeDataTask对象,该对象内部会调用StorageManager的wipeAdoptableDisks方法清除外置存储卡的数据。
3、StorageManager的wipeAdoptableDisks方法如下所示。
frameworks/base/core/java/android/os/storage/StorageManager.java
public class StorageManager {
//清除外置存储卡中的数据,高版本Android系统只有部分设备才有外置存储卡
public void wipeAdoptableDisks() {
// We only wipe devices in "adoptable" locations, which are in a
// long-term stable slot/location on the device, where apps have a
// reasonable chance of storing sensitive data. (Apps need to go through
// SAF to write to transient volumes.)
final List<DiskInfo> disks = getDisks();
for (DiskInfo disk : disks) {
final String diskId = disk.getId();
if (disk.isAdoptable()) {
Slog.d(TAG, "Found adoptable " + diskId + "; wiping");
try {
// TODO: switch to explicit wipe command when we have it,
// for now rely on the fact that vfat format does a wipe
//进行格式化
mStorageManager.partitionPublic(diskId);
} catch (Exception e) {
Slog.w(TAG, "Failed to wipe " + diskId + ", but soldiering onward", e);
}
} else {
Slog.d(TAG, "Ignorning non-adoptable disk " + disk.getId());
}
}
}
}
在调用StorageManager的wipeAdoptableDisks清除完外置存储卡中的数据之后,会执行thr线程的start方法,触发run方法,调用RecoverySystem的rebootWipeUserData方法。
4、RecoverySystem的rebootWipeUserData方法如下所示。
frameworks/base/core/java/android/os/RecoverySystem.java
public class RecoverySystem {
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 until the ordered broadcast has completed.
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)) {
reasonArg = "--reason=" + sanitizeArg(reason);
}
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
//调用bootCommand方法
bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}
/**
* 重启进入恢复(recovery)模式
*/
private static void bootCommand(Context context, String... args) throws IOException {
synchronized (sRequestLock) {
LOG_FILE.delete();//删除日志信息文件
StringBuilder command = new StringBuilder();
for (String arg : args) {
if (!TextUtils.isEmpty(arg)) {
command.append(arg);
command.append("\n");
}
}
RecoverySystem rs = (RecoverySystem) context.getSystemService( Context.RECOVERY_SERVICE);
//通过Binder把命令写入到BCB(bootloader control block)。
rs.setupBcb(command.toString());
//设置完BCB,调用电源管理重启系统,进入恢复模式
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);
throw new IOException("Reboot failed (no permissions?)");
}
}
/**
* 通过Binder与RecoverySystemService对话以设置BCB。
*/
private boolean setupBcb(String command) {
try {
return mService.setupBcb(command);
} catch (RemoteException unused) {
}
return false;
}
}
rebootWipeUserData方法会封装参数 --wipe_data,–locale,然后调用bootCommand方法,这些命令可以在recovery log(/cache/recovery/*.log)信息中看到,bootCommand方法会进一步调用RecoverySystemService的setupBcb方法将封装的参数写入到BCB中。
5、RecoverySystemService的setupBcb方法如下所示。
/frameworks/base/services/core/java/com/android/server/RecoverySystemService.java
public final class RecoverySystemService extends SystemService {
@Override // Binder call
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;
}
if (isSetup) {
SystemProperties.set("ctl.start", "setup-bcb");
} else {
SystemProperties.set("ctl.start", "clear-bcb");
}
// Connect to the uncrypt service socket.
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) {
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;
}
}
}
三、重启系统
1、重新回到RecoverySystem的bootCommand方法中,在调用RecoverySystemService的setupBcb方法将参数写入BCB之后,会调用PowerManage的reboot方法来重启系统。
frameworks/base/core/java/android/os/PowerManager.java
public final class PowerManager {
final IPowerManager mService;
public void reboot(String reason) {
try {
//进一步调用PowerManagerService的reboot,第一个参数如果为true,会显示一个确认弹窗,这里设置为false表示不需要确认弹窗。
mService.reboot(false, reason, true);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
PowerManage的reboot会进一步调用PowerManagerService的reboot,第一个参数如果为true,会显示一个确认弹窗,这里设置为false表示不需要确认弹窗。
2、PowerManagerService的reboot方法如下所示。
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
public final class PowerManagerService extends SystemService{
/**
* 重启系统
* @param confirm如果为true,会显示一个确认弹窗
* @param reason 重启的原因
* @param wait If true, this call waits for the reboot to complete and does not return.
*/
@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 {
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) {
if (RescueParty.isAttemptingFactoryReset()) {
// If we're stuck in a really low-level reboot loop, and a
// rescue party is trying to prompt the user for a factory data
// reset, we must GET TO DA CHOPPA!
PowerManagerService.lowLevelReboot(reason);
} else {
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(getUiContext(), confirm);
} else if (haltMode == HALT_MODE_REBOOT) {
//调用ShutdownThread线程对象的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) {
}
}
}
}
}
}
PowerManagerService的reboot方法会进一步调用shutdownOrRebootInternal方法,最后会触发ShutdownThread的reboot方法。
3、ShutdownThread的reboot方法如下所示。
frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
public final class ShutdownThread extends Thread {
/**
* Request a clean shutdown, waiting for subsystems to clean up their
* state etc. Must be called from a Looper thread in which its UI
* is shown.
*
* @param context Context used to display the shutdown progress dialog. This must be a context
* suitable for displaying UI (aka Themable).
* @param reason code to pass to the kernel (e.g. "recovery"), or null.
* @param confirm true if user confirmation is needed before shutting down.
*/
public static void reboot(final Context context, String reason, boolean confirm) {
mReboot = true;
mRebootSafeMode = false;
mRebootHasProgressBar = false;
mReason = reason;
//调用shutdownInner方法
shutdownInner(context, confirm);
}
private static void shutdownInner(final Context context, boolean confirm) {
// ShutdownThread is called from many places, so best to verify here that the context passed
// in is themed.
context.assertRuntimeOverlayThemable();
// 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;
}
}
final int longPressBehavior = context.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
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) {//上面提到过的确认弹窗
final CloseDialogReceiver closer = new CloseDialogReceiver(context);
if (sConfirmDialog != null) {
sConfirmDialog.dismiss();
}
sConfirmDialog = new AlertDialog.Builder(context)
.setTitle(mRebootSafeMode
? com.android.internal.R.string.reboot_safemode_title
: com.android.internal.R.string.power_off)
.setMessage(resourceId)
.setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
beginShutdownSequence(context);
}
})
.setNegativeButton(com.android.internal.R.string.no, null)
.create();
closer.dialog = sConfirmDialog;
sConfirmDialog.setOnDismissListener(closer);
sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
sConfirmDialog.show();
} else {
//调用shutdownInner方法
beginShutdownSequence(context);
}
}
}
reboot方法进一步调用shutdownInner方法,shutdownInner首先判断是否需要显示确认弹窗,由于时恢复出厂设置,这里为false表示不需要显示确认弹窗,会直接调用beginShutdownSequence方法。
4、ShutdownThread的beginShutdownSequence方法如下所示。
public final class ShutdownThread extends Thread {
private static void beginShutdownSequence(Context context) {
synchronized (sIsStartedGuard) {
if (sIsStarted) {
Log.d(TAG, "Shutdown sequence already running, returning.");
return;
}
sIsStarted = true;
}
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();
}
}
四、总结
1、点击按钮,系统发送恢复出厂设置广播
2、系统MasterClearReceiver接收广播,并进行android层的相关处理最后重启
3、在/cache/recovery/command文件中写入命令字段
4、设置Recovery模式,recovery get_args() 将"boot-recovery"和"–wipe_data"写入BCB,重启Recovery模式会根据/cache/recovery/command中的命令字段来清除用户对应的数据
1)erase_root 格式化Data分区
2)erase_root 格式化Case分区
3)finish_recovery擦除BCB分区
5、重新启动系统,恢复出厂设置成功
💡 技术无价,赞赏随心
写文不易,如果本文帮你避开了“八小时踩坑”,或者让你直呼“学到了!”
欢迎扫码赞赏,让我知道这篇内容值得!
(1元点赞,5元加鸡腿,10元请我喝杯咖啡,100…您就是我的榜一大哥!😂)