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