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