Analysis of the process of restoring factory settings with Android 6.0 Reset

Process analysis after clicking the restore factory settings button in the Settings application:

First use the grep command to search for the "restore factory settings" string and find the corresponding layout file:

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" />
Under this node we can see such a sentence:

android:fragment="com.android.settings.MasterClear"
This sentence means that when the item "Restore Factory Settings" is clicked, it will jump directly to the Fragment subclass of MasterClear.java:
First look at the onCreateView() method, which is to load the layout file:

    @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;
    }
Judge whether the user has the authority to "restore factory settings", and load the layout according to the judgment result; because the analysis is the "restore factory settings" process, all directly default it to load the master_clear.xml layout.
Next, let’s look at the establishInitialState() method. This method is mainly used to initialize the layout control without setting the corresponding click event;

    /**
     * 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. Initialize the "Restore Factory Settings" button and not set the click event to listen to mInitiateListener;
2. Determine whether the SDcard is inserted, if it is inserted, a reminder message of whether to clear the SDcard will be displayed, instantiate the checkbox of the checkbox and set the click event;
3. Load the user List, loadAccountList();
4. Instantiate the reminder information area and display the reminder information on the interface;

Next look at the mInitiateListener listener:

    /**
     * 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);
            }
        }
    };
The startActivityForResult() method enters the password interface and returns the result. Let’s look directly at the onActivityResult() method:

    @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();
        }
    }
If the password is confirmed, call the showFinalConfirmation() method:
    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);
    }
Enter MasterClearConfirm.java, and pass in the parameters of whether to check the clear SD card data; the
MasterClearConfirm.java class is used to confirm whether to start "factory reset", in this class we only need to care about the most important doMasterClear() method Can

    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.
    }
This method is mainly used to send a broadcast to notify the android system to start "restore factory settings". The final receiving method broadcast is in the
frameworks/base/services/core/java/com/android/server/MasterClearReceiver.java class;

onReceiver() method:

    @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();
        }
    }
By comparing the place where the broadcast is sent, only need to care about the two parameters reason and wipeExternalStorage;

1. When wipeExternalStorage is true, it means that sdcard data needs to be cleared; an asynchronous task is started:

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) First execute the onPreExecute() method, in the UI Thread at this time; set the Dialog to remind the user;
2) execute the doInBackground() method, this method is generally used to perform time-consuming operations; use the StorageManager to clear the sdcard content;
3 ) Finally, execute the onPostExecute() method. This method will return the result to this method after our asynchronous task is executed; start a thr thread, which also needs to be executed when wipeExternalStorage is false, and will be analyzed later.

2. When wipeExternalStorage is false, it means that only user data needs to be cleared; at this time, only need to open the execution thr thread;

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);
                }
            }
        };
Call the rebootWipeUserData() method of RecoverySystem.java:

    /**
     * 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. Check whether the current user has permission to clear data;
2. Send an orderly broadcast and interrupt the thread until the orderly broadcast is completed;
3. Encapsulate the command parameters and execute the bootCommand() method;

    /**
     * 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. Write the incoming command parameters into the "/cache/recovery/command" file;
2. Call the reboot() method of PowerManager.java to restart the operation;

    public void reboot(String reason) {
        try {
            mService.reboot(false, reason, true);
        } catch (RemoteException e) {
        }
    }
Aidl is transferred to the reboot() method of PowerManagerService.java:

        /**
         * 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);
            }
        }
First check if you have REBOOT and RECOVERY permissions, and then call the shutdownOrRebootInternal() method:

    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) {
                    }
                }
            }
        }
    }
The most important thing about this method is the implementation of the run() method in Runnable. Since the shutdown passed in before is false, it continues to call:

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);
    }
Continue to call the shutdownInner() method, at this time confirm is false; the
shutdownInner() method is very long, the main function is to create a pop-up box, according to the input parameter confirm to determine whether to display the Dialog that requires the user to confirm shutdown or restart, and then start the thread Perform shutdown or restart operations. Follow-up process analysis can go to see another article: Android6.0 shutdown shutdown & restart process analysis


After the device restarts, it will automatically enter the recovery mode, read /cache/recovery/command, the content is "--wipe_data", and start to clear the data and cache partitions. After the clearing is successful, the system restarts, and then enters the normal boot process and reuses The content of the system partition is initialized after booting. This process is consistent with our first software programming process.
At this point, the factory reset is completed.


The summary of restoring factory settings:
1. Confirm in MasterClearConfirm.java to start the operation of restoring factory settings, and send out the broadcast of "restore factory settings";
2. Receive the broadcast from MasterClearConfirm.java in MasterClearReceiver.java, according to whether to clear the sdcard option. Perform corresponding operations;
3. Call RecoverySystem.rebootWipeUserData() method to clear user data and restart the device; during the execution of this method, "android.intent.action.MASTER_CLEAR_NOTIFICATION" broadcast and write "/cache/recovery/command" file will be issued (The content contains "--wipe_data"), and then restart the device;
4. After the device enters recovery mode after restarting, read /cache/recovery/command, the content is "--wipe_data";
5. Follow the read command, proceed Wipe data clear data operation;
6. After clearing successfully, the system restarts, and then enters the normal boot process.

Guess you like

Origin blog.csdn.net/Otaku_627/article/details/53899346