Merge "Added unit tests for MasterClearReceiver."

This commit is contained in:
TreeHugger Robot
2020-12-08 08:51:42 +00:00
committed by Android (Google) Code Review
3 changed files with 220 additions and 2 deletions

View File

@@ -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();
}
}

View File

@@ -27,6 +27,9 @@
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
<!-- needed by MasterClearReceiverTest to display a system dialog -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />

View File

@@ -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();
}
}