chromium/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.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.background_sync;

import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;

import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.After;
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.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.background_sync.BackgroundSyncBackgroundTaskScheduler.BackgroundSyncTask;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.browser.TabTitleObserver;
import org.chromium.chrome.test.util.browser.signin.AccountManagerTestRule;
import org.chromium.components.externalauth.ExternalAuthUtils;
import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
import org.chromium.content_public.browser.test.util.BackgroundSyncNetworkUtils;
import org.chromium.net.ConnectionType;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.ServerCertificate;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/** Instrumentation test for Background Sync. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public final class BackgroundSyncTest {
    @Rule
    public final ChromeTabbedActivityTestRule mActivityTestRule =
            new ChromeTabbedActivityTestRule();

    // loadNativeLibraryNoBrowserProcess will access AccountManagerFacade, so we need
    // to mock AccountManagerFacade
    @Rule
    public final AccountManagerTestRule mAccountManagerTestRule = new AccountManagerTestRule();

    private EmbeddedTestServer mTestServer;
    private static final String BACKGROUND_SYNC_TEST_PAGE =
            "/chrome/test/data/background_sync/background_sync_test.html";
    private static final int TITLE_UPDATE_TIMEOUT_SECONDS = (int) scaleTimeout(10);
    private static final long WAIT_TIME_MS = scaleTimeout(5000);
    private static final String DISABLE_ANDROID_NETWORK_DETECTION =
            "BackgroundSync.RelyOnAndroidNetworkDetection:rely_on_android_network_detection/false";

    private CountDownLatch mScheduleLatch;
    private CountDownLatch mCancelLatch;

    private BackgroundSyncBackgroundTaskScheduler.Observer mSchedulerObserver;

    @Before
    public void setUp() {
        addSchedulerObserver();

        // This is necessary because our test devices don't have Google Play Services up to date,
        // and BackgroundSync requires that. Remove this once https://crbug.com/514449 has been
        // fixed.
        // Note that this should be done before the startMainActivityOnBlankPage(), because Chrome
        // will otherwise run this check on startup and disable BackgroundSync code.
        if (!ExternalAuthUtils.getInstance().canUseGooglePlayServices()) {
            NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess();
            disableGooglePlayServicesVersionCheck();
        }

        mActivityTestRule.startMainActivityOnBlankPage();

        // BackgroundSync only works with HTTPS.
        mTestServer =
                EmbeddedTestServer.createAndStartHTTPSServer(
                        InstrumentationRegistry.getInstrumentation().getContext(),
                        ServerCertificate.CERT_OK);
    }

    @After
    public void tearDown() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    BackgroundSyncBackgroundTaskScheduler.getInstance()
                            .removeObserver(mSchedulerObserver);
                });
    }

    @Test
    @MediumTest
    @Feature({"BackgroundSync"})
    @CommandLineFlags.Add({"force-fieldtrial-params=" + DISABLE_ANDROID_NETWORK_DETECTION})
    public void onSyncCalledWithNetworkConnectivity() throws Exception {
        forceConnectionType(ConnectionType.CONNECTION_NONE);

        mActivityTestRule.loadUrl(mTestServer.getURL(BACKGROUND_SYNC_TEST_PAGE));
        runJavaScript("SetupReplyForwardingForTests();");

        // Register Sync.
        runJavaScript("RegisterSyncForTag('tagSucceedsSync');");
        assertTitleBecomes("registered sync");
        Assert.assertTrue(mScheduleLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));

        forceConnectionType(ConnectionType.CONNECTION_WIFI);
        assertTitleBecomes("onsync: tagSucceedsSync");

        // Now that sync has completed, browser wakeup should get canceled.
        Assert.assertTrue(mCancelLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
    }

    @Test
    @MediumTest
    @Feature({"BackgroundSync"})
    public void browserWakeUpScheduledWhenSyncEventFails() throws Exception {
        forceConnectionType(ConnectionType.CONNECTION_NONE);

        mActivityTestRule.loadUrl(mTestServer.getURL(BACKGROUND_SYNC_TEST_PAGE));
        runJavaScript("SetupReplyForwardingForTests();");

        // Register Sync.
        runJavaScript("RegisterSyncForTag('tagFailsSync');");
        assertTitleBecomes("registered sync");
        Assert.assertTrue(mScheduleLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
        forceConnectionType(ConnectionType.CONNECTION_WIFI);

        // Browser wakeup must not be canceled.
        Assert.assertFalse(mCancelLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
    }

    /** Helper methods. */
    private String runJavaScript(String code) throws TimeoutException {
        return mActivityTestRule.runJavaScriptCodeInCurrentTab(code);
    }

    @SuppressWarnings("MissingFail")
    private void assertTitleBecomes(String expectedTitle) {
        Tab tab = mActivityTestRule.getActivity().getActivityTab();
        TabTitleObserver titleObserver = new TabTitleObserver(tab, expectedTitle);
        try {
            titleObserver.waitForTitleUpdate(TITLE_UPDATE_TIMEOUT_SECONDS);
        } catch (TimeoutException e) {
            // The title is not as expected, this assertion neatly logs what the difference is.
            Assert.assertEquals(expectedTitle, tab.getTitle());
        }
    }

    private void forceConnectionType(int connectionType) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    BackgroundSyncNetworkUtils.setConnectionTypeForTesting(connectionType);
                });
    }

    private void disableGooglePlayServicesVersionCheck() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    BackgroundSyncBackgroundTaskSchedulerJni.get()
                            .setPlayServicesVersionCheckDisabledForTests(/* disabled= */ true);
                });
    }

    private void addSchedulerObserver() {
        mScheduleLatch = new CountDownLatch(1);
        mCancelLatch = new CountDownLatch(1);
        mSchedulerObserver =
                new BackgroundSyncBackgroundTaskScheduler.Observer() {
                    @Override
                    public void oneOffTaskScheduledFor(
                            @BackgroundSyncTask int taskType, long delay) {
                        if (taskType == BackgroundSyncTask.ONE_SHOT_SYNC_CHROME_WAKE_UP) {
                            mScheduleLatch.countDown();
                        }
                    }

                    @Override
                    public void oneOffTaskCanceledFor(@BackgroundSyncTask int taskType) {
                        if (taskType == BackgroundSyncTask.ONE_SHOT_SYNC_CHROME_WAKE_UP) {
                            mCancelLatch.countDown();
                        }
                    }
                };

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    BackgroundSyncBackgroundTaskScheduler.getInstance()
                            .addObserver(mSchedulerObserver);
                });
    }
}