1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings;
18 
19 
20 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
21 
22 import android.app.ActionBar;
23 import android.app.Activity;
24 import android.app.ProgressDialog;
25 import android.app.admin.DevicePolicyManager;
26 import android.app.admin.FactoryResetProtectionPolicy;
27 import android.app.settings.SettingsEnums;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.ActivityInfo;
31 import android.graphics.Color;
32 import android.os.AsyncTask;
33 import android.os.Bundle;
34 import android.os.SystemProperties;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.service.oemlock.OemLockManager;
38 import android.service.persistentdata.PersistentDataBlockManager;
39 import android.util.Log;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.widget.Button;
44 import android.widget.TextView;
45 
46 import androidx.annotation.VisibleForTesting;
47 
48 import com.android.settings.core.InstrumentedFragment;
49 import com.android.settings.enterprise.ActionDisabledByAdminDialogHelper;
50 import com.android.settingslib.RestrictedLockUtilsInternal;
51 
52 import com.google.android.setupcompat.template.FooterBarMixin;
53 import com.google.android.setupcompat.template.FooterButton;
54 import com.google.android.setupcompat.template.FooterButton.ButtonType;
55 import com.google.android.setupcompat.util.WizardManagerHelper;
56 import com.google.android.setupdesign.GlifLayout;
57 
58 /**
59  * Confirm and execute a reset of the device to a clean "just out of the box"
60  * state.  Multiple confirmations are required: first, a general "are you sure
61  * you want to do this?" prompt, followed by a keyguard pattern trace if the user
62  * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
63  * ON THE PHONE" prompt.  If at any time the phone is allowed to go to sleep, is
64  * locked, et cetera, then the confirmation sequence is abandoned.
65  *
66  * This is the confirmation screen.
67  */
68 public class MainClearConfirm extends InstrumentedFragment {
69     private static final String TAG = "MainClearConfirm";
70 
71     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
72 
73     @VisibleForTesting View mContentView;
74     private boolean mEraseSdCard;
75     @VisibleForTesting boolean mEraseEsims;
76 
77     /**
78      * The user has gone through the multiple confirmation, so now we go ahead
79      * and invoke the Checkin Service to reset the device to its factory-default
80      * state (rebooting in the process).
81      */
82     private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
83 
84         public void onClick(View v) {
85             if (Utils.isMonkeyRunning()) {
86                 return;
87             }
88 
89             final PersistentDataBlockManager pdbManager;
90             // pre-flight check hardware support PersistentDataBlockManager
91             if (!SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("")) {
92                 pdbManager = (PersistentDataBlockManager)
93                     getActivity().getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
94             } else {
95                 pdbManager = null;
96             }
97 
98             if (shouldWipePersistentDataBlock(pdbManager)) {
99 
100                 new AsyncTask<Void, Void, Void>() {
101                     int mOldOrientation;
102                     ProgressDialog mProgressDialog;
103 
104                     @Override
105                     protected Void doInBackground(Void... params) {
106                         pdbManager.wipe();
107                         return null;
108                     }
109 
110                     @Override
111                     protected void onPostExecute(Void aVoid) {
112                         mProgressDialog.hide();
113                         if (getActivity() != null) {
114                             getActivity().setRequestedOrientation(mOldOrientation);
115                             doMainClear();
116                         }
117                     }
118 
119                     @Override
120                     protected void onPreExecute() {
121                         mProgressDialog = getProgressDialog();
122                         mProgressDialog.show();
123 
124                         // need to prevent orientation changes as we're about to go into
125                         // a long IO request, so we won't be able to access inflate resources on
126                         // flash
127                         mOldOrientation = getActivity().getRequestedOrientation();
128                         getActivity().setRequestedOrientation(
129                                 ActivityInfo.SCREEN_ORIENTATION_LOCKED);
130                     }
131                 }.execute();
132             } else {
133                 doMainClear();
134             }
135         }
136 
137         private ProgressDialog getProgressDialog() {
138             final ProgressDialog progressDialog = new ProgressDialog(getActivity());
139             progressDialog.setIndeterminate(true);
140             progressDialog.setCancelable(false);
141             progressDialog.setTitle(
142                     getActivity().getString(R.string.main_clear_progress_title));
143             progressDialog.setMessage(
144                     getActivity().getString(R.string.main_clear_progress_text));
145             return progressDialog;
146         }
147     };
148 
149     @VisibleForTesting
shouldWipePersistentDataBlock(PersistentDataBlockManager pdbManager)150     boolean shouldWipePersistentDataBlock(PersistentDataBlockManager pdbManager) {
151         if (pdbManager == null) {
152             return false;
153         }
154 
155         // The persistent data block will persist if the device is still being provisioned.
156         if (isDeviceStillBeingProvisioned()) {
157             return false;
158         }
159 
160         // If OEM unlock is allowed, the persistent data block will be wiped during the FR
161         // process on devices without FRP Hardening. If disabled, it will be wiped here instead.
162         // On devices with FRP Hardening, the persistent data block should always be wiped,
163         // regardless of the OEM Unlocking state.
164         if (!android.security.Flags.frpEnforcement() && isOemUnlockedAllowed()) {
165             return false;
166         }
167 
168         final DevicePolicyManager dpm = (DevicePolicyManager) getActivity()
169                 .getSystemService(Context.DEVICE_POLICY_SERVICE);
170         // Do not erase the factory reset protection data (from Settings) if factory reset
171         // protection policy is not supported on the device.
172         if (!dpm.isFactoryResetProtectionPolicySupported()) {
173             return false;
174         }
175 
176         // Do not erase the factory reset protection data (from Settings) if the
177         // device is an organization-owned managed profile device and a factory
178         // reset protection policy has been set.
179         FactoryResetProtectionPolicy frpPolicy = dpm.getFactoryResetProtectionPolicy(null);
180         if (dpm.isOrganizationOwnedDeviceWithManagedProfile() && frpPolicy != null
181                 && frpPolicy.isNotEmpty()) {
182             return false;
183         }
184 
185         return true;
186     }
187 
188     @VisibleForTesting
isOemUnlockedAllowed()189     boolean isOemUnlockedAllowed() {
190         return ((OemLockManager) getActivity().getSystemService(
191                 Context.OEM_LOCK_SERVICE)).isOemUnlockAllowed();
192     }
193 
194     @VisibleForTesting
isDeviceStillBeingProvisioned()195     boolean isDeviceStillBeingProvisioned() {
196         return !WizardManagerHelper.isDeviceProvisioned(getActivity());
197     }
198 
doMainClear()199     private void doMainClear() {
200         Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
201         intent.setPackage("android");
202         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
203         intent.putExtra(Intent.EXTRA_REASON, "MainClearConfirm");
204         intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, mEraseSdCard);
205         intent.putExtra(Intent.EXTRA_WIPE_ESIMS, mEraseEsims);
206         getActivity().sendBroadcast(intent);
207         // Intent handling is asynchronous -- assume it will happen soon.
208     }
209 
210     /**
211      * Configure the UI for the final confirmation interaction
212      */
establishFinalConfirmationState()213     private void establishFinalConfirmationState() {
214         final GlifLayout layout = mContentView.findViewById(R.id.setup_wizard_layout);
215 
216         final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
217         mixin.setPrimaryButton(
218                 new FooterButton.Builder(getActivity())
219                         .setText(R.string.main_clear_button_text)
220                         .setListener(mFinalClickListener)
221                         .setButtonType(ButtonType.OTHER)
222                         .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
223                         .build()
224         );
225     }
226 
setUpActionBarAndTitle()227     private void setUpActionBarAndTitle() {
228         final Activity activity = getActivity();
229         if (activity == null) {
230             Log.e(TAG, "No activity attached, skipping setUpActionBarAndTitle");
231             return;
232         }
233         final ActionBar actionBar = activity.getActionBar();
234         if (actionBar == null) {
235             Log.e(TAG, "No actionbar, skipping setUpActionBarAndTitle");
236             return;
237         }
238         actionBar.hide();
239         activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
240     }
241 
242     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)243     public View onCreateView(LayoutInflater inflater, ViewGroup container,
244             Bundle savedInstanceState) {
245         final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
246                 getActivity(), UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId());
247         if (RestrictedLockUtilsInternal.hasBaseUserRestriction(getActivity(),
248                 UserManager.DISALLOW_FACTORY_RESET, UserHandle.myUserId())) {
249             return inflater.inflate(R.layout.main_clear_disallowed_screen, null);
250         } else if (admin != null) {
251             new ActionDisabledByAdminDialogHelper(getActivity())
252                     .prepareDialogBuilder(UserManager.DISALLOW_FACTORY_RESET, admin)
253                     .setOnDismissListener(__ -> getActivity().finish())
254                     .show();
255             return new View(getActivity());
256         }
257         mContentView = inflater.inflate(R.layout.main_clear_confirm, null);
258         setUpActionBarAndTitle();
259         establishFinalConfirmationState();
260         setAccessibilityTitle();
261         setSubtitle();
262         return mContentView;
263     }
264 
setAccessibilityTitle()265     private void setAccessibilityTitle() {
266         CharSequence currentTitle = getActivity().getTitle();
267         TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
268         if (confirmationMessage != null) {
269             String accessibleText = new StringBuilder(currentTitle).append(",").append(
270                     confirmationMessage.getText()).toString();
271             getActivity().setTitle(Utils.createAccessibleSequence(currentTitle, accessibleText));
272         }
273     }
274 
275     @VisibleForTesting
setSubtitle()276     void setSubtitle() {
277         if (mEraseEsims) {
278             TextView confirmationMessage = mContentView.findViewById(R.id.sud_layout_description);
279             if (confirmationMessage != null) {
280                 confirmationMessage.setText(R.string.main_clear_final_desc_esim);
281                 }
282         }
283     }
284 
285     @Override
onCreate(Bundle savedInstanceState)286     public void onCreate(Bundle savedInstanceState) {
287         super.onCreate(savedInstanceState);
288 
289         Bundle args = getArguments();
290         mEraseSdCard = args != null
291                 && args.getBoolean(MainClear.ERASE_EXTERNAL_EXTRA);
292         mEraseEsims = args != null
293                 && args.getBoolean(MainClear.ERASE_ESIMS_EXTRA);
294     }
295 
296     @Override
getMetricsCategory()297     public int getMetricsCategory() {
298         return SettingsEnums.MASTER_CLEAR_CONFIRM;
299     }
300 }
301