diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 793e342926239..e248b215ef4a7 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -25,7 +25,6 @@ import android.os.RecoverySystem;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.text.TextUtils;
-import android.util.Log;
import android.util.Slog;
import android.view.WindowManager;
@@ -59,6 +58,7 @@ public class MasterClearReceiver extends BroadcastReceiver {
.getString(com.android.internal.R.string.config_factoryResetPackage);
if (Intent.ACTION_FACTORY_RESET.equals(intent.getAction())
&& !TextUtils.isEmpty(factoryResetPackage)) {
+ Slog.i(TAG, "Re-directing intent to " + factoryResetPackage);
intent.setPackage(factoryResetPackage).setComponent(null);
context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
return;
@@ -77,9 +77,12 @@ public class MasterClearReceiver extends BroadcastReceiver {
@Override
public void run() {
try {
+ Slog.i(TAG, "Calling RecoverySystem.rebootWipeUserData(context, "
+ + "shutdown=" + shutdown + ", reason=" + reason
+ + ", forceWipe=" + forceWipe + ", wipeEsims=" + mWipeEsims + ")");
RecoverySystem
.rebootWipeUserData(context, shutdown, reason, forceWipe, mWipeEsims);
- Log.wtf(TAG, "Still running after master clear?!");
+ Slog.wtf(TAG, "Still running after master clear?!");
} catch (IOException e) {
Slog.e(TAG, "Can't perform master clear/factory reset", e);
} catch (SecurityException e) {
@@ -90,8 +93,10 @@ public class MasterClearReceiver extends BroadcastReceiver {
if (mWipeExternalStorage) {
// thr will be started at the end of this task.
+ Slog.i(TAG, "Wiping external storage on async task");
new WipeDataTask(context, thr).execute();
} else {
+ Slog.i(TAG, "NOT wiping external storage; starting thread " + thr.getName());
thr.start();
}
}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 182fe9ae94767..fbde1d2cf1cf8 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -27,6 +27,9 @@
+
+
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
new file mode 100644
index 0000000000000..f01120e1ed4c3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/MasterClearReceiverTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+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.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.never;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.Looper;
+import android.os.RecoverySystem;
+import android.os.storage.StorageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+import android.view.WindowManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Run it as {@code FrameworksMockingServicesTests:MasterClearReceiverTest}.
+ */
+@Presubmit
+public final class MasterClearReceiverTest {
+
+ private static final String TAG = MasterClearReceiverTest.class.getSimpleName();
+
+ private MockitoSession mSession;
+
+ // Cannot @Mock context because MasterClearReceiver shows an AlertDialog, which relies
+ // on resources - we'd need to mock them as well.
+ private final Context mContext = new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()) {
+
+ @Override
+ public Object getSystemService(String name) {
+ Log.v(TAG, "getSystemService(): " + name);
+ return name.equals(Context.STORAGE_SERVICE) ? mSm : super.getSystemService(name);
+ }
+ };
+
+ private final MasterClearReceiver mReceiver = new MasterClearReceiver();
+
+ // Used to make sure that wipeAdoptableDisks() is called before rebootWipeUserData()
+ private boolean mWipeExternalDataCalled;
+
+ // Uset to block test until rebootWipeUserData() is called, as it might be asynchronous called
+ // in a different thread
+ private final CountDownLatch mRebootWipeUserDataLatch = new CountDownLatch(1);
+
+ @Mock
+ private StorageManager mSm;
+
+ @Mock
+ private WindowManager mWm;
+
+ @Before
+ public void startSession() {
+ mSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(RecoverySystem.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ }
+
+ @After
+ public void finishSession() {
+ if (mSession == null) {
+ Log.w(TAG, "finishSession(): no session");
+ return;
+ }
+ mSession.finishMocking();
+ }
+
+ @Test
+ public void testNoExtras() throws Exception {
+ expectNoWipeExternalData();
+ expectRebootWipeUserData();
+
+ Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
+ mReceiver.onReceive(mContext, intent);
+
+ verifyRebootWipeUserData();
+ verifyNoWipeExternalData();
+ }
+
+ @Test
+ public void testWipeExternalDirectory() throws Exception {
+ expectWipeExternalData();
+ expectRebootWipeUserData();
+
+ Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
+ intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
+ mReceiver.onReceive(mContext, intent);
+
+ verifyRebootWipeUserData();
+ verifyWipeExternalData();
+ }
+
+ @Test
+ public void testAllExtras() throws Exception {
+ expectWipeExternalData();
+ expectRebootWipeUserData();
+
+ Intent intent = new Intent(Intent.ACTION_FACTORY_RESET);
+ intent.putExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, true);
+ intent.putExtra("shutdown", true);
+ intent.putExtra(Intent.EXTRA_REASON, "Self destruct");
+ intent.putExtra(Intent.EXTRA_FORCE_FACTORY_RESET, true);
+ intent.putExtra(Intent.EXTRA_WIPE_ESIMS, true);
+ mReceiver.onReceive(mContext, intent);
+
+ verifyRebootWipeUserData(/* shutdown= */ true, /* reason= */ "Self destruct",
+ /* force= */ true, /* wipeEuicc= */ true);
+ verifyWipeExternalData();
+ }
+
+
+ private void expectNoWipeExternalData() {
+ // This is a trick to simplify how the order of methods are called: as wipeAdoptableDisks()
+ // should be called before rebootWipeUserData(), expectRebootWipeUserData() throws an
+ // exception if it's not called, so this method "emulates" a call when it's not neeeded.
+ //
+ // A more robust solution would be using internal counters for expected and actual mocked
+ // calls, so the expectXXX() methods would increment expected counter and the Answer
+ // implementations would increment the actual counter and check if they match, but that
+ // would be an overkill (and make the test logic more complicated).
+ mWipeExternalDataCalled = true;
+ }
+
+ private void expectRebootWipeUserData() {
+ doAnswer((inv) -> {
+ Log.i(TAG, inv.toString());
+ if (!mWipeExternalDataCalled) {
+ String error = "rebootWipeUserData() called before wipeAdoptableDisks()";
+ Log.e(TAG, error);
+ throw new IllegalStateException(error);
+ }
+ mRebootWipeUserDataLatch.countDown();
+ return null;
+ }).when(() -> RecoverySystem
+ .rebootWipeUserData(any(), anyBoolean(), any(), anyBoolean(), anyBoolean()));
+ }
+
+ private void expectWipeExternalData() {
+ Looper.prepare(); // needed by Dialog
+
+ doAnswer((inv) -> {
+ Log.i(TAG, inv.toString());
+ mWipeExternalDataCalled = true;
+ return null;
+ }).when(mSm).wipeAdoptableDisks();
+ }
+
+ private void verifyRebootWipeUserData() throws Exception {
+ verifyRebootWipeUserData(/* shutdown= */ false, /* reason= */ null, /* force= */ false,
+ /* wipeEuicc= */ false);
+
+ }
+
+ private void verifyRebootWipeUserData(boolean shutdown, String reason, boolean force,
+ boolean wipeEuicc) throws Exception {
+ boolean called = mRebootWipeUserDataLatch.await(5, TimeUnit.SECONDS);
+ assertWithMessage("rebootWipeUserData not called in 5s").that(called).isTrue();
+
+ verify(()-> RecoverySystem.rebootWipeUserData(same(mContext), eq(shutdown), eq(reason),
+ eq(force), eq(wipeEuicc)));
+ }
+
+ private void verifyWipeExternalData() {
+ verify(mSm).wipeAdoptableDisks();
+ }
+
+ private void verifyNoWipeExternalData() {
+ verify(mSm, never()).wipeAdoptableDisks();
+ }
+}