Android 原生恢复出场设置流程

Android 原生恢复出场设置流程

将解析后的参数保存到全局变量。

恢复出厂设置流程:

选择“设置”->“系统”->”系统还原”。

Main system 向 BCB 写入 “–wipe_data”;

Main system 重新启动 reboot(‘recovery’),进入 Recovery 模式;

get_args() 函数读取 BCB写入 “boot-recovery” 和 “–wipe_data”,获知需要擦除数据。

erase_volume() 执行格式化/data分区

同样格式化 /cache 分区

finish_recovery() 清除 BCB。然后重启进入 Main system。

OTA Recovery 升级流程:

主系统下载更新包到 /cache/some-filename.zip

主系统向 BCB 写入 “–update_package=/cache/some-filename.zip”

主系统重启进入 recovery

get_args() 向 BCB 写入 “boot-recovery” and “–update_package=…”

– 如此再下次异常重启后会重新进入 recovery 安装更新 –

nstall_package() 安装更新

finish_recovery() 完成安装,完成 recovery,擦除 BCB

– 如此之后系统重启将进入主系统 –

如果安装失败

7a. prompt_and_wait() 显示错误,并等待用户操作

7b. 用户重启进入主系统

3、恢复出厂设置流程 1.原生设置中响应 恢复出厂设置 功能,继而发出重置广播(Intent.ACTION_FACTORY_RESET) .

2.frameWork层 接收到此广播,根据广播所携带的参数执行Android层的Reset设定.

3.Android层执行到最后会将Reset配置信息写入 BCB 中,最终进入Recovery

Master Reset 的时序图:

Master Reset 流程分析如下

(一)java层的代码解析 1.framework接收 Reset 广播 在framework/base/core/res/AndroidManifest.xml 中声明 MasterClearReceiver .java静态接收 Reset 广播

1.

3.

5.

//这条广播表示 恢复出厂设置

6.

7.

9.

10.

11.

12.

13.

14.

15. 2.MasterClearReceiver .onReceive() 在 MasterClearReceiver .java 执行 onReceive() 方法

framework/base/services/core/java/com/android/server/MasterClearReceiver .java

1. public void onReceive(final Context context, final Intent intent) {

2. if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {

3. if (!"google.com".equals(intent.getStringExtra("from"))) {

4. Slog.w(TAG, "Ignoring master clear request -- not from trusted server.");

5. return;

6. }

7. }

8. if (Intent.ACTION_MASTER_CLEAR.equals(intent.getAction())) {

9. Slog.w(TAG, "The request uses the deprecated Intent#ACTION_MASTER_CLEAR, "

10. + "Intent#ACTION_FACTORY_RESET should be used instead.");

11. }

12. if (intent.hasExtra(Intent.EXTRA_FORCE_MASTER_CLEAR)) {

13. Slog.w(TAG, "The request uses the deprecated Intent#EXTRA_FORCE_MASTER_CLEAR, "

14. + "Intent#EXTRA_FORCE_FACTORY_RESET should be used instead.");

15. }

16.

17. final String factoryResetPackage = context

18. .getString(com.android.internal.R.string.config_factoryResetPackage);

//判断如果广播是Intent.ACTION_FACTORY_RESET,且factoryResetPackage不为空

19. if (Intent.ACTION_FACTORY_RESET.equals(intent.getAction())

20. && !TextUtils.isEmpty(factoryResetPackage)) {

21. intent.setPackage(factoryResetPackage).setComponent(null);

//重新将广播发出去

22. context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);

23. return;

24. }

25. // 从广播中获取shutdown、reason、forceWipe、mWipeEsims等参数的值

//mWipeExternalStorage代表是否需要擦除外部储存的标志

// mWipeEsims代表是否需要擦除eSIM的标志

26. final boolean shutdown = intent.getBooleanExtra("shutdown", false);

27. final String reason = intent.getStringExtra(Intent.EXTRA_REASON);

28. mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);

29. mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false);

30. final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)

31. || intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false);

32.

33. Slog.w(TAG, "!!! FACTORY RESET !!!");

34. // The reboot call is blocking, so we need to do it on another thread.

// 创建一个新线程thr

35. Thread thr = new Thread("Reboot") {

36. @Override

37. public void run() {

38. try {

39. RecoverySystem

40. .rebootWipeUserData(context, shutdown, reason, forceWipe, mWipeEsims);

41. Log.wtf(TAG, "Still running after master clear?!");

42. } catch (IOException e) {

43. Slog.e(TAG, "Can't perform master clear/factory reset", e);

44. } catch (SecurityException e) {

45. Slog.e(TAG, "Can't perform master clear/factory reset", e);

46. }

47. }

48. };

49.

50. if (mWipeExternalStorage) {

51. // thr will be started at the end of this task.

52. new WipeDataTask(context, thr).execute();

53. } else {

54. thr.start();

55. }

56. }

查看MasterClearReceiver代码,onReceive接收到广播时,判断如果广播是Intent.ACTION_FACTORY_RESET,且factoryResetPackage不为空,就重新将广播发出去,return退出。之后会创建一个新线程thr,该线程内部会重启并擦除用户数据,但是thr线程并不会立刻执行,而是会判断是否需要擦除外置存储卡或者sim卡中的数据,如果需要则会创建WipeDataTask对象,该对象内部会调用StorageManager的wipeAdoptableDisks方法清除外置存储卡的数据。

在调用StorageManager的wipeAdoptableDisks清除完外置存储卡中的数据之后,会执行thr线程的start方法,触发run方法,调用RecoverySystem的rebootWipeUserData方法,

3.RecoverySystem.rebootWipeUserData() frameworks/base/core/java/android/os/RecoverySystem.java

RecoverySystem的rebootWipeUserData方法如下所示。

1. public static void rebootWipeUserData(Context context, boolean shutdown, String reason, boolean force, boolean wipeEuicc) throws IOException {

// 检查是否允许执行工厂重置,如果不允许则抛出SecurityException

3. UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);

4. if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {

5. throw new SecurityException("Wiping data is not allowed for this user.");

6. }

7. final ConditionVariable condition = new ConditionVariable();

8. //发送Intent.ACTION_MASTER_CLEAR_NOTIFICATION广播,并等待其完成

9. Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");

10. intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND

11. | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

12. context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM,

13. android.Manifest.permission.MASTER_CLEAR,

14. new BroadcastReceiver() {

15. @Override

16. public void onReceive(Context context, Intent intent) {

17. condition.open();

18. }

19. }, null, 0, null, null);

20.

21. // Block until the ordered broadcast has completed.

22. condition.block();

23. //如果需要擦除eUICC数据,则调用wipeEuiccData()执行擦除操作。否则调用removeEuiccInvisibleSubs()

24. EuiccManager euiccManager = context.getSystemService(EuiccManager.class);

25. if (wipeEuicc) {

26. wipeEuiccData(context, PACKAGE_NAME_EUICC_DATA_MANAGEMENT_CALLBACK);

27. } else {

28. removeEuiccInvisibleSubs(context, euiccManager);

29. }

30.

31. String shutdownArg = null;

32. if (shutdown) {

33. shutdownArg = "--shutdown_after";

34. }

35.

36. String reasonArg = null;

37. if (!TextUtils.isEmpty(reason)) {

38. String timeStamp = DateFormat.format("yyyy-MM-ddTHH:mm:ssZ", System.currentTimeMillis()).toString();

39. reasonArg = "--reason=" + sanitizeArg(reason + "," + timeStamp);

40. }

41.

42. final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;

43. Log.i(TAG," RecoverySystem.java rebootWipeUserData");

44. bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);

45. }

rebootWipeUserData方法执行过程中,会发送封装好的参数 --wipe_data,–locale,然后调用bootCommand方法。

4.RecoverySystem.bootCommand() BootCommand方法如下:

1. private static void bootCommand(Context context, String... args) throws IOException {

2. LOG_FILE.delete();

3. //构建command字符串,将args中的每个参数拼接并换行

4. StringBuilder command = new StringBuilder();

5. for (String arg : args) {

6. if (!TextUtils.isEmpty(arg)) {

7. command.append(arg);

8. command.append("\n");

9. }

10. }

11.

12. // Write the command into BCB (bootloader control block) and boot from

13. // there. Will not return unless failed.

14. RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);

15. Log.i(TAG," RecoverySystem.java bootcommand");

16. rs.rebootRecoveryWithCommand(command.toString());

17.

18. throw new IOException("Reboot failed (no permissions?)");

19. }

这个方法会把传入的命令参数写入bootCommand中,bootCommand方法会进一步调用RecoverySystemService的rebootRecoveryWithCommand方法

5.RecoverySystemService.rebootRecoveryWithCommand() frameworks/base/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java

RecoverySystemService的rebootRecoveryWithCommand方法如下:

1. public void rebootRecoveryWithCommand(String command) {

2. if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");

3. synchronized (sRequestLock) {

//setupOrClearBcb将之前传递过来的参数写入BCB中

4. if (!setupOrClearBcb(true, command)) {

5. return;

6. }

7.

8. // Having set up the BCB, go ahead and reboot.

9. PowerManager pm = mInjector.getPowerManager();

10. pm.reboot(PowerManager.REBOOT_RECOVERY);

11. }

12. }

private boolean setupOrClearBcb(boolean isSetup, String command) {

mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);

//检查解密服务是否可用,如果不可用直接返回false

final boolean available = checkAndWaitForUncryptService();

if (!available) {

Slog.e(TAG, "uncrypt service is unavailable.");

return false;

}

// 根据isSetup参数决定是设置BCB(setup-bcb)还是清除BCB(clear-bcb)

if (isSetup) {

mInjector.systemPropertiesSet("ctl.start", "setup-bcb");

} else {

mInjector.systemPropertiesSet("ctl.start", "clear-bcb");

}

//通过socket与 uncrypt通信,将之前的参数发送给 uncrypt

// Connect to the uncrypt service socket.

UncryptSocket socket = mInjector.connectService();

if (socket == null) {

Slog.e(TAG, "Failed to connect to uncrypt socket");

return false;

}

try {

// Send the BCB commands if it's to setup BCB.

if (isSetup) {

socket.sendCommand(command);

}

// Read the status from the socket.

int status = socket.getPercentageUncrypted();

// Ack receipt of the status code. uncrypt waits for the ack so

// the socket won't be destroyed before we receive the code.

socket.sendAck();

//根据状态码判断设置/清除BCB是否成功,成功则返回true,失败返回false

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 {

socket.close();

}

return true;

}

uncrypt.cpp对程序启动的参数分别处理,当参数为“setup-bcb”时调用setup_bcb()方法

bootable/recovery/uncrypt/uncrypt.cpp

static bool setup_bcb(const int socket) {

// c5. receive message length

int length;

if (!android::base::ReadFully(socket, &length, 4)) {

PLOG(ERROR)

PLOG(ERROR)

if (android::base::StartsWith(option, "--wipe_package=")) {

std::string path = option.substr(strlen("--wipe_package="));

if (!android::base::ReadFileToString(path, &wipe_package)) {

PLOG(ERROR)

LOG(ERROR)

PLOG(ERROR)

android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY));

if (fd == -1) {

*err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(),

strerror(errno));

return false;

}

//通过lseek()将文件指针移动到offset偏移位置

if (lseek(fd, static_cast

*err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(),

strerror(errno));

return false;

}

//通过WriteFully()将p指向的缓冲区的数据写入fd文件描述符,大小为size个字节

if (!android::base::WriteFully(fd, p, size)) {

*err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(),

strerror(errno));

return false;

}

//通过fsync()刷新文件缓冲,将数据写入物理设备

if (fsync(fd) == -1) {

*err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(),

strerror(errno));

return false;

}

return true;

}

2. if (REBOOT_USERSPACE.equals(reason) && !isRebootingUserspaceSupported()) {

3. throw new UnsupportedOperationException(

4. "Attempted userspace reboot on a device that doesn't support it");

5. }

6. try {

7. mService.reboot(false, reason, true);

8. } catch (RemoteException e) {

9. throw e.rethrowFromSystemServer();

10. }

11. }

//检查调用方是否有REBOOT和RECOVERY权限

2. mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

3. if (PowerManager.REBOOT_RECOVERY.equals(reason)

4. || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {

5. mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);

6. }

7.

8. final long ident = Binder.clearCallingIdentity();

9. try {

10. Log.i(TAG," powermanagerservice.java reboot start");

11. (HALT_MODE_REBOOT, confirm, reason, wait);

12. Log.i(TAG," powermanagerservice.java reboot--- shutdownOrRebootInternal");

13. } finally {

14. Binder.restoreCallingIdentity(ident);

15. }

16. }

17. private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,@Nullable final String reason, boolean wait) {

19. if (PowerManager.REBOOT_USERSPACE.equals(reason)) {

20. if (!PowerManager.isRebootingUserspaceSupportedImpl()) {

21. throw new UnsupportedOperationException(

22. "Attempted userspace reboot on a device that doesn't support it");

23. }

24. UserspaceRebootLogger.noteUserspaceRebootWasRequested();

25. }

26. if (mHandler == null || !mSystemReady) {

27. if (RescueParty.isAttemptingFactoryReset()) {

28. // If we're stuck in a really low-level reboot loop, and a

29. // rescue party is trying to prompt the user for a factory data

30. // reset, we must GET TO DA CHOPPA!

31. PowerManagerService.lowLevelReboot(reason);

32. } else {

33. throw new IllegalStateException("Too early to call shutdown() or reboot()");

34. }

35. }

36. //创建一个Runnable,在run()方法中根据haltMode的值来调用ShutdownThread的reboot()、 rebootSafeMode()或shutdown()方法执行重启或关机操作

37. Runnable runnable = new Runnable() {

38. @Override

39. public void run() {

40. synchronized (this) {

41. if (haltMode == HALT_MODE_REBOOT_SAFE_MODE) {

42. ShutdownThread.rebootSafeMode(getUiContext(), confirm);

43. } else if (haltMode == HALT_MODE_REBOOT) {

44. //走到这里

45. ShutdownThread.reboot(getUiContext(), reason, confirm);

46. } else {

47. ShutdownThread.shutdown(getUiContext(), reason, confirm);

48. }

49. }

50. }

51. };

52.

53. // ShutdownThread must run on a looper capable of displaying the UI.

54. Message msg = Message.obtain(UiThread.getHandler(), runnable);

55. msg.setAsynchronous(true);

56. UiThread.getHandler().sendMessage(msg);

57.

58. // PowerManager.reboot() is documented not to return so just wait for the inevitable.

//PowerManager.reboot()被记录为不会返回,所以只需等待不可避免的结果。

59. if (wait) {

60. synchronized (runnable) {

61. while (true) {

62. try {

63. runnable.wait();

64. } catch (InterruptedException e) {

65. }

66. }

67. }

68. }

69. }

2. mReboot = true;

3. mRebootSafeMode = false;

4. mRebootHasProgressBar = false;

5. mReason = reason;

6. //走到这里

7. shutdownInner(context, confirm);

8. }

9. private static void shutdownInner(final Context context, boolean confirm) {

10. // ShutdownThread is called from many places, so best to verify here that the context passed

11. // in is themed.

12. context.assertRuntimeOverlayThemable();

13.

14. // ensure that only one thread is trying to power down.

15. // any additional calls are just returned

// 检查sIsStarted标志位,确保重启操作没有已经启动,防止重复调用

16. synchronized (sIsStartedGuard) {

17. if (sIsStarted) {

18. Log.d(TAG, "Request to shutdown already running, returning.");

19. return;

20. }

21. }

22. //根据longPressBehavior和mRebootSafeMode的值决定显示哪个确认对话框

23. final int longPressBehavior = context.getResources().getInteger(

24. com.android.internal.R.integer.config_longPressOnPowerBehavior);

25. final int resourceId = mRebootSafeMode

26. ? com.android.internal.R.string.reboot_safemode_confirm

27. : (longPressBehavior == 2

28. ? com.android.internal.R.string.shutdown_confirm_question

29. : com.android.internal.R.string.shutdown_confirm);

30.

31. Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);

32. //如果confirm为true,则显示确认对话框

33. if (confirm) {

34. final CloseDialogReceiver closer = new CloseDialogReceiver(context);

35. if (sConfirmDialog != null) {

36. sConfirmDialog.dismiss();

37. }

38. sConfirmDialog = new AlertDialog.Builder(context)

39. .setTitle(mRebootSafeMode

40. ? com.android.internal.R.string.reboot_safemode_title

41. : com.android.internal.R.string.power_off)

42. .setMessage(resourceId)

43. .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {

44. public void onClick(DialogInterface dialog, int which) {

45. beginShutdownSequence(context);

46. }

47. })

48. .setNegativeButton(com.android.internal.R.string.no, null)

49. .create();

50. closer.dialog = sConfirmDialog;

51. sConfirmDialog.setOnDismissListener(closer);

52. sConfirmDialog.getWindow().

setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);

53. sConfirmDialog.show();

54. } else {// 如果confirm为false,则直接调用beginShutdownSequence启动关机序列

55. beginShutdownSequence(context);

56. }

57. }

2. synchronized (sIsStartedGuard) {

//检查sIsStarted标志位,确保关机序列没有已经启动,防止重复启动

3. if (sIsStarted) {

4. Log.d(TAG, "Shutdown sequence already running, returning.");

5. return;

6. }

7. sIsStarted = true;

8. }

9. //显示shutdownDialog对话框并获取Context

10. sInstance.mProgressDialog = showShutdownDialog(context);

11. sInstance.mContext = context;

//获取PowerManager实例,用于后续获取WakeLock

12. sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

13.

14. // make sure we never fall asleep again

15. sInstance.mCpuWakeLock = null;

16. try {

17. sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(

18. PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");

19. sInstance.mCpuWakeLock.setReferenceCounted(false);

20. sInstance.mCpuWakeLock.acquire();

21. } catch (SecurityException e) {

22. Log.w(TAG, "No permission to acquire wake lock", e);

23. sInstance.mCpuWakeLock = null;

24. }

25.

26. // also make sure the screen stays on for better user experience

//检查屏幕是否已开启,如果是则获取一个FULL_WAKE_LOCK权限的ScreenWakeLock,保持屏幕常亮

27. sInstance.mScreenWakeLock = null;

28. if (sInstance.mPowerManager.isScreenOn()) {

29. try {

30. sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(

31. PowerManager.FULL_WAKE_LOCK, TAG + "-screen");

32. sInstance.mScreenWakeLock.setReferenceCounted(false);

33. sInstance.mScreenWakeLock.acquire();

34. } catch (SecurityException e) {

35. Log.w(TAG, "No permission to acquire wake lock", e);

36. sInstance.mScreenWakeLock = null;

37. }

38. }

39.

40. if (SecurityLog.isLoggingEnabled()) {

41. SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);

42. }

43.

44. // start the thread that initiates shutdown

//创建Handler并启动关机线程

45. sInstance.mHandler = new Handler() {

46. };

47. // 启动线程

48. sInstance.start();

49. }

2. TimingsTraceLog shutdownTimingLog = newTimingsLog();

3. shutdownTimingLog.traceBegin("SystemServerShutdown");

4. metricShutdownStart();

5. metricStarted(METRIC_SYSTEM_SERVER);

6.

7. BroadcastReceiver br = new BroadcastReceiver() {

8. @Override public void onReceive(Context context, Intent intent) {

9. // We don't allow apps to cancel this, so ignore the result.

10. actionDone();

11. }

12. };

13. ……

14. shutdownTimingLog.traceEnd(); // SystemServerShutdown

15. metricEnded(METRIC_SYSTEM_SERVER);

16. saveMetrics(mReboot, mReason);

17. // Remaining work will be done by init, including vold shutdown

// 重启前的准备工作结束, 开始重启

18. rebootOrShutdown(mContext, mReboot, mReason);

19. }

// 如果reboot为true,则先调用PowerManagerService.lowLevelReboot()尝试重启。如果重启失败,则将reason设置为null,并进行关机操作

2. if (reboot) {

3. Log.i(TAG, "Rebooting, reason: " + reason);

4. PowerManagerService.lowLevelReboot(reason);

5. Log.e(TAG, "Reboot failed, will attempt shutdown instead");

6. reason = null;

7. } else if (SHUTDOWN_VIBRATE_MS 0 && context != null) {

8. // vibrate before shutting down(关闭前振动)

9. Vibrator vibrator = new SystemVibrator(context);

10. try {

11. vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);

12. } catch (Exception e) {

13. // Failure to vibrate shouldn't interrupt shutdown. Just log it.

//振动故障不应中断停机。只需记录即可。

14. Log.w(TAG, "Failed to vibrate during shutdown.", e);

15. }

16.

17. // vibrator is asynchronous so we need to wait to avoid shutting down too soon.(可控震源是异步的,所以我们需要等待以避免过早关闭。)

18. try {

19. Thread.sleep(SHUTDOWN_VIBRATE_MS);

20. } catch (InterruptedException unused) {

21. }

22. }

23. // Shutdown power

24. Log.i(TAG, "Performing low-level shutdown...");

25. PowerManagerService.lowLevelShutdown(reason);

26. }

//检查reason是否为空,如果是则设置为空字符串

2. if (reason == null) {

3. reason = "";

4. }

5.

6. // If the reason is "quiescent", it means that the boot process should proceed

7. // without turning on the screen/lights.

8. // The "quiescent" property is sticky, meaning that any number

9. // of subsequent reboots should honor the property until it is reset.

//如果reason为"quiescent"或以",quiescent"结尾,则设置sQuiescent标志位,并截取reason

10. if (reason.equals(PowerManager.REBOOT_QUIESCENT)) {

11. sQuiescent = true;

12. reason = "";

13. } else if (reason.endsWith("," + PowerManager.REBOOT_QUIESCENT)) {

14. sQuiescent = true;

15. reason = reason.substring(0,

16. reason.length() - PowerManager.REBOOT_QUIESCENT.length() - 1);

17. }

18. // 如果reason为REBOOT_RECOVERY或REBOOT_RECOVERY_UPDATE,则将reason设置为"recovery"

19. if (reason.equals(PowerManager.REBOOT_RECOVERY)

20. || reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) {

21. // 走的这里

22. reason = "recovery";

23. }

24. // 如果sQuiescent被设置,则将",quiescent"追加到reason的末尾

25. if (sQuiescent) {

28. reason = reason + ",quiescent";

29. }

30. // 设置这个prop触发init进程重启进入recovery模式

31. SystemProperties.set("sys.powerctl", "reboot," + reason);

32. try {

33. Thread.sleep(20 * 1000L);

34. } catch (InterruptedException e) {

35. Thread.currentThread().interrupt();

36. }

37. Slog.wtf(TAG, "Unexpected return from lowLevelReboot!");

38. }

相关养生推荐

广大坛友对麦博的音箱如何评价
office365企业邮箱设置

广大坛友对麦博的音箱如何评价

📅 06-30 👁️ 2002
手机屏幕一直上下抖动怎么办
体育365真正官网下载

手机屏幕一直上下抖动怎么办

📅 02-08 👁️ 9799
海蜇丝用什么水泡好 海蜇怎么清洗和处理
office365企业邮箱设置

海蜇丝用什么水泡好 海蜇怎么清洗和处理

📅 02-03 👁️ 8074