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