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

// Copyright 2017 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 static org.junit.Assert.fail;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.IBinder;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;

import org.chromium.base.CommandLine;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.test.MockCertVerifierRuleAndroid;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.ui.test.util.UiDisableIf;
import org.chromium.webapk.lib.client.WebApkServiceConnectionManager;
import org.chromium.webapk.lib.runtime_library.IWebApkApi;

import java.util.concurrent.TimeoutException;

/** Integration tests for WebAPK feature. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@DoNotBatch(reason = "Tests activity start behavior")
public class WebApkIntegrationTest {
    public final WebApkActivityTestRule mActivityTestRule = new WebApkActivityTestRule();

    public MockCertVerifierRuleAndroid mCertVerifierRule =
            new MockCertVerifierRuleAndroid(0 /* net::OK */);

    @Rule
    public RuleChain mRuleChain =
            RuleChain.emptyRuleChain().around(mActivityTestRule).around(mCertVerifierRule);

    private static final long STARTUP_TIMEOUT = 15000L;

    @Before
    public void setUp() {
        mActivityTestRule.getEmbeddedTestServerRule().setServerUsesHttps(true);
        Uri mapToUri =
                Uri.parse(mActivityTestRule.getEmbeddedTestServerRule().getServer().getURL("/"));
        CommandLine.getInstance()
                .appendSwitchWithValue(
                        ContentSwitches.HOST_RESOLVER_RULES, "MAP * " + mapToUri.getAuthority());
        WebApkValidator.setDisableValidationForTesting(true);
    }

    /** Tests that sending deep link intent to WebAPK launches WebAPK Activity. */
    @Test
    @LargeTest
    @Feature({"Webapps"})
    public void testDeepLink() {
        String pageUrl = "https://pwa-directory.appspot.com/defaultresponse";

        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(pageUrl));
        intent.setPackage("org.chromium.webapk.test");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        ApplicationProvider.getApplicationContext().startActivity(intent);

        WebappActivity lastActivity =
                ChromeActivityTestRule.waitFor(WebappActivity.class, STARTUP_TIMEOUT);
        Assert.assertEquals(ActivityType.WEB_APK, lastActivity.getActivityType());
        Assert.assertEquals(pageUrl, lastActivity.getIntentDataProvider().getUrlToLoad());
    }

    /**
     * Tests that Chrome will trampoline out to WebAPKs if they exist but are not verified. See
     * https://crbug.com/1232514
     */
    @Test
    @LargeTest
    @Feature({"Webapps"})
    @MinAndroidSdkLevel(Build.VERSION_CODES.S)
    public void testWebApkTrampoline() {
        Context targetContext = ApplicationProvider.getApplicationContext();
        String pageUrl = "https://pwa-directory.appspot.com/defaultresponse";

        // Make a standard browsable Intent to a page within the WebAPK's scope.
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(pageUrl));
        intent.addCategory(Intent.CATEGORY_BROWSABLE);

        // FLAG_ACTIVITY_NEW_TASK required because we're launching from a non-Activity context.
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        // We need to set the component name to make sure the Intent ends up in the Chrome build
        // that we're testing. We can't set the package name, because our launch code has special
        // handling if the package name is set and is equal to Chrome
        // (see RedirectHandler#updateIntent).
        intent.setComponent(new ComponentName(targetContext, ChromeLauncherActivity.class));

        targetContext.startActivity(intent);

        // Check we end up in the WebAPK.
        ChromeActivityTestRule.waitFor(WebappActivity.class, STARTUP_TIMEOUT);
    }

    /** Tests launching WebAPK via POST share intent. */
    @Test
    @LargeTest
    @Feature({"Webapps"})
    @DisabledTest(message = "https://crbug.com/1112352")
    public void testShare() throws TimeoutException {
        final String sharedSubject = "Fun tea parties";
        final String sharedText = "Boston";
        final String expectedShareUrl = "https://pwa-directory.appspot.com/echoall";

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setPackage("org.chromium.webapk.test");
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_SUBJECT, sharedSubject);
        intent.putExtra(Intent.EXTRA_TEXT, sharedText);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        ApplicationProvider.getApplicationContext().startActivity(intent);

        WebappActivity lastActivity =
                ChromeActivityTestRule.waitFor(WebappActivity.class, STARTUP_TIMEOUT);
        Assert.assertEquals(ActivityType.WEB_APK, lastActivity.getActivityType());

        Tab tab = lastActivity.getActivityTab();
        ChromeTabUtils.waitForTabPageLoaded(tab, expectedShareUrl);
        String postDataJson =
                JavaScriptUtils.executeJavaScriptAndWaitForResult(
                        tab.getWebContents(), "document.getElementsByTagName('pre')[0].innerText");
        assertEquals("\"title=Fun+tea+parties\\ntext=Boston\\n\"", postDataJson);
    }

    /**
     * Integration test for the WebAPK service loading logic. The WebAPK service loads its
     * implementation from a dex stored in Chrome's APK.
     */
    @Test
    @LargeTest
    @Feature({"Webapps"})
    @DisableIf.Device(type = {UiDisableIf.TABLET}) // crbug.com/362218524
    public void testWebApkServiceIntegration() throws Exception {
        Context context = ApplicationProvider.getApplicationContext();

        // Launch WebAPK in order to cache host browser.
        Intent intent =
                new Intent(
                        Intent.ACTION_VIEW,
                        Uri.parse("https://pwa-directory.appspot.com/defaultresponse"));
        intent.setPackage("org.chromium.webapk.test");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
        ChromeActivityTestRule.waitFor(WebappActivity.class);

        // Extract small icon id from WebAPK resources.
        Resources res =
                context.getPackageManager().getResourcesForApplication("org.chromium.webapk.test");
        final int expectedSmallIconId =
                res.getIdentifier("notification_badge", "drawable", "org.chromium.webapk.test");

        CallbackHelper callbackHelper = new CallbackHelper();
        WebApkServiceConnectionManager connectionManager =
                new WebApkServiceConnectionManager(
                        TaskTraits.UI_DEFAULT,
                        WebApkServiceClient.CATEGORY_WEBAPK_API,
                        /* action= */ null);
        connectionManager.connect(
                ApplicationProvider.getApplicationContext(),
                "org.chromium.webapk.test",
                new WebApkServiceConnectionManager.ConnectionCallback() {
                    @Override
                    public void onConnected(IBinder api) {
                        try {
                            int actualSmallIconId =
                                    IWebApkApi.Stub.asInterface(api).getSmallIconId();
                            assertEquals(actualSmallIconId, expectedSmallIconId);
                            callbackHelper.notifyCalled();
                        } catch (Exception e) {
                            fail("WebApkService binder call threw exception");
                        }
                    }
                });

        callbackHelper.waitForNext();
    }
}