1. 程式人生 > >Android6.0 Reset恢復出廠設定流程分析

Android6.0 Reset恢復出廠設定流程分析

點選Settings應用中的恢復出廠設定按鈕後流程分析:

先使用grep命令搜尋"恢復出廠設定"字串,找到相應的佈局檔案:

packages/apps/Settings/res/xml/privacy_settings.xml

    <PreferenceScreen
        android:key="factory_reset"
        android:title="@string/master_clear_title"
        settings:keywords="@string/keywords_factory_data_reset"
        android:fragment="com.android.settings.MasterClear" />
在這個節點下我們可以看到這麼一句話:
android:fragment="com.android.settings.MasterClear"
這句話表示當點選"恢復出廠設定"這個item後,會直接跳轉到MasterClear.java這個Fragment子類內:
先來看onCreateView()方法,這個方法是來載入佈局檔案的:
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (!Process.myUserHandle().isOwner()
                || UserManager.get(getActivity()).hasUserRestriction(
                UserManager.DISALLOW_FACTORY_RESET)) {
            return inflater.inflate(R.layout.master_clear_disallowed_screen, null);
        }

        mContentView = inflater.inflate(R.layout.master_clear, null);

        establishInitialState();
        return mContentView;
    }
判斷使用者是否具有"恢復出廠設定"的許可權,根據判斷結果來載入佈局;由於分析的是"恢復出廠設定"流程,所有直接預設它載入的是master_clear.xml佈局。
接下來再來看establishInitialState()方法,這個方法主要是作為初始化佈局控制元件並未其設定相應的點選事件的作用;
    /**
     * In its initial state, the activity presents a button for the user to
     * click in order to initiate a confirmation sequence.  This method is
     * called from various other points in the code to reset the activity to
     * this base state.
     *
     * <p>Reinflating views from resources is expensive and prevents us from
     * caching widget pointers, so we use a single-inflate pattern:  we lazy-
     * inflate each view, caching all of the widget pointers we'll need at the
     * time, then simply reuse the inflated views directly whenever we need
     * to change contents.
     */
    private void establishInitialState() {
        mInitiateButton = (Button) mContentView.findViewById(R.id.initiate_master_clear);
        mInitiateButton.setOnClickListener(mInitiateListener);
        mExternalStorageContainer = mContentView.findViewById(R.id.erase_external_container);
        mExternalStorage = (CheckBox) mContentView.findViewById(R.id.erase_external);

        /*
         * If the external storage is emulated, it will be erased with a factory
         * reset at any rate. There is no need to have a separate option until
         * we have a factory reset that only erases some directories and not
         * others. Likewise, if it's non-removable storage, it could potentially have been
         * encrypted, and will also need to be wiped.
         */
        boolean isExtStorageEmulated = false;
        StorageHelpUtil mStorageHelpUtil = new StorageHelpUtil();
        if (mStorageHelpUtil.getSdCardPath(getActivity()) != null) {
            isExtStorageEmulated = true;
        }
        if (isExtStorageEmulated) {
            mExternalStorageContainer.setVisibility(View.VISIBLE);

            final View externalOption = mContentView.findViewById(R.id.erase_external_option_text);
            externalOption.setVisibility(View.VISIBLE);

            final View externalAlsoErased = mContentView.findViewById(R.id.also_erases_external);
            externalAlsoErased.setVisibility(View.VISIBLE);

            // If it's not emulated, it is on a separate partition but it means we're doing
            // a force wipe due to encryption.
            mExternalStorage.setChecked(!isExtStorageEmulated);
        } else {
                mExternalStorageContainer.setVisibility(View.GONE);
                final View externalOption = mContentView.findViewById(R.id.erase_external_option_text);
                externalOption.setVisibility(View.GONE);
            mExternalStorageContainer.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    mExternalStorage.toggle();
                }
            });
        }

        final UserManager um = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
        loadAccountList(um);
        StringBuffer contentDescription = new StringBuffer();
        View masterClearContainer = mContentView.findViewById(R.id.master_clear_container);
        getContentDescription(masterClearContainer, contentDescription);
        masterClearContainer.setContentDescription(contentDescription);
    }
1、初始化"恢復出廠設定"按鈕並未其設定點選事件監聽mInitiateListener;
2、判斷是否插入SDcard,如果插入則顯示是否清空SDcard的提醒訊息,例項化選擇框CheckBox並設定點選事件;
3、載入使用者列表,loadAccountList();
4、例項化提醒資訊區域,並顯示提醒資訊到介面上;

接下來再來看mInitiateListener監聽器:

    /**
     * If the user clicks to begin the reset sequence, we next require a
     * keyguard confirmation if the user has currently enabled one.  If there
     * is no keyguard available, we simply go to the final confirmation prompt.
     */
    private final Button.OnClickListener mInitiateListener = new Button.OnClickListener() {

        public void onClick(View v) {
            if (!runKeyguardConfirmation(KEYGUARD_REQUEST)) {
                Intent intent = new Intent("android.settings.PASSWORD_MANAGER");
                startActivityForResult(intent,PASSWORD_MANAGER);
            }
        }
    };
startActivityForResult()方法進入密碼介面,並返回結果,直接來看onActivityResult()方法:
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode != KEYGUARD_REQUEST && requestCode != PASSWORD_MANAGER) {
            return;
        } else if (requestCode == PASSWORD_MANAGER) {
             if (resultCode == Activity.RESULT_OK) {
                 validatePassword = data.getExtras().getBoolean("password");
                 if (validatePassword) {
                    showFinalConfirmation();
                 }
             }
             return;
        }

        // If the user entered a valid keyguard trace, present the final
        // confirmation prompt; otherwise, go back to the initial state.
        if (resultCode == Activity.RESULT_OK) {
            showFinalConfirmation();
        } else {
            establishInitialState();
        }
    }
如果密碼確認,呼叫showFinalConfirmation()方法:
    private void showFinalConfirmation() {
        Bundle args = new Bundle();
        args.putBoolean(ERASE_EXTERNAL_EXTRA, mExternalStorage.isChecked());
        ((SettingsActivity) getActivity()).startPreferencePanel(MasterClearConfirm.class.getName(),
                args, R.string.master_clear_confirm_title, null, null, 0);
    }
進入MasterClearConfirm.java內,並傳入是否勾選清除sd卡資料的引數;
MasterClearConfirm.java類是用來確認是否開始"恢復出廠設定",在這個類裡面我們只需要關心最重要的doMasterClear()方法即可
    private void doMasterClear() {
        Intent intent = new Intent(Intent.ACTION_MASTER_CLEAR);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm");
        intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard);
        getActivity().sendBroadcast(intent);
        // Intent handling is asynchronous -- assume it will happen soon.
    }
這個方法主要是用來發送廣播,通知android系統開始"恢復出廠設定",最終的接收方法廣播的地方是在
frameworks/base/services/core/java/com/android/server/MasterClearReceiver.java類裡面;

onReceiver()方法:

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

        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 {
                    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);
                }
            }
        };
        if (wipeExternalStorage) {
            // thr will be started at the end of this task.
            new WipeAdoptableDisksTask(context, thr).execute();
        } else {
            thr.start();
        }
    }
通過對比傳送廣播的地方,只需要關心兩個引數reason和wipeExternalStorage;

1、wipeExternalStorage為true時,表示需要清除sdcard的資料;開啟一個非同步任務:

new WipeAdoptableDisksTask(context, thr).execute();
    private class WipeAdoptableDisksTask extends AsyncTask<Void, Void, Void> {
        private final Thread mChainedTask;
        private final Context mContext;
        private final ProgressDialog mProgressDialog;

        public WipeAdoptableDisksTask(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");
            StorageManager sm = (StorageManager) mContext.getSystemService(
                    Context.STORAGE_SERVICE);
            sm.wipeAdoptableDisks();
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            mProgressDialog.dismiss();
            mChainedTask.start();
        }

    }
1)首先執行onPreExecute()方法,此時是在UI Thread內;設定Dialog,提醒使用者;
2)執行doInBackground()方法,這個方法一般是用來執行耗時操作;使用StorageManager去清除sdcard內容;
3)最後執行onPostExecute()方法,這個方法是當我們的非同步任務執行完之後,就會將結果返回給這個方法;開啟一個thr執行緒,這個執行緒也是wipeExternalStorage為false需要執行的,後面一起分析。

2、wipeExternalStorage為false時,表示只需要清除使用者資料;此時只要需要開啟執行thr執行緒即可;

thr.start();
        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);
                }
            }
        };
呼叫RecoverySystem.java的rebootWipeUserData()方法:
    /**
     * 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);
    }
1、檢視當前使用者是否有許可權清除資料;
2、傳送有序廣播並中斷執行緒,直到有序廣播完成;
3、封裝命令引數,執行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)) {
                    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?)");
    }
1、將傳入的命令引數寫入到"/cache/recovery/command"檔案中去;
2、呼叫PowerManager.java的reboot()方法進行重啟操作;
    public void reboot(String reason) {
        try {
            mService.reboot(false, reason, true);
        } catch (RemoteException e) {
        }
    }
aidl調入到PowerManagerService.java的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);
            }
        }
首先檢查是否有REBOOT和RECOVERY許可權,然後呼叫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) {
                    }
                }
            }
        }
    }
這個方法最主要的還是Runnable內的run()方法實現,由於之前傳進來的shutdown為false,於是繼續呼叫:
ShutdownThread.reboot(mContext, reason, confirm);
    /**
     * 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;
        mRebootUpdate = false;
        mRebootReason = reason;
        shutdownInner(context, confirm);
    }
繼續呼叫shutdownInner()方法,此時confirm為false;
shutdownInner()這個方法很長,主要的作用是建立彈出框,根據傳入引數confirm來判斷是否顯示需要使用者確認關機或者重啟的Dialog,然後開執行緒執行關機或者重啟操作。後續的流程分析可以去檢視另外一篇文章:Android6.0 關機shutdown & 重啟reboot流程分析


裝置重啟後,會自動進入recovery mode模式,讀取/cache/recovery/command, 內容為"--wipe_data",開始清除data和cache分割槽,清除成功後,系統重啟,然後進入正常開機流程,重新使用system分割槽的內容完成開機初始化,此過程跟我們第一次燒寫軟體過程一致。
至此完成恢復出廠設定。


恢復出廠設定總結:
1、在MasterClearConfirm.java中確認開始執行恢復出廠設定操作,併發出"恢復出廠設定"的廣播;
2、在MasterClearReceiver.java接收MasterClearConfirm.java發出的廣播,根據是否清除sdcard選項來執行相應的操作;
3、呼叫RecoverySystem.rebootWipeUserData()方法來清除使用者資料並重啟裝置;這個方法執行過程中會發出"android.intent.action.MASTER_CLEAR_NOTIFICATION"廣播、寫"/cache/recovery/command"檔案(內容包含"--wipe_data"),然後重啟裝置;
4、裝置重啟後進入recovery mode之後,讀取/cache/recovery/command, 內容為"--wipe_data";
5.按照讀取的command,進行wipe data清除資料操作;
6.清除成功後,系統重啟,然後進入正常開機流程。