summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/MasterClearReceiver.java72
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java80
2 files changed, 149 insertions, 3 deletions
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index e248b215ef4a..b2f539680252 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -16,19 +16,29 @@
package com.android.server;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
+import android.os.Binder;
import android.os.RecoverySystem;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.Slog;
import android.view.WindowManager;
import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.server.utils.Slogf;
import java.io.IOException;
@@ -71,6 +81,19 @@ public class MasterClearReceiver extends BroadcastReceiver {
final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
|| intent.getBooleanExtra(Intent.EXTRA_FORCE_FACTORY_RESET, false);
+ // TODO(b/189938391): properly handle factory reset on headless system user mode.
+ final int sendingUserId = getSendingUserId();
+ if (sendingUserId != UserHandle.USER_SYSTEM && !UserManager.isHeadlessSystemUserMode()) {
+ Slogf.w(
+ TAG,
+ "ACTION_FACTORY_RESET received on a non-system user %d, WIPING THE USER!!",
+ sendingUserId);
+ if (!Binder.withCleanCallingIdentity(() -> wipeUser(context, sendingUserId, reason))) {
+ Slogf.e(TAG, "Failed to wipe user %d", sendingUserId);
+ }
+ return;
+ }
+
Slog.w(TAG, "!!! FACTORY RESET !!!");
// The reboot call is blocking, so we need to do it on another thread.
Thread thr = new Thread("Reboot") {
@@ -101,6 +124,55 @@ public class MasterClearReceiver extends BroadcastReceiver {
}
}
+ private boolean wipeUser(Context context, @UserIdInt int userId, String wipeReason) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ final int result = userManager.removeUserOrSetEphemeral(
+ userId, /* evenWhenDisallowed= */ false);
+ if (result == UserManager.REMOVE_RESULT_ERROR) {
+ Slogf.e(TAG, "Can't remove user %d", userId);
+ return false;
+ }
+ if (getCurrentForegroundUserId() == userId) {
+ try {
+ if (!ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM)) {
+ Slogf.w(TAG, "Can't switch from current user %d, user will get removed when "
+ + "it is stopped.", userId);
+
+ }
+ } catch (RemoteException e) {
+ Slogf.w(TAG, "Can't switch from current user %d, user will get removed when "
+ + "it is stopped.", userId);
+ }
+ }
+ if (userManager.isManagedProfile(userId)) {
+ sendWipeProfileNotification(context, wipeReason);
+ }
+ return true;
+ }
+
+ // This method is copied from DevicePolicyManagedService.
+ private void sendWipeProfileNotification(Context context, String wipeReason) {
+ final Notification notification =
+ new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN)
+ .setSmallIcon(android.R.drawable.stat_sys_warning)
+ .setContentTitle(context.getString(R.string.work_profile_deleted))
+ .setContentText(wipeReason)
+ .setColor(context.getColor(R.color.system_notification_accent_color))
+ .setStyle(new Notification.BigTextStyle().bigText(wipeReason))
+ .build();
+ context.getSystemService(NotificationManager.class).notify(
+ SystemMessageProto.SystemMessage.NOTE_PROFILE_WIPED, notification);
+ }
+
+ private @UserIdInt int getCurrentForegroundUserId() {
+ try {
+ return ActivityManager.getCurrentUser();
+ } catch (Exception e) {
+ Slogf.e(TAG, "Can't get current user", e);
+ }
+ return UserHandle.USER_NULL;
+ }
+
private class WipeDataTask extends AsyncTask<Void, Void, Void> {
private final Thread mChainedTask;
private final Context mContext;
diff --git a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
index f01120e1ed4c..5a6275d71d1e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
@@ -24,19 +24,25 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.os.Looper;
import android.os.RecoverySystem;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.StorageManager;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
@@ -68,7 +74,13 @@ public final class MasterClearReceiverTest {
@Override
public Object getSystemService(String name) {
Log.v(TAG, "getSystemService(): " + name);
- return name.equals(Context.STORAGE_SERVICE) ? mSm : super.getSystemService(name);
+ if (name.equals(Context.STORAGE_SERVICE)) {
+ return mSm;
+ }
+ if (name.equals(Context.USER_SERVICE)) {
+ return mUserManager;
+ }
+ return super.getSystemService(name);
}
};
@@ -85,15 +97,17 @@ public final class MasterClearReceiverTest {
private StorageManager mSm;
@Mock
- private WindowManager mWm;
+ private UserManager mUserManager;
@Before
public void startSession() {
mSession = mockitoSession()
.initMocks(this)
.mockStatic(RecoverySystem.class)
+ .mockStatic(UserManager.class)
.strictness(Strictness.LENIENT)
.startMocking();
+ setPendingResultForUser(UserHandle.myUserId());
}
@After
@@ -148,6 +162,32 @@ public final class MasterClearReceiverTest {
verifyWipeExternalData();
}
+ @Test
+ public void testNonSystemUser() throws Exception {
+ expectWipeNonSystemUser();
+
+ Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
+ setPendingResultForUser(/* userId= */ 10);
+ mReceiver.onReceive(mContext, intent);
+
+ verifyNoRebootWipeUserData();
+ verifyNoWipeExternalData();
+ verifyWipeNonSystemUser();
+ }
+
+ @Test
+ public void testHeadlessSystemUser() throws Exception {
+ expectNoWipeExternalData();
+ expectRebootWipeUserData();
+ expectHeadlessSystemUserMode();
+
+ Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
+ setPendingResultForUser(/* userId= */ 10);
+ mReceiver.onReceive(mContext, intent);
+
+ verifyRebootWipeUserData();
+ verifyNoWipeExternalData();
+ }
private void expectNoWipeExternalData() {
// This is a trick to simplify how the order of methods are called: as wipeAdoptableDisks()
@@ -185,6 +225,18 @@ public final class MasterClearReceiverTest {
}).when(mSm).wipeAdoptableDisks();
}
+ private void expectWipeNonSystemUser() {
+ when(mUserManager.removeUserOrSetEphemeral(anyInt(), anyBoolean()))
+ .thenReturn(UserManager.REMOVE_RESULT_REMOVED);
+ }
+
+ private void expectHeadlessSystemUserMode() {
+ doAnswer((inv) -> {
+ Log.i(TAG, inv.toString());
+ return true;
+ }).when(() -> UserManager.isHeadlessSystemUserMode());
+ }
+
private void verifyRebootWipeUserData() throws Exception {
verifyRebootWipeUserData(/* shutdown= */ false, /* reason= */ null, /* force= */ false,
/* wipeEuicc= */ false);
@@ -200,6 +252,11 @@ public final class MasterClearReceiverTest {
eq(force), eq(wipeEuicc)));
}
+ private void verifyNoRebootWipeUserData() {
+ verify(()-> RecoverySystem.rebootWipeUserData(
+ any(), anyBoolean(), anyString(), anyBoolean(), anyBoolean()), never());
+ }
+
private void verifyWipeExternalData() {
verify(mSm).wipeAdoptableDisks();
}
@@ -207,4 +264,21 @@ public final class MasterClearReceiverTest {
private void verifyNoWipeExternalData() {
verify(mSm, never()).wipeAdoptableDisks();
}
+
+ private void verifyWipeNonSystemUser() {
+ verify(mUserManager).removeUserOrSetEphemeral(anyInt(), anyBoolean());
+ }
+
+ private void setPendingResultForUser(int userId) {
+ mReceiver.setPendingResult(new BroadcastReceiver.PendingResult(
+ Activity.RESULT_OK,
+ "resultData",
+ /* resultExtras= */ null,
+ BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
+ /* ordered= */ true,
+ /* sticky= */ false,
+ /* token= */ null,
+ userId,
+ /* flags= */ 0));
+ }
}