Loading services/core/java/com/android/server/MasterClearReceiver.java +72 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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") { Loading Loading @@ -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; Loading services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java +77 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } }; Loading @@ -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 Loading Loading @@ -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() Loading Loading @@ -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); Loading @@ -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(); } Loading @@ -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)); } } Loading
services/core/java/com/android/server/MasterClearReceiver.java +72 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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") { Loading Loading @@ -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; Loading
services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java +77 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } }; Loading @@ -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 Loading Loading @@ -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() Loading Loading @@ -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); Loading @@ -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(); } Loading @@ -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)); } }