chromium/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPrivacySandboxDialogTest.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.customtabs;

import static androidx.browser.customtabs.CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;

import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.lifecycle.Stage;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.test.AutomotiveContextWrapperTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.test.util.DeviceRestriction;

@RunWith(ChromeJUnit4ClassRunner.class)
@DoNotBatch(
        reason =
                "Some tests are Testing CCT start up behavior. "
                        + "Unit test conversion tracked in crbug.com/1217031")
public class CustomTabPrivacySandboxDialogTest {
    private static final String TEST_PAGE = "/chrome/test/data/android/google.html";

    @Rule
    public AutomotiveContextWrapperTestRule mAutomotiveRule =
            new AutomotiveContextWrapperTestRule();

    @Rule
    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();

    @Rule
    public ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule =
            new ChromeTabbedActivityTestRule();

    private String mTestPage;
    private EmbeddedTestServer mTestServer;
    private CustomTabsConnection mConnectionToCleanup;

    private CustomTabActivity getActivity() {
        return mCustomTabActivityTestRule.getActivity();
    }

    /**
     * @see CustomTabsIntentTestUtils#createMinimalCustomTabIntent(Context, String).
     */
    private Intent createMinimalCustomTabIntent() {
        return CustomTabsIntentTestUtils.createMinimalCustomTabIntent(
                ApplicationProvider.getApplicationContext(), mTestPage);
    }

    @Before
    public void setUp() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
        mAutomotiveRule.setIsAutomotive(false);
        Context appContext = getInstrumentation().getTargetContext().getApplicationContext();
        mTestServer = EmbeddedTestServer.createAndStartServer(appContext);
        mTestPage = mTestServer.getURL(TEST_PAGE);
        LibraryLoader.getInstance().ensureInitialized();
    }

    @After
    public void tearDown() {
        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
        SharedPreferencesManager pref = ChromeSharedPreferences.getInstance();
        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID);
        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL);
        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE);

        ChromeSharedPreferences.getInstance()
                .removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION);
        ChromeSharedPreferences.getInstance()
                .removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP);

        // finish() is called on a non-UI thread by the testing harness. Must hide the menu
        // first, otherwise the UI is manipulated on a non-UI thread.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    if (getActivity() == null) return;
                    AppMenuCoordinator coordinator =
                            mCustomTabActivityTestRule.getAppMenuCoordinator();
                    // CCT doesn't always have a menu (ex. in the media viewer).
                    if (coordinator == null) return;
                    AppMenuHandler handler = coordinator.getAppMenuHandler();
                    if (handler != null) handler.hideAppMenu();
                });

        if (mConnectionToCleanup != null) {
            CustomTabsTestUtils.cleanupSessions(mConnectionToCleanup);
        }
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    @DisableFeatures({ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING})
    public void adsNoticeCCT() {
        HistogramWatcher watcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
        onViewWaiting(withId(R.id.privacy_sandbox_dialog)).check(matches(isDisplayed()));
        watcher.assertExpected();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    @DisableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
        ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING
    })
    public void adsNoticeCCT_WithoutAdsNoticeFeature() {
        HistogramWatcher watcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
        watcher.assertExpected();
    }

    @Test
    @SmallTest
    @DisableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
        ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING
    })
    public void adsNoticeCCT_WithoutAdsNoticeAndForceShowNoticeFeatures() {
        HistogramWatcher watcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", false);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
        watcher.assertExpected();
    }

    private void doTestLaunchPartialCustomTabWithInitialHeight() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
        CustomTabsConnection connection = CustomTabsConnection.getInstance();
        connection.newSession(token);
        connection.overridePackageNameForSessionForTesting(token, "org.chromium.testapp");
        intent.putExtra(EXTRA_INITIAL_ACTIVITY_HEIGHT_PX, 50);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
    }

    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO)
    @EnableFeatures({
        ChromeFeatureList.CCT_RESIZABLE_FOR_THIRD_PARTIES,
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    @DisableFeatures({ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING})
    public void adsNoticeCCT_PartialShouldNotShowNotice() throws Exception {
        doTestLaunchPartialCustomTabWithInitialHeight();
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
    }

    private void startActivityForResultCCT() {
        CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
        Intent intent = customTabsIntent.intent;
        intent.setData(Uri.parse("https://example.com"));
        String packageName = ContextUtils.getApplicationContext().getPackageName();
        intent.setPackage(packageName);
        mChromeTabbedActivityTestRule.startMainActivityOnBlankPage();

        ApplicationTestUtils.waitForActivityWithClass(
                CustomTabActivity.class,
                Stage.CREATED,
                () -> {
                    mChromeTabbedActivityTestRule.getActivity().startActivityForResult(intent, 0);
                });
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT + ":app-id/org.chromium.chrome.tests",
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    @DisableFeatures({ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING})
    public void adsNoticeCCT_appIdCheckDoesShowDialog() {
        HistogramWatcher shouldShowWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        HistogramWatcher appIDCheckWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.AdsNoticeCCTAppIDCheck", true);
        startActivityForResultCCT();
        // Set checkRootDialog=true to prevent flakiness after api 30 with espresso 30+.
        onViewWaiting(withId(R.id.privacy_sandbox_dialog), true).check(matches(isDisplayed()));
        shouldShowWatcher.pollInstrumentationThreadUntilSatisfied();
        appIDCheckWatcher.pollInstrumentationThreadUntilSatisfied();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
                + ":app-id/org.chromium.chrome.tests/include-mode-b/false",
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    @DisableFeatures({ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING})
    public void adsNoticeCCT_includeModeBParamFalseDoesShowDialogWhenNotInModeB() {
        HistogramWatcher shouldShowWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        startActivityForResultCCT();
        // Set checkRootDialog=true to prevent flakiness after api 30 with espresso 30+.
        onViewWaiting(withId(R.id.privacy_sandbox_dialog), true).check(matches(isDisplayed()));
        shouldShowWatcher.pollInstrumentationThreadUntilSatisfied();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
                + ":app-id/org.chromium.chrome.tests/include-mode-b/true",
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true",
        ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING
    })
    public void adsNoticeCCT_includeModeBParamTrueDoesShowDialogWhenInModeB() {
        HistogramWatcher shouldShowWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        startActivityForResultCCT();
        // Set checkRootDialog=true to prevent flakiness after api 30 with espresso 30+.
        onViewWaiting(withId(R.id.privacy_sandbox_dialog), true).check(matches(isDisplayed()));
        shouldShowWatcher.pollInstrumentationThreadUntilSatisfied();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
                + ":app-id/org.chromium.chrome.tests/include-mode-b/true",
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true",
        ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING
    })
    @DisableFeatures({ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING})
    public void adsNoticeCCT_includeModeBParamTrueDoesShowDialogWhenNotInModeB() {
        HistogramWatcher shouldShowWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        startActivityForResultCCT();
        // Set checkRootDialog=true to prevent flakiness after api 30 with espresso 30+.
        onViewWaiting(withId(R.id.privacy_sandbox_dialog), true).check(matches(isDisplayed()));
        shouldShowWatcher.pollInstrumentationThreadUntilSatisfied();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
                + ":app-id/org.chromium.chrome.tests/include-mode-b/false",
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true",
        ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING
    })
    public void adsNoticeCCT_includeModeBParamFalseDoesNotShowDialogWhenInModeB() {
        HistogramWatcher shouldShowWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        startActivityForResultCCT();
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
        shouldShowWatcher.pollInstrumentationThreadUntilSatisfied();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT + ":app-id/different.app.id",
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    @DisableFeatures({ChromeFeatureList.COOKIE_DEPRECATION_FACILITATED_TESTING})
    public void adsNoticeCCT_appIdCheckDoesNotShowDialog() {
        HistogramWatcher shouldShowWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        HistogramWatcher appIDCheckWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.AdsNoticeCCTAppIDCheck", false);
        startActivityForResultCCT();
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
        shouldShowWatcher.pollInstrumentationThreadUntilSatisfied();
        appIDCheckWatcher.pollInstrumentationThreadUntilSatisfied();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT
                + ":app-id/org.chromium.chrome.tests/include-mode-b/true",
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    public void adsNoticeCCT_AppIDNullShowDialog() {
        HistogramWatcher shouldShowWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.ShouldShowAdsNoticeCCT", true);
        HistogramWatcher appIDCheckWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Startup.Android.PrivacySandbox.AdsNoticeCCTAppIDCheck", false);
        // Starting a CCT with mCustomTabActivityTestRule, causes the package name set to null
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
        // If the package name is null, we do not show the dialog
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
        shouldShowWatcher.pollInstrumentationThreadUntilSatisfied();
        appIDCheckWatcher.pollInstrumentationThreadUntilSatisfied();
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    public void adsNoticeCCT_PWAShouldNotShowDialog() throws Exception {
        CustomTabActivityTypeTestUtils.launchActivity(
                ActivityType.WEBAPP,
                CustomTabActivityTypeTestUtils.createActivityTestRule(ActivityType.WEBAPP),
                "about:blank");
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
    }

    @Test
    @SmallTest
    @EnableFeatures({
        ChromeFeatureList.PRIVACY_SANDBOX_ADS_NOTICE_CCT,
        ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4
                + ":force-show-notice-row-for-testing/true/notice-required/true"
    })
    public void adsNoticeCCT_TWAShouldNotShowDialog() throws Exception {
        CustomTabActivityTypeTestUtils.launchActivity(
                ActivityType.TRUSTED_WEB_ACTIVITY,
                CustomTabActivityTypeTestUtils.createActivityTestRule(
                        ActivityType.TRUSTED_WEB_ACTIVITY),
                "about:blank");
        onView(withId(R.id.privacy_sandbox_dialog)).check(doesNotExist());
    }
}