chromium/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTest.java

// Copyright 2019 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.webapps;

import static org.junit.Assert.assertEquals;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.runner.lifecycle.Stage;

import org.junit.Assert;
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.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.cc.input.BrowserControlsState;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tab.TabTestUtils;
import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.chrome.test.util.browser.webapps.WebApkIntentDataProviderBuilder;
import org.chromium.components.permissions.PermissionDialogController;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.webapk.lib.common.WebApkConstants;

import java.util.concurrent.TimeoutException;

/** Tests for WebAPK {@link WebappActivity}. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public final class WebApkActivityTest {
    private static final String TEST_WEBAPK_PACKAGE_NAME = "org.chromium.webapk.for.testing";
    private static final String TEST_WEBAPK_ID =
            WebApkConstants.WEBAPK_ID_PREFIX + TEST_WEBAPK_PACKAGE_NAME;

    @Rule public final WebApkActivityTestRule mActivityTestRule = new WebApkActivityTestRule();

    @Before
    public void setUp() {
        mActivityTestRule.getEmbeddedTestServerRule().setServerUsesHttps(true);

        // WebAPK is not installed. Ensure that WebappRegistry#unregisterOldWebapps() does not
        // delete the WebAPK's shared preferences.
        SharedPreferences sharedPrefs =
                ContextUtils.getApplicationContext()
                        .getSharedPreferences(
                                WebappRegistry.REGISTRY_FILE_NAME, Context.MODE_PRIVATE);
        sharedPrefs
                .edit()
                .putLong(WebappRegistry.KEY_LAST_CLEANUP, System.currentTimeMillis())
                .apply();
    }

    /**
     * Test that navigating a WebAPK to a URL which is outside of the WebAPK's scope shows the
     * toolbar.
     */
    @Test
    @LargeTest
    @Feature({"WebApk"})
    public void testLaunchAndNavigateOutsideScope() throws Exception {
        WebappActivity webApkActivity =
                mActivityTestRule.startWebApkActivity(
                        createIntentDataProvider(
                                getTestServerUrl("scope_a/page_1.html"),
                                getTestServerUrl("scope_a/")));
        assertEquals(
                BrowserControlsState.HIDDEN,
                WebappActivityTestRule.getToolbarShowState(webApkActivity));

        // We navigate outside scope and expect CCT toolbar to show on top of WebAPK Activity.
        String outOfScopeUrl = getTestServerUrl("manifest_test_page.html");
        mActivityTestRule.runJavaScriptCodeInCurrentTab(
                "window.top.location = '" + outOfScopeUrl + "'");

        ChromeTabUtils.waitForTabPageLoaded(webApkActivity.getActivityTab(), outOfScopeUrl);
        WebappActivityTestRule.assertToolbarShownMaybeHideable(webApkActivity);
    }

    /**
     * Test the L+ logic in {@link TabWebContentsDelegateAndroid#activateContents} for bringing
     * WebAPK to the foreground.
     */
    @LargeTest
    @Test
    public void testActivateWebApkLPlus() throws Exception {
        // Launch WebAPK.
        WebappActivity webApkActivity =
                mActivityTestRule.startWebApkActivity(
                        createIntentDataProvider(
                                getTestServerUrl("manifest_test_page.html"),
                                getTestServerUrl("/")));

        Class<? extends ChromeActivity> mainClass = ChromeTabbedActivity.class;

        // Move WebAPK to the background by launching Chrome.
        Intent intent = new Intent(ApplicationProvider.getApplicationContext(), mainClass);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
        ApplicationProvider.getApplicationContext().startActivity(intent);
        ChromeActivityTestRule.waitFor(mainClass);

        ApplicationTestUtils.waitForActivityState(webApkActivity, Stage.STOPPED);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    TabWebContentsDelegateAndroid tabDelegate =
                            TabTestUtils.getTabWebContentsDelegate(webApkActivity.getActivityTab());
                    tabDelegate.activateContents();
                });

        // WebAPK Activity should have been brought back to the foreground.
        ChromeActivityTestRule.waitFor(WebappActivity.class);
    }

    /** Test a permission dialog can be correctly presented. */
    @Test
    @LargeTest
    @DisabledTest(message = "The test is flaky, see b/324471058.")
    public void testShowPermissionPrompt() throws TimeoutException {
        EmbeddedTestServer server = mActivityTestRule.getEmbeddedTestServerRule().getServer();
        String url = server.getURL("/content/test/data/android/permission_navigation.html");
        String baseUrl = server.getURL("/content/test/data/android/");
        WebappActivity activity =
                mActivityTestRule.startWebApkActivity(createIntentDataProvider(url, baseUrl));
        mActivityTestRule.runJavaScriptCodeInCurrentTab("requestGeolocationPermission()");
        CriteriaHelper.pollUiThread(
                () -> PermissionDialogController.getInstance().isDialogShownForTest(),
                "Permission prompt did not appear in allotted time");
        Assert.assertEquals(
                "Only App modal dialog is supported on web apk",
                activity.getModalDialogManager()
                        .getPresenterForTest(ModalDialogManager.ModalDialogType.APP),
                activity.getModalDialogManager().getCurrentPresenterForTest());
    }

    private BrowserServicesIntentDataProvider createIntentDataProvider(
            String startUrl, String scopeUrl) {
        WebApkIntentDataProviderBuilder intentDataProviderBuilder =
                new WebApkIntentDataProviderBuilder(TEST_WEBAPK_PACKAGE_NAME, startUrl);
        intentDataProviderBuilder.setScope(scopeUrl);
        return intentDataProviderBuilder.build();
    }

    private String getTestServerUrl(String relativeUrl) {
        return mActivityTestRule
                .getEmbeddedTestServerRule()
                .getServer()
                .getURL("/chrome/test/data/banners/" + relativeUrl);
    }

    /** Register WebAPK with WebappDataStorage */
    private WebappDataStorage registerWithStorage(final String webappId) throws Exception {
        TestFetchStorageCallback callback = new TestFetchStorageCallback();
        WebappRegistry.getInstance().register(webappId, callback);
        callback.waitForCallback(0);
        return WebappRegistry.getInstance().getWebappDataStorage(webappId);
    }
}