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

Commit 8f746675 authored by Dieter Hsu's avatar Dieter Hsu Committed by Gavin Corkery
Browse files

Fix consent timed out to sharing bugreport data

Also mark the fixed tests as large test to be ignored when presubmit
running.

Bug: 159590499
Test: atest BugreportManagerTest
Change-Id: Ie926eefacb659cce4c54f42b0835308da5a71bba
Merged-In: Ie926eefacb659cce4c54f42b0835308da5a71bba
(cherry picked from commit 572ef12b)
parent 59a1d2b4
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -20,7 +20,11 @@ android_test {
        "android.test.runner",
        "android.test.base",
    ],
    static_libs: ["androidx.test.rules", "truth-prebuilt"],
    static_libs: [
        "androidx.test.rules",
        "androidx.test.uiautomator_uiautomator",
        "truth-prebuilt",
    ],
    test_suites: ["general-tests"],
    sdk_version: "test_current",
    platform_apis: true,
+114 −14
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.os.bugreports.tests;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.Manifest;
@@ -25,17 +27,27 @@ import android.content.Context;
import android.os.BugreportManager;
import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
import android.os.StrictMode;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -51,9 +63,11 @@ import java.util.concurrent.TimeUnit;
@RunWith(JUnit4.class)
public class BugreportManagerTest {
    @Rule public TestName name = new TestName();
    @Rule public ExtendedStrictModeVmPolicy mTemporaryVmPolicy = new ExtendedStrictModeVmPolicy();

    private static final String TAG = "BugreportManagerTest";
    private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
    private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);

    private Handler mHandler;
    private Executor mExecutor;
@@ -86,6 +100,8 @@ public class BugreportManagerTest {
    @After
    public void teardown() throws Exception {
        dropPermissions();
        FileUtils.closeQuietly(mBugreportFd);
        FileUtils.closeQuietly(mScreenshotFd);
    }


@@ -95,47 +111,45 @@ public class BugreportManagerTest {
        // wifi bugreport does not take screenshot
        mBrm.startBugreport(mBugreportFd, null /*screenshotFd = null*/, wifi(),
                mExecutor, callback);
        shareConsentDialog(ConsentReply.ALLOW);
        waitTillDoneOrTimeout(callback);

        assertThat(callback.isDone()).isTrue();
        // Wifi bugreports should not receive any progress.
        assertThat(callback.hasReceivedProgress()).isFalse();
        // TODO: Because of b/130234145, consent dialog is not shown; so we get a timeout error.
        // When the bug is fixed, accept consent via UIAutomator and verify contents
        // of mBugreportFd.
        assertThat(callback.getErrorCode()).isEqualTo(
                BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
        assertThat(mBugreportFile.length()).isGreaterThan(0L);
        assertFdsAreClosed(mBugreportFd);
    }

    @LargeTest
    @Test
    public void normalFlow_interactive() throws Exception {
        BugreportCallbackImpl callback = new BugreportCallbackImpl();
        // interactive bugreport does not take screenshot
        mBrm.startBugreport(mBugreportFd, null /*screenshotFd = null*/, interactive(),
                mExecutor, callback);

        shareConsentDialog(ConsentReply.ALLOW);
        waitTillDoneOrTimeout(callback);

        assertThat(callback.isDone()).isTrue();
        // Interactive bugreports show progress updates.
        assertThat(callback.hasReceivedProgress()).isTrue();
        assertThat(callback.getErrorCode()).isEqualTo(
                BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
        assertThat(mBugreportFile.length()).isGreaterThan(0L);
        assertFdsAreClosed(mBugreportFd);
    }

    @LargeTest
    @Test
    public void normalFlow_full() throws Exception {
        BugreportCallbackImpl callback = new BugreportCallbackImpl();
        mBrm.startBugreport(mBugreportFd, mScreenshotFd, full(), mExecutor, callback);

        shareConsentDialog(ConsentReply.ALLOW);
        waitTillDoneOrTimeout(callback);

        assertThat(callback.isDone()).isTrue();
        assertThat(callback.getErrorCode()).isEqualTo(
                BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
        // bugreport and screenshot files should be empty when user consent timed out.
        assertThat(mBugreportFile.length()).isEqualTo(0);
        assertThat(mScreenshotFile.length()).isEqualTo(0);
        // bugreport and screenshot files shouldn't be empty when user consents.
        assertThat(mBugreportFile.length()).isGreaterThan(0L);
        assertThat(mScreenshotFile.length()).isGreaterThan(0L);
        assertFdsAreClosed(mBugreportFd, mScreenshotFd);
    }

@@ -144,6 +158,8 @@ public class BugreportManagerTest {
        // Start bugreport #1
        BugreportCallbackImpl callback = new BugreportCallbackImpl();
        mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
        // TODO(b/162389762) Make sure the wait time is reasonable
        shareConsentDialog(ConsentReply.ALLOW);

        // Before #1 is done, try to start #2.
        assertThat(callback.isDone()).isFalse();
@@ -375,4 +391,88 @@ public class BugreportManagerTest {
    private static BugreportParams full() {
        return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
    }

    /* Allow/deny the consent dialog to sharing bugreport data or check existence only. */
    private enum ConsentReply {
        ALLOW,
        DENY,
        TIMEOUT
    }

    /*
     * Ensure the consent dialog is shown and take action according to <code>consentReply<code/>.
     * It will fail if the dialog is not shown when <code>ignoreNotFound<code/> is false.
     */
    private void shareConsentDialog(@NonNull ConsentReply consentReply) throws Exception {
        mTemporaryVmPolicy.permitIncorrectContextUse();
        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

        // Unlock before finding/clicking an object.
        device.wakeUp();
        device.executeShellCommand("wm dismiss-keyguard");

        final BySelector consentTitleObj = By.res("android", "alertTitle");
        if (!device.wait(Until.hasObject(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)) {
            fail("The consent dialog is not found");
        }
        if (consentReply.equals(ConsentReply.TIMEOUT)) {
            return;
        }
        final BySelector selector;
        if (consentReply.equals(ConsentReply.ALLOW)) {
            selector = By.res("android", "button1");
            Log.d(TAG, "Allow the consent dialog");
        } else { // ConsentReply.DENY
            selector = By.res("android", "button2");
            Log.d(TAG, "Deny the consent dialog");
        }
        final UiObject2 btnObj = device.findObject(selector);
        assertNotNull("The button of consent dialog is not found", btnObj);
        btnObj.click();

        Log.d(TAG, "Wait for the dialog to be dismissed");
        assertTrue(device.wait(Until.gone(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS));
    }

    /**
     * A rule to change strict mode vm policy temporarily till test method finished.
     *
     * To permit the non-visual context usage in tests while taking bugreports need user consent,
     * or UiAutomator/BugreportManager.DumpstateListener would run into error.
     * UiDevice#findObject creates UiObject2, its Gesture object and ViewConfiguration and
     * UiObject2#click need to know bounds. Both of them access to WindowManager internally without
     * visual context comes from InstrumentationRegistry and violate the policy.
     * Also <code>DumpstateListener<code/> violate the policy when onScreenshotTaken is called.
     *
     * TODO(b/161201609) Remove this class once violations fixed.
     */
    static class ExtendedStrictModeVmPolicy extends ExternalResource {
        private boolean mWasVmPolicyChanged = false;
        private StrictMode.VmPolicy mOldVmPolicy;

        @Override
        protected void after() {
            restoreVmPolicyIfNeeded();
        }

        public void permitIncorrectContextUse() {
            // Allow to call multiple times without losing old policy.
            if (mOldVmPolicy == null) {
                mOldVmPolicy = StrictMode.getVmPolicy();
            }
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectAll()
                    .permitIncorrectContextUse()
                    .penaltyLog()
                    .build());
            mWasVmPolicyChanged = true;
        }

        private void restoreVmPolicyIfNeeded() {
            if (mWasVmPolicyChanged && mOldVmPolicy != null) {
                StrictMode.setVmPolicy(mOldVmPolicy);
                mOldVmPolicy = null;
            }
        }
    }
}