diff options
| -rw-r--r-- | services/core/java/com/android/server/MasterClearReceiver.java | 72 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java | 80 |
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)); + } } |