1 /* 2 * Copyright (C) 2008 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.server; 18 19 import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; 20 21 import android.annotation.UserIdInt; 22 import android.app.ActivityManager; 23 import android.app.Notification; 24 import android.app.NotificationManager; 25 import android.app.ProgressDialog; 26 import android.app.admin.DevicePolicyManager; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.os.AsyncTask; 31 import android.os.Binder; 32 import android.os.RecoverySystem; 33 import android.os.RemoteException; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.os.storage.StorageManager; 37 import android.text.TextUtils; 38 import android.util.Slog; 39 import android.view.WindowManager; 40 41 import com.android.internal.R; 42 import com.android.internal.messages.nano.SystemMessageProto; 43 import com.android.internal.notification.SystemNotificationChannels; 44 import com.android.server.utils.Slogf; 45 46 import java.io.IOException; 47 48 public class MasterClearReceiver extends BroadcastReceiver { 49 private static final String TAG = "MasterClear"; 50 private boolean mWipeExternalStorage; 51 private boolean mWipeEsims; 52 private boolean mShowWipeProgress = true; 53 54 @Override onReceive(final Context context, final Intent intent)55 public void onReceive(final Context context, final Intent intent) { 56 if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) { 57 if (!"google.com".equals(intent.getStringExtra("from"))) { 58 Slog.w(TAG, "Ignoring master clear request -- not from trusted server."); 59 return; 60 } 61 } 62 if (Intent.ACTION_MASTER_CLEAR.equals(intent.getAction())) { 63 Slog.w(TAG, "The request uses the deprecated Intent#ACTION_MASTER_CLEAR, " 64 + "Intent#ACTION_FACTORY_RESET should be used instead."); 65 } 66 if (intent.hasExtra(Intent.EXTRA_FORCE_MASTER_CLEAR)) { 67 Slog.w(TAG, "The request uses the deprecated Intent#EXTRA_FORCE_MASTER_CLEAR, " 68 + "Intent#EXTRA_FORCE_FACTORY_RESET should be used instead."); 69 } 70 71 final String factoryResetPackage = context 72 .getString(com.android.internal.R.string.config_factoryResetPackage); 73 if (Intent.ACTION_FACTORY_RESET.equals(intent.getAction()) 74 && !TextUtils.isEmpty(factoryResetPackage)) { 75 Slog.i(TAG, "Re-directing intent to " + factoryResetPackage); 76 intent.setPackage(factoryResetPackage).setComponent(null); 77 context.sendBroadcastAsUser(intent, UserHandle.SYSTEM); 78 return; 79 } 80 81 final String reason = intent.getStringExtra(Intent.EXTRA_REASON); 82 83 // Factory reset dialog has its own UI for the reset process, so suppress ours if indicated. 84 mShowWipeProgress = intent.getBooleanExtra(Intent.EXTRA_SHOW_WIPE_PROGRESS, true); 85 86 final boolean shutdown = intent.getBooleanExtra("shutdown", false); 87 mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false); 88 mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false); 89 final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false) 90 || intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false); 91 // This is ONLY used by TestHarnessService within System Server, so we don't add a proper 92 // API constant in Intent for this. 93 final boolean keepMemtagMode = intent.getBooleanExtra("keep_memtag_mode", false); 94 95 // TODO(b/189938391): properly handle factory reset on headless system user mode. 96 final int sendingUserId = getSendingUserId(); 97 if (sendingUserId != UserHandle.USER_SYSTEM && !UserManager.isHeadlessSystemUserMode()) { 98 Slogf.w( 99 TAG, 100 "ACTION_FACTORY_RESET received on a non-system user %d, WIPING THE USER!!", 101 sendingUserId); 102 if (!Binder.withCleanCallingIdentity(() -> wipeUser(context, sendingUserId, reason))) { 103 Slogf.e(TAG, "Failed to wipe user %d", sendingUserId); 104 } 105 return; 106 } 107 108 Slog.w(TAG, "!!! FACTORY RESET !!!"); 109 // The reboot call is blocking, so we need to do it on another thread. 110 Thread thr = new Thread("Reboot") { 111 @Override 112 public void run() { 113 try { 114 Slog.i(TAG, "Calling RecoverySystem.rebootWipeUserData(context, " 115 + "shutdown=" + shutdown + ", reason=" + reason 116 + ", forceWipe=" + forceWipe + ", wipeEsims=" + mWipeEsims 117 + ", keepMemtagMode=" + keepMemtagMode + ")"); 118 RecoverySystem 119 .rebootWipeUserData( 120 context, shutdown, reason, forceWipe, mWipeEsims, keepMemtagMode); 121 Slog.wtf(TAG, "Still running after master clear?!"); 122 } catch (IOException e) { 123 Slog.e(TAG, "Can't perform master clear/factory reset", e); 124 } catch (SecurityException e) { 125 Slog.e(TAG, "Can't perform master clear/factory reset", e); 126 } 127 } 128 }; 129 130 if (mWipeExternalStorage) { 131 // thr will be started at the end of this task. 132 Slog.i(TAG, "Wiping external storage on async task"); 133 new WipeDataTask(context, thr).execute(); 134 } else { 135 Slog.i(TAG, "NOT wiping external storage; starting thread " + thr.getName()); 136 thr.start(); 137 } 138 } 139 wipeUser(Context context, @UserIdInt int userId, String wipeReason)140 private boolean wipeUser(Context context, @UserIdInt int userId, String wipeReason) { 141 final UserManager userManager = context.getSystemService(UserManager.class); 142 final int result = userManager.removeUserWhenPossible( 143 UserHandle.of(userId), /* overrideDevicePolicy= */ false); 144 if (!UserManager.isRemoveResultSuccessful(result)) { 145 Slogf.e(TAG, "Can't remove user %d", userId); 146 return false; 147 } 148 if (getCurrentForegroundUserId() == userId) { 149 try { 150 if (!ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM)) { 151 Slogf.w(TAG, "Can't switch from current user %d, user will get removed when " 152 + "it is stopped.", userId); 153 154 } 155 } catch (RemoteException e) { 156 Slogf.w(TAG, "Can't switch from current user %d, user will get removed when " 157 + "it is stopped.", userId); 158 } 159 } 160 if (userManager.isManagedProfile(userId)) { 161 sendWipeProfileNotification(context, wipeReason); 162 } 163 return true; 164 } 165 166 // This method is copied from DevicePolicyManagedService. sendWipeProfileNotification(Context context, String wipeReason)167 private void sendWipeProfileNotification(Context context, String wipeReason) { 168 final Notification notification = 169 new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN) 170 .setSmallIcon(android.R.drawable.stat_sys_warning) 171 .setContentTitle(getWorkProfileDeletedTitle(context)) 172 .setContentText(wipeReason) 173 .setColor(context.getColor(R.color.system_notification_accent_color)) 174 .setStyle(new Notification.BigTextStyle().bigText(wipeReason)) 175 .build(); 176 context.getSystemService(NotificationManager.class).notify( 177 SystemMessageProto.SystemMessage.NOTE_PROFILE_WIPED, notification); 178 } 179 getWorkProfileDeletedTitle(Context context)180 private String getWorkProfileDeletedTitle(Context context) { 181 final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); 182 return dpm.getResources().getString(WORK_PROFILE_DELETED_TITLE, 183 () -> context.getString(R.string.work_profile_deleted)); 184 } 185 getCurrentForegroundUserId()186 private @UserIdInt int getCurrentForegroundUserId() { 187 try { 188 return ActivityManager.getCurrentUser(); 189 } catch (Exception e) { 190 Slogf.e(TAG, "Can't get current user", e); 191 } 192 return UserHandle.USER_NULL; 193 } 194 195 private class WipeDataTask extends AsyncTask<Void, Void, Void> { 196 private final Thread mChainedTask; 197 private final Context mContext; 198 private final ProgressDialog mProgressDialog; 199 WipeDataTask(Context context, Thread chainedTask)200 public WipeDataTask(Context context, Thread chainedTask) { 201 mContext = context; 202 mChainedTask = chainedTask; 203 mProgressDialog = mShowWipeProgress 204 ? new ProgressDialog(context, R.style.Theme_DeviceDefault_System) 205 : null; 206 } 207 208 @Override onPreExecute()209 protected void onPreExecute() { 210 if (mProgressDialog != null) { 211 mProgressDialog.setIndeterminate(true); 212 mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 213 mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing)); 214 mProgressDialog.show(); 215 } 216 } 217 218 @Override doInBackground(Void... params)219 protected Void doInBackground(Void... params) { 220 Slog.w(TAG, "Wiping adoptable disks"); 221 if (mWipeExternalStorage) { 222 StorageManager sm = (StorageManager) mContext.getSystemService( 223 Context.STORAGE_SERVICE); 224 sm.wipeAdoptableDisks(); 225 } 226 return null; 227 } 228 229 @Override onPostExecute(Void result)230 protected void onPostExecute(Void result) { 231 if (mProgressDialog != null && mProgressDialog.isShowing()) { 232 mProgressDialog.dismiss(); 233 } 234 mChainedTask.start(); 235 } 236 237 } 238 } 239