1. 程式人生 > >android N 恢復出廠設定流程簡析

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);
    }