Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d93b5e0e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Added unit tests for MasterClearReceiver."

parents c49e071a d9902e3c
Loading
Loading
Loading
Loading
+7 −2
Original line number Original line Diff line number Diff line
@@ -25,7 +25,6 @@ import android.os.RecoverySystem;
import android.os.UserHandle;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.Slog;
import android.view.WindowManager;
import android.view.WindowManager;


@@ -59,6 +58,7 @@ public class MasterClearReceiver extends BroadcastReceiver {
                .getString(com.android.internal.R.string.config_factoryResetPackage);
                .getString(com.android.internal.R.string.config_factoryResetPackage);
        if (Intent.ACTION_FACTORY_RESET.equals(intent.getAction())
        if (Intent.ACTION_FACTORY_RESET.equals(intent.getAction())
                && !TextUtils.isEmpty(factoryResetPackage)) {
                && !TextUtils.isEmpty(factoryResetPackage)) {
            Slog.i(TAG, "Re-directing intent to " + factoryResetPackage);
            intent.setPackage(factoryResetPackage).setComponent(null);
            intent.setPackage(factoryResetPackage).setComponent(null);
            context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
            context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
            return;
            return;
@@ -77,9 +77,12 @@ public class MasterClearReceiver extends BroadcastReceiver {
            @Override
            @Override
            public void run() {
            public void run() {
                try {
                try {
                    Slog.i(TAG, "Calling RecoverySystem.rebootWipeUserData(context, "
                            + "shutdown=" + shutdown + ", reason=" + reason
                            + ", forceWipe=" + forceWipe + ", wipeEsims=" + mWipeEsims + ")");
                    RecoverySystem
                    RecoverySystem
                            .rebootWipeUserData(context, shutdown, reason, forceWipe, mWipeEsims);
                            .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) {
                } catch (IOException e) {
                    Slog.e(TAG, "Can't perform master clear/factory reset", e);
                    Slog.e(TAG, "Can't perform master clear/factory reset", e);
                } catch (SecurityException e) {
                } catch (SecurityException e) {
@@ -90,8 +93,10 @@ public class MasterClearReceiver extends BroadcastReceiver {


        if (mWipeExternalStorage) {
        if (mWipeExternalStorage) {
            // thr will be started at the end of this task.
            // thr will be started at the end of this task.
            Slog.i(TAG, "Wiping external storage on async task");
            new WipeDataTask(context, thr).execute();
            new WipeDataTask(context, thr).execute();
        } else {
        } else {
            Slog.i(TAG, "NOT wiping external storage; starting thread " + thr.getName());
            thr.start();
            thr.start();
        }
        }
    }
    }
+3 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,9 @@
    <uses-permission android:name="android.permission.MANAGE_APPOPS"/>
    <uses-permission android:name="android.permission.MANAGE_APPOPS"/>
    <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
    <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"
    <application android:testOnly="true"
                 android:debuggable="true">
                 android:debuggable="true">
        <uses-library android:name="android.test.runner" />
        <uses-library android:name="android.test.runner" />
+210 −0
Original line number Original line Diff line number Diff line
/*
 * 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();
    }
}