chromium/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java

// Copyright 2015 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.download;

import android.app.Notification;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Looper;
import android.util.Pair;
import android.view.View;

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

import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.download.DownloadTestRule.CustomMainActivityStart;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.profiles.OTRProfileID;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabCreator;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.components.download.DownloadState;
import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.FailState;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItem.Progress;
import org.chromium.components.offline_items_collection.PendingState;
import org.chromium.components.offline_items_collection.UpdateDelta;
import org.chromium.components.policy.test.annotations.Policies;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.util.TestWebServer;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.test.util.DeviceRestriction;
import org.chromium.url.GURL;

import java.util.ArrayList;
import java.util.List;

/** Tests Chrome download feature by attempting to download some files. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Batch(Batch.PER_CLASS)
public class DownloadTest {
    @ClassRule
    public static DownloadTestRule sDownloadTestRule =
            new DownloadTestRule(
                    new CustomMainActivityStart() {
                        @Override
                        public void customMainActivityStart() throws InterruptedException {
                            sDownloadTestRule.startMainActivityOnBlankPage();
                        }
                    });

    @Rule
    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
            new BlankCTATabInitialStateRule(sDownloadTestRule, false);

    private static final String SUPERBO_CONTENTS = "plain text response from a POST";

    private static EmbeddedTestServer sTestServer;

    private static final String TEST_DOWNLOAD_DIRECTORY = "/chrome/test/data/android/download/";

    private static final String FILENAME_WALLPAPER = "[large]wallpaper.dm";
    private static final String FILENAME_TEXT = "superbo.txt";
    private static final String FILENAME_TEXT_1 = "superbo (1).txt";
    private static final String FILENAME_TEXT_2 = "superbo (2).txt";
    private static final String FILENAME_SWF = "test.swf";
    private static final String FILENAME_GZIP = "test.gzip";

    private static final String[] TEST_FILES =
            new String[] {
                FILENAME_WALLPAPER,
                FILENAME_TEXT,
                FILENAME_TEXT_1,
                FILENAME_TEXT_2,
                FILENAME_SWF,
                FILENAME_GZIP
            };

    static class DownloadManagerRequestInterceptorForTest
            implements DownloadManagerService.DownloadManagerRequestInterceptor {
        public DownloadItem mDownloadItem;

        @Override
        public void interceptDownloadRequest(DownloadItem item, boolean notifyComplete) {
            mDownloadItem = item;
            Assert.assertTrue(notifyComplete);
        }
    }

    static class TestDownloadMessageUiController implements DownloadMessageUiController {
        public TestDownloadMessageUiController() {}

        @Override
        public void onDownloadStarted() {}

        @Override
        public void showIncognitoDownloadMessage(Callback<Boolean> callback) {}

        @Override
        public void onNotificationShown(ContentId id, int notificationId) {}

        @Override
        public void addDownloadInterstitialSource(GURL originalUrl) {}

        @Override
        public boolean isDownloadInterstitialItem(GURL originalUrl, String guid) {
            return false;
        }

        @Override
        public void onItemsAdded(List<OfflineItem> items) {}

        @Override
        public void onItemRemoved(ContentId id) {}

        @Override
        public void onItemUpdated(OfflineItem item, UpdateDelta updateDelta) {}

        @Override
        public boolean isShowing() {
            return false;
        }
    }

    private static class MockNotificationService extends DownloadNotificationService {
        @Override
        void updateNotification(int id, Notification notification) {}

        @Override
        public void cancelNotification(int notificationId, ContentId id) {}

        @Override
        public int notifyDownloadSuccessful(
                final ContentId id,
                final String filePath,
                final String fileName,
                final long systemDownloadId,
                final OTRProfileID otrProfileID,
                final boolean isSupportedMimeType,
                final boolean isOpenable,
                final Bitmap icon,
                final GURL originalUrl,
                final boolean shouldPromoteOrigin,
                final GURL referrer,
                final long totalBytes) {
            return 0;
        }

        @Override
        public void notifyDownloadProgress(
                final ContentId id,
                final String fileName,
                final Progress progress,
                final long bytesReceived,
                final long timeRemainingInMillis,
                final long startTime,
                final OTRProfileID otrProfileID,
                final boolean canDownloadWhileMetered,
                final boolean isTransient,
                final Bitmap icon,
                final GURL originalUrl,
                final boolean shouldPromoteOrigin) {}

        @Override
        void notifyDownloadPaused(
                ContentId id,
                String fileName,
                boolean isResumable,
                boolean isAutoResumable,
                OTRProfileID otrProfileID,
                boolean isTransient,
                Bitmap icon,
                final GURL originalUrl,
                final boolean shouldPromoteOrigin,
                boolean hasUserGesture,
                boolean forceRebuild,
                @PendingState int pendingState) {}

        @Override
        public void notifyDownloadFailed(
                final ContentId id,
                final String fileName,
                final Bitmap icon,
                final GURL originalUrl,
                final boolean shouldPromoteOrigin,
                OTRProfileID otrProfileID,
                @FailState int failState) {}

        @Override
        public void notifyDownloadCanceled(final ContentId id, boolean hasUserGesture) {}

        @Override
        void resumeDownload(Intent intent) {}
    }

    @BeforeClass
    public static void beforeClass() {
        Looper.prepare();
        sTestServer = sDownloadTestRule.getTestServer();
        DownloadNotificationService.setInstanceForTests(new MockNotificationService());
    }

    @Before
    public void setUp() {
        sDownloadTestRule.resetCallbackHelper();
    }

    @After
    public void tearDown() {
        deleteTestFiles();
    }

    void waitForLastDownloadToFinish() {
        CriteriaHelper.pollUiThread(
                () -> {
                    List<DownloadItem> downloads = sDownloadTestRule.getAllDownloads();
                    Criteria.checkThat(downloads.size(), Matchers.greaterThanOrEqualTo(1));
                    Criteria.checkThat(
                            downloads.get(downloads.size() - 1).getDownloadInfo().state(),
                            Matchers.is(DownloadState.COMPLETE));
                });
    }

    void waitForAnyDownloadToCancel() {
        CriteriaHelper.pollUiThread(
                () -> {
                    List<DownloadItem> downloads = sDownloadTestRule.getAllDownloads();
                    Criteria.checkThat(downloads.size(), Matchers.greaterThanOrEqualTo(1));
                    boolean hasCanceled = false;
                    for (DownloadItem download : downloads) {
                        if (download.getDownloadInfo().state() == DownloadState.CANCELLED) {
                            hasCanceled = true;
                            break;
                        }
                    }
                    Criteria.checkThat(hasCanceled, Matchers.is(true));
                });
    }

    @Test
    @LargeTest
    @Feature({"Downloads"})
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO)
    public void testHttpGetDownload() throws Exception {
        loadUrl(sTestServer.getURL(TEST_DOWNLOAD_DIRECTORY + "get.html"));
        waitForFocus();
        View currentView = sDownloadTestRule.getActivity().getActivityTab().getView();

        int callCount = sDownloadTestRule.getChromeDownloadCallCount();
        TouchCommon.singleClickView(currentView);
        Assert.assertTrue(sDownloadTestRule.waitForChromeDownloadToFinish(callCount));
        Assert.assertTrue(sDownloadTestRule.hasDownloaded(FILENAME_GZIP, null));
    }

    @Test
    @LargeTest
    @Feature({"Downloads"})
    public void testHttpPostDownload() throws Exception {
        loadUrl(sTestServer.getURL(TEST_DOWNLOAD_DIRECTORY + "post.html"));
        waitForFocus();
        View currentView = sDownloadTestRule.getActivity().getActivityTab().getView();

        int callCount = sDownloadTestRule.getChromeDownloadCallCount();
        TouchCommon.singleClickView(currentView);
        Assert.assertTrue(sDownloadTestRule.waitForChromeDownloadToFinish(callCount));
        Assert.assertTrue(sDownloadTestRule.hasDownloaded(FILENAME_TEXT, SUPERBO_CONTENTS));
    }

    @Test
    @LargeTest
    @Feature({"Downloads"})
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO)
    @Policies.Add({@Policies.Item(key = "PromptForDownloadLocation", string = "false")})
    public void testCloseEmptyDownloadTab() throws Exception {
        loadUrl(sTestServer.getURL(TEST_DOWNLOAD_DIRECTORY + "get.html"));
        waitForFocus();
        final int initialTabCount = sDownloadTestRule.getActivity().getCurrentTabModel().getCount();
        int currentCallCount = sDownloadTestRule.getChromeDownloadCallCount();
        View currentView = sDownloadTestRule.getActivity().getActivityTab().getView();
        TouchCommon.singleClickView(currentView);
        Assert.assertTrue(sDownloadTestRule.waitForChromeDownloadToFinish(currentCallCount));

        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            sDownloadTestRule.getActivity().getCurrentTabModel().getCount(),
                            Matchers.is(initialTabCount));
                });
    }

    private void openNewTab(String url) {
        Tab oldTab = sDownloadTestRule.getActivity().getActivityTabProvider().get();
        TabCreator tabCreator = sDownloadTestRule.getActivity().getTabCreator(false);
        final TabModel model = sDownloadTestRule.getActivity().getCurrentTabModel();
        final int count = model.getCount();
        final Tab newTab =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return tabCreator.createNewTab(
                                    new LoadUrlParams(url, PageTransition.LINK),
                                    TabLaunchType.FROM_LINK,
                                    oldTab);
                        });
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(count + 1, Matchers.is(model.getCount()));
                    Criteria.checkThat(newTab, Matchers.is(model.getTabAt(count)));
                    Criteria.checkThat(ChromeTabUtils.isRendererReady(newTab), Matchers.is(true));
                });
    }

    @Test
    @LargeTest
    @Feature({"Downloads"})
    public void testUrlEscaping() throws Exception {
        loadUrl(sTestServer.getURL(TEST_DOWNLOAD_DIRECTORY + "urlescaping.html"));
        waitForFocus();
        View currentView = sDownloadTestRule.getActivity().getActivityTab().getView();

        int callCount = sDownloadTestRule.getChromeDownloadCallCount();
        TouchCommon.singleClickView(currentView);
        Assert.assertTrue(sDownloadTestRule.waitForChromeDownloadToFinish(callCount));
        Assert.assertTrue(sDownloadTestRule.hasDownloaded(FILENAME_WALLPAPER, null));
    }

    private void loadUrl(String url) {
        sDownloadTestRule.loadUrlInTab(
                url,
                PageTransition.TYPED | PageTransition.FROM_ADDRESS_BAR,
                sDownloadTestRule.getActivity().getActivityTab(),
                20L // 20 seconds timeout
                );
    }

    @Test
    @LargeTest
    @Feature({"Navigation"})
    public void testOMADownloadInterception() throws Exception {
        TestWebServer webServer = TestWebServer.start();
        try {
            final DownloadManagerRequestInterceptorForTest interceptor =
                    new DownloadManagerRequestInterceptorForTest();
            ThreadUtils.runOnUiThreadBlocking(
                    () ->
                            DownloadManagerService.getDownloadManagerService()
                                    .setDownloadManagerRequestInterceptor(interceptor));
            List<Pair<String, String>> headers = new ArrayList<>();
            headers.add(Pair.create("Content-Type", "application/vnd.oma.drm.message"));
            final String url = webServer.setResponse("/test.dm", "testdata", headers);
            sDownloadTestRule.loadUrl(
                    UrlUtils.encodeHtmlDataUri(
                            "<script>"
                                    + "  function download() {"
                                    + "    window.open( '"
                                    + url
                                    + "')"
                                    + "  }"
                                    + "</script>"
                                    + "<body id='body' onclick='download()'></body>"));
            DOMUtils.clickNode(sDownloadTestRule.getActivity().getCurrentWebContents(), "body");
            CriteriaHelper.pollUiThread(
                    () -> {
                        Criteria.checkThat(interceptor.mDownloadItem, Matchers.notNullValue());
                        Criteria.checkThat(
                                interceptor.mDownloadItem.getDownloadInfo().getUrl().getSpec(),
                                Matchers.is(url));
                    });
        } finally {
            webServer.shutdown();
        }
    }

    private void waitForFocus() {
        View currentView = sDownloadTestRule.getActivity().getActivityTab().getView();
        if (!currentView.hasFocus()) {
            TouchCommon.singleClickView(currentView);
        }
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    }

    /**
     * Makes sure there are no files with names identical to the ones this test uses in the
     * downloads directory
     */
    private void deleteTestFiles() {
        sDownloadTestRule.deleteFilesInDownloadDirectory(TEST_FILES);
    }
}