Bootstrap

Android 恢复出厂设置(recovery)


Android 恢复出厂设置基本流程

(1)遥控器/按键板后门键触发,或者应用里面从系统设置里面恢复出厂选项也可触发; // 后面以系统设置的应用触发为例
(2)选择恢复出厂设置之后,就会发送广播“android.intent.action.MASTER_CLEAR” ;// framework/base/core/res/AndroidManifest.xml
(3)MasterClearReceiver 捕获广播 ,并进行android 层的相关处理最后重启 ;
(4)往 /cache/recovery/command 文件中写入命令字段;
(5)重启系统;

recovery 进入方式

(1) 通过读取 /cache 分区中文件 /cache/recovery/command 内容进入 
(2)通过按键操作进入 (G1 通过同时按 HOME 和 挂断键) 
          以上两种方式进入都需要 blob的支持

进入recovery 的条件

(1) blob 必须能从 recovery 分区中装载内核和文件系统 
2 )flash 必须有 cache 分区 和 recovery 分区 
3 )必须编译提供 recovery.img 烧录到 recovery 分区


Android 的处理流程

广播接收
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>
MasterClearReceiver接收广播android.intent.action.MASTER_CLEAR,创建一个县城作一下处理
Thread thr = new Thread("Reboot") {
    @Override
    public void run() {
        try {
            RecoverySystem.rebootWipeUserData(context, shutdown, reason);
            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);
        }
    }
};
thr.start();
RecoverySystem 来重启,启动擦除用户数据的操作
/**
 * Reboots the device and wipes the user data and cache
 * partitions.  This is sometimes called a "factory reset", which
 * is something of a misnomer because the system partition is not
 * restored to its factory state.  Requires the
 * {@link android.Manifest.permission#REBOOT} permission.
 *
 * @param context   the Context to use
 * @param shutdown  if true, the device will be powered down after
 *                  the wipe completes, rather than being rebooted
 *                  back to the regular system.
 *
 * @throws IOException  if writing the recovery command file
 * fails, or if the reboot itself fails.
 * @throws SecurityException if the current user is not allowed to wipe data.
 *
 * @hide
 */
public static void rebootWipeUserData(Context context, boolean shutdown, String reason)
        throws IOException {
    UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
    if (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.OWNER,
            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();
    bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
}
我们可以注意到在启动bootCommand传递命令时,封装参数 --wipe_data , --locale , 这些命令我们可以在查看recovery log ( /cache/recovery/*.log )信息时看到
“Command: "/sbin/recovery" "--wipe_data" "--locale=zh_CN"  ,其实这应该也就是bootCommand 执行的命令
/**
 * Reboot into the recovery system with the supplied argument.
 * @param args to pass to the recovery utility.
 * @throws IOException if something goes wrong.
 */
private static void bootCommand(Context context, String... args) throws IOException {
    RECOVERY_DIR.mkdirs();  // In case we need it
    COMMAND_FILE.delete();  // In case it's not writable
    LOG_FILE.delete();

    FileWriter command = new FileWriter(COMMAND_FILE);
    try {
        for (String arg : args) {
            if (!TextUtils.isEmpty(arg)) {
                // MStar Android Patch Begin
                String cmd = arg;
                String label = null;
                String uuid = null;
                if (cmd.startsWith("--update_package")) {
                    cmd = arg.substring(17, 23);
                    if (cmd.equals("/cache")) {
                        command.write("--uuid=mstar-cache");
                        command.write("\n");
                        command.write("--label=mstar-cache");
                        command.write("\n");
                    } else {
                        cmd = arg.substring(17, 28);
                        if (cmd.equals("/mnt/usb/sd")) {
                            cmd = arg.substring(17, 30);
                            uuid = "--uuid=" + getVolumeUUID(cmd).toString();
                            label = "--label=" + getVolumeLabel(cmd).toString();
                            command.write(uuid);
                            command.write("\n");
                            command.write(label);
                            command.write("\n");
                        } else {
                            if (cmd.equals("/mnt/sdcard")) {
                                uuid = "--uuid=" + getVolumeUUID(cmd).toString();
                                label = "--label=" + getVolumeLabel(cmd).toString();
                                command.write(uuid);
                                command.write("\n");
                                command.write(label);
                                command.write("\n");
                            } else {
                                cmd = arg.substring(17, 32);
                                if (cmd.equals("/mnt/usb/mmcblk")) {
                                    cmd = arg.substring(17, 35);
                                    uuid = "--uuid=" + getVolumeUUID(cmd).toString();
                                    label = "--label=" + getVolumeLabel(cmd).toString();
                                    command.write(uuid);
                                    command.write("\n");
                                    command.write(label);
                                    command.write("\n");
                                }
                            }
                        }
                    }
                }
                // MStar Android Patch End
                command.write(arg);
                command.write("\n");
            }
        }
    } finally {
        command.close();
    }

    // Having written the command file, go ahead and reboot
    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    pm.reboot(PowerManager.REBOOT_RECOVERY);

    throw new IOException("Reboot failed (no permissions?)");
}
从以上代码分析,bootCommand 主要工作就是重启进入recovery,此处可以看到COMMAND_FILE 就是文件 “ /cache/recovery/command " , 将上面封装的参数信息
写入改文件,待重启之后读取该文件时进入recovery模式,另外我们看到写完文件之后,调用PowerManager 来reboot,注意参数PowerManager.REBOOT_RECOVERY
// Having written the command file, go ahead and reboot
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
pm.reboot(PowerManager.REBOOT_RECOVERY);

/**
 * Reboot the device.  Will not return if the reboot is successful.
 * <p>
 * Requires the {@link android.Manifest.permission#REBOOT} permission.
 * </p>
 *
 * @param reason code to pass to the kernel (e.g., "recovery") to
 *               request special boot modes, or null.
 */
public void reboot(String reason) {
    try {
        mService.reboot(false, reason, true);
    } catch (RemoteException e) {
    }
}
最后又进入PowerManagerService 的reboot函数
/**
 * Reboots the device.
 *
 * @param confirm If true, shows a reboot confirmation dialog.
 * @param reason The reason for the reboot, or null if none.
 * @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)) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
    }

    final long ident = Binder.clearCallingIdentity();
    try {
        shutdownOrRebootInternal(false, confirm, reason, wait);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}
接着进入shutdownOrRebootInternal
private void shutdownOrRebootInternal(final boolean shutdown, 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 (shutdown) {
                    ShutdownThread.shutdown(mContext, confirm);
                } else {
                    ShutdownThread.reboot(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) {
                }
            }
        }
    }
}
ShutdownThread来负责重启动作
/**
 * 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.
 * @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;
    mRebootReason = reason;
    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;
        }
    }

    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 {
        beginShutdownSequence(context);
    }
}
beginShutdownSequence进入主要的关机流程, 接着启动ShutdownThread.run() , 发送光机广播,关闭核心服务,最后进入rebootOrShutdown重启。


recovery进入流程

进入recovery有几种途径:
(1)进入recovery前先写misc分区,重启时发现变化就直接进入recovery模式;
(2)写文件 /cache/recovery/command 文件,重启时进去recovery模式; // 此种模式暂未找到启动recovery的地方,只是在启动recovery后有看到读
                                                                                                                                           // 取/cache/recovery/command 文件数据再做后续操作

MISC分区内容

Bootloader Control Block(BCB) 存放recovery bootloader message,结构如下:
struct bootloader_message {
    char command[32];
    char status[32];
    char recovery[768];


    // The 'recovery' field used to be 1024 bytes.  It has only ever
    // been used to store the recovery command line, so 768 bytes
    // should be plenty.  We carve off the last 256 bytes to store the
    // stage string (for multistage packages) and possible future
    // expansion.
    char stage[32];
    char reserved[224];
};
command可以有以下两个值
“boot-recovery”:表示recovery正在进行或者指示bootloader应该进入recovery mode
“update--hboot/radio”:标志bootloader更新fireware

recovery内容

“recovery\n
<recovery command>\n
<recovery command>\n”其中 recovery command为CACHE:/recovery/command命令


Recovery Case 

Factory reset(恢复出厂设置)



1. 用户选择“恢复出厂设置”
2. 设置系统将“--wipe_data”命令写入 /cache/recovery/command
3. 系统重启,并进入recovery模式 (sbin/recovery  or /system/bin/recovery)
4. recovery get_args() 将“boot-recovery”和“--wipe_data”写入BCB
5. erase_root 格式化DATA 分区
6. erase_root 格式化CACHE 分区
7. finish_recovery 擦除BCB分区
8. 重启系统

OTA INSTALL (OTA升级)

1. 升级系统系在OTA包包/cache/some-filename.zip
2. 升级系统写入recovery命令 “--update_package=CACHE:some-filename.zip”
3. 重启系统,进入recovery模式
4. get_args()将“boot-recovery”和“--wipe_packkage=...”写入BCB
5. install_package 做升级
6. finish_recovery() 擦除BCB
7. **如果安装包失败**prompt_and_wait()等待用户操作,选择ALT+S或者ALT+W升级或回复出厂设置
8. main() 里面调用maybe_install_firmware_update()
    1.如果包里含有hboot/radio的fireware则继续,否则返回
    2.将"boot-recovery"和"--wipe_cache"写入BCB
    3.将fireware image写入cache分区
    4.将"update-readio/hboot"和“--wipe_date”写入BCB
    5.重启系统
    6.bootloader自身更新fireware
    7.bootloader将"boot-recovery"写入BCB
    8.erase_root擦除CACHE分区
    9.清除BCB
9. main 调用reboot重启系统




Recovery代码位置:bootable/recovery/ ,主文件recovery.cpp


后续再分析 recovery流程
                  

Android Recovery 模式学习










;