blob: 33a217158ba0b5d186a304bbce536f7eb25fb5f8 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.IntDef;
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.metrics.RecordHistogram;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.download.DownloadInfo.Builder;
import org.chromium.chrome.browser.download.DownloadManagerServiceTest.MockDownloadNotifier.MethodID;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.OfflineItem.Progress;
import org.chromium.components.offline_items_collection.OfflineItemProgressUnit;
import org.chromium.components.offline_items_collection.PendingState;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.ConnectionType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Test for DownloadManagerService.
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class DownloadManagerServiceTest {
@Rule
public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
private static final int UPDATE_DELAY_FOR_TEST = 1;
private static final int DELAY_BETWEEN_CALLS = 10;
private static final int LONG_UPDATE_DELAY_FOR_TEST = 500;
/**
* The MockDownloadNotifier. Currently there is no support for creating mock objects this is a
* simple mock object that provides testing support for checking a sequence of calls.
*/
static class MockDownloadNotifier extends SystemDownloadNotifier {
/**
* The Ids of different methods in this mock object.
*/
@IntDef({MethodID.DOWNLOAD_SUCCESSFUL, MethodID.DOWNLOAD_FAILED, MethodID.DOWNLOAD_PROGRESS,
MethodID.DOWNLOAD_PAUSED, MethodID.DOWNLOAD_INTERRUPTED,
MethodID.CANCEL_DOWNLOAD_ID, MethodID.CLEAR_PENDING_DOWNLOADS})
@Retention(RetentionPolicy.SOURCE)
public @interface MethodID {
int DOWNLOAD_SUCCESSFUL = 0;
int DOWNLOAD_FAILED = 1;
int DOWNLOAD_PROGRESS = 2;
int DOWNLOAD_PAUSED = 3;
int DOWNLOAD_INTERRUPTED = 4;
int CANCEL_DOWNLOAD_ID = 5;
int CLEAR_PENDING_DOWNLOADS = 6;
}
// Use MethodID for Integer values.
private final Queue<Pair<Integer, Object>> mExpectedCalls =
new ConcurrentLinkedQueue<Pair<Integer, Object>>();
public MockDownloadNotifier() {
expect(MethodID.CLEAR_PENDING_DOWNLOADS, null);
}
/** @deprecated Use constructor with no arguments instead. */
public MockDownloadNotifier(Context context) {
this();
}
public MockDownloadNotifier expect(@MethodID int method, Object param) {
mExpectedCalls.clear();
mExpectedCalls.add(getMethodSignature(method, param));
return this;
}
public void waitTillExpectedCallsComplete() {
CriteriaHelper.pollInstrumentationThread(
new Criteria("Failed while waiting for all calls to complete.") {
@Override
public boolean isSatisfied() {
return mExpectedCalls.isEmpty();
}
});
}
public MockDownloadNotifier andThen(@MethodID int method, Object param) {
mExpectedCalls.add(getMethodSignature(method, param));
return this;
}
static Pair<Integer, Object> getMethodSignature(@MethodID int methodId, Object param) {
return new Pair<Integer, Object>(methodId, param);
}
void assertCorrectExpectedCall(@MethodID int methodId, Object param, boolean matchParams) {
Log.w("MockDownloadNotifier", "Called: " + methodId);
Assert.assertFalse("Unexpected call:, no call expected, but got: " + methodId,
mExpectedCalls.isEmpty());
Pair<Integer, Object> actual = getMethodSignature(methodId, param);
Pair<Integer, Object> expected = mExpectedCalls.poll();
Assert.assertEquals("Unexpected call", expected.first, actual.first);
if (matchParams) {
Assert.assertTrue(
"Incorrect arguments", MatchHelper.macthes(expected.second, actual.second));
}
}
@Override
public void notifyDownloadSuccessful(DownloadInfo downloadInfo,
long systemDownloadId, boolean canResolve, boolean isSupportedMimeType) {
assertCorrectExpectedCall(MethodID.DOWNLOAD_SUCCESSFUL, downloadInfo, false);
Assert.assertEquals("application/unknown", downloadInfo.getMimeType());
super.notifyDownloadSuccessful(downloadInfo, systemDownloadId, canResolve,
isSupportedMimeType);
}
@Override
public void notifyDownloadFailed(DownloadInfo downloadInfo) {
assertCorrectExpectedCall(MethodID.DOWNLOAD_FAILED, downloadInfo, true);
}
@Override
public void notifyDownloadProgress(
DownloadInfo downloadInfo, long startTime, boolean canDownloadWhileMetered) {
assertCorrectExpectedCall(MethodID.DOWNLOAD_PROGRESS, downloadInfo, true);
}
@Override
public void notifyDownloadPaused(DownloadInfo downloadInfo) {
assertCorrectExpectedCall(MethodID.DOWNLOAD_PAUSED, downloadInfo, true);
}
@Override
public void notifyDownloadInterrupted(DownloadInfo downloadInfo, boolean isAutoResumable,
@PendingState int pendingState) {
assertCorrectExpectedCall(MethodID.DOWNLOAD_INTERRUPTED, downloadInfo, true);
}
@Override
public void notifyDownloadCanceled(ContentId id) {
assertCorrectExpectedCall(MethodID.CANCEL_DOWNLOAD_ID, id, true);
}
@Override
public void resumePendingDownloads() {}
}
/**
* A set that each object can be matched ^only^ once. Once matched, the object
* will be removed from the set. This is useful to write expectations
* for a sequence of calls where order of calls is not defined. Client can
* do the following. OneTimeMatchSet matchSet = new OneTimeMatchSet(possibleValue1,
* possibleValue2, possibleValue3); mockObject.expect(method1, matchSet).andThen(method1,
* matchSet).andThen(method3, matchSet); .... Some work.
* mockObject.waitTillExpectedCallsComplete(); assertTrue(matchSet.mMatches.empty());
*/
private static class OneTimeMatchSet {
private final HashSet<Object> mMatches;
OneTimeMatchSet(Object... params) {
mMatches = new HashSet<Object>();
Collections.addAll(mMatches, params);
}
public boolean matches(Object obj) {
if (obj == null) return false;
if (this == obj) return true;
if (!mMatches.contains(obj)) return false;
// Remove the object since it has been matched.
mMatches.remove(obj);
return true;
}
}
/**
* Class that helps matching 2 objects with either of them may be a OneTimeMatchSet object.
*/
private static class MatchHelper {
public static boolean macthes(Object obj1, Object obj2) {
if (obj1 == null) return obj2 == null;
if (obj1.equals(obj2)) return true;
if (obj1 instanceof OneTimeMatchSet) {
return ((OneTimeMatchSet) obj1).matches(obj2);
} else if (obj2 instanceof OneTimeMatchSet) {
return ((OneTimeMatchSet) obj2).matches(obj1);
}
return false;
}
}
private static class DownloadManagerServiceForTest extends DownloadManagerService {
boolean mResumed;
public DownloadManagerServiceForTest(
MockDownloadNotifier mockNotifier, long updateDelayInMillis) {
super(mockNotifier, getTestHandler(), updateDelayInMillis);
}
@Override
protected void init() {}
@Override
public void resumeDownload(ContentId id, DownloadItem item, boolean hasUserGesture) {
mResumed = true;
}
@Override
protected void scheduleUpdateIfNeeded() {
TestThreadUtils.runOnUiThreadBlocking(
() -> DownloadManagerServiceForTest.super.scheduleUpdateIfNeeded());
}
}
private DownloadManagerServiceForTest mService;
@Before
public void setUp() {
RecordHistogram.setDisabledForTests(true);
}
@After
public void tearDown() {
mService = null;
RecordHistogram.setDisabledForTests(false);
}
private static boolean useDownloadOfflineContentProvider() {
return ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER);
}
private static Handler getTestHandler() {
HandlerThread handlerThread = new HandlerThread("handlerThread");
handlerThread.start();
return new Handler(handlerThread.getLooper());
}
private DownloadInfo getDownloadInfo() {
return new Builder()
.setBytesReceived(100)
.setDownloadGuid(UUID.randomUUID().toString())
.setFileName("test")
.setDescription("test")
.setFilePath(UrlUtils.getIsolatedTestFilePath(
"chrome/test/data/android/download/download.txt"))
.build();
}
private void createDownloadManagerService(MockDownloadNotifier notifier, int delayForTest) {
TestThreadUtils.runOnUiThreadBlocking(
() -> { mService = new DownloadManagerServiceForTest(notifier, delayForTest); });
}
@Test
@MediumTest
@Feature({"Download"})
@RetryOnFailure
public void testAllDownloadProgressIsCalledForSlowUpdates() throws InterruptedException {
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
DownloadInfo downloadInfo = getDownloadInfo();
notifier.expect(MethodID.DOWNLOAD_PROGRESS, downloadInfo);
mService.onDownloadUpdated(downloadInfo);
notifier.waitTillExpectedCallsComplete();
// Now post multiple download updated calls and make sure all are received.
DownloadInfo update1 =
Builder.fromDownloadInfo(downloadInfo)
.setProgress(new Progress(10, 100L, OfflineItemProgressUnit.PERCENTAGE))
.build();
DownloadInfo update2 =
Builder.fromDownloadInfo(downloadInfo)
.setProgress(new Progress(30, 100L, OfflineItemProgressUnit.PERCENTAGE))
.build();
DownloadInfo update3 =
Builder.fromDownloadInfo(downloadInfo)
.setProgress(new Progress(30, 100L, OfflineItemProgressUnit.PERCENTAGE))
.build();
notifier.expect(MethodID.DOWNLOAD_PROGRESS, update1)
.andThen(MethodID.DOWNLOAD_PROGRESS, update2)
.andThen(MethodID.DOWNLOAD_PROGRESS, update3);
mService.onDownloadUpdated(update1);
Thread.sleep(DELAY_BETWEEN_CALLS);
mService.onDownloadUpdated(update2);
Thread.sleep(DELAY_BETWEEN_CALLS);
mService.onDownloadUpdated(update3);
notifier.waitTillExpectedCallsComplete();
}
@Test
@MediumTest
@Feature({"Download"})
public void testOnlyTwoProgressForFastUpdates() throws InterruptedException {
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, LONG_UPDATE_DELAY_FOR_TEST);
DownloadInfo downloadInfo = getDownloadInfo();
DownloadInfo update1 =
Builder.fromDownloadInfo(downloadInfo)
.setProgress(new Progress(10, 100L, OfflineItemProgressUnit.PERCENTAGE))
.build();
DownloadInfo update2 =
Builder.fromDownloadInfo(downloadInfo)
.setProgress(new Progress(10, 100L, OfflineItemProgressUnit.PERCENTAGE))
.build();
DownloadInfo update3 =
Builder.fromDownloadInfo(downloadInfo)
.setProgress(new Progress(10, 100L, OfflineItemProgressUnit.PERCENTAGE))
.build();
// Should get 2 update calls, the first and the last. The 2nd update will be merged into
// the last one.
notifier.expect(MethodID.DOWNLOAD_PROGRESS, update1)
.andThen(MethodID.DOWNLOAD_PROGRESS, update3);
mService.onDownloadUpdated(update1);
Thread.sleep(DELAY_BETWEEN_CALLS);
mService.onDownloadUpdated(update2);
Thread.sleep(DELAY_BETWEEN_CALLS);
mService.onDownloadUpdated(update3);
Thread.sleep(DELAY_BETWEEN_CALLS);
notifier.waitTillExpectedCallsComplete();
}
@Test
@MediumTest
@Feature({"Download"})
@RetryOnFailure
public void testDownloadCompletedIsCalled() throws InterruptedException {
if (useDownloadOfflineContentProvider()) return;
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
TestThreadUtils.runOnUiThreadBlocking(
(Runnable) () -> DownloadManagerService.setDownloadManagerService(mService));
DownloadInfoBarController infoBarController = mService.getInfoBarController(false);
// Try calling download completed directly.
DownloadInfo successful = getDownloadInfo();
notifier.expect(MethodID.DOWNLOAD_SUCCESSFUL, successful);
mService.onDownloadCompleted(successful);
notifier.waitTillExpectedCallsComplete();
// Now check that a successful notification appears after a download progress.
DownloadInfo progress = getDownloadInfo();
notifier.expect(MethodID.DOWNLOAD_PROGRESS, progress)
.andThen(MethodID.DOWNLOAD_SUCCESSFUL, progress);
mService.onDownloadUpdated(progress);
Thread.sleep(DELAY_BETWEEN_CALLS);
mService.onDownloadCompleted(progress);
notifier.waitTillExpectedCallsComplete();
}
@Test
@MediumTest
@Feature({"Download"})
public void testDownloadFailedIsCalled() {
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
TestThreadUtils.runOnUiThreadBlocking(
(Runnable) () -> DownloadManagerService.setDownloadManagerService(mService));
// Check that if an interrupted download cannot be resumed, it will trigger a download
// failure.
DownloadInfo failure =
Builder.fromDownloadInfo(getDownloadInfo()).setIsResumable(false).build();
notifier.expect(MethodID.DOWNLOAD_FAILED, failure);
mService.onDownloadInterrupted(failure, false);
notifier.waitTillExpectedCallsComplete();
}
@Test
@MediumTest
@Feature({"Download"})
public void testDownloadPausedIsCalled() {
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
DownloadManagerService.disableNetworkListenerForTest();
DownloadInfo interrupted =
Builder.fromDownloadInfo(getDownloadInfo()).setIsResumable(true).build();
notifier.expect(MethodID.DOWNLOAD_INTERRUPTED, interrupted);
mService.onDownloadInterrupted(interrupted, true);
notifier.waitTillExpectedCallsComplete();
}
@Test
@MediumTest
@Feature({"Download"})
@RetryOnFailure
public void testMultipleDownloadProgress() {
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
DownloadInfo download1 = getDownloadInfo();
DownloadInfo download2 = getDownloadInfo();
DownloadInfo download3 = getDownloadInfo();
OneTimeMatchSet matchSet = new OneTimeMatchSet(download1, download2, download3);
notifier.expect(MethodID.DOWNLOAD_PROGRESS, matchSet)
.andThen(MethodID.DOWNLOAD_PROGRESS, matchSet)
.andThen(MethodID.DOWNLOAD_PROGRESS, matchSet);
mService.onDownloadUpdated(download1);
mService.onDownloadUpdated(download2);
mService.onDownloadUpdated(download3);
notifier.waitTillExpectedCallsComplete();
Assert.assertTrue("All downloads should be updated.", matchSet.mMatches.isEmpty());
}
@Test
@MediumTest
@Feature({"Download"})
@RetryOnFailure
public void testInterruptedDownloadAreAutoResumed() throws InterruptedException {
FeatureUtilities.setDownloadAutoResumptionEnabledInNativeForTesting(false);
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
DownloadManagerService.disableNetworkListenerForTest();
DownloadInfo interrupted =
Builder.fromDownloadInfo(getDownloadInfo()).setIsResumable(true).build();
notifier.expect(MethodID.DOWNLOAD_PROGRESS, interrupted)
.andThen(MethodID.DOWNLOAD_INTERRUPTED, interrupted);
mService.onDownloadUpdated(interrupted);
Thread.sleep(DELAY_BETWEEN_CALLS);
mService.onDownloadInterrupted(interrupted, true);
notifier.waitTillExpectedCallsComplete();
int resumableIdCount = mService.mAutoResumableDownloadIds.size();
mService.onConnectionTypeChanged(ConnectionType.CONNECTION_WIFI);
Assert.assertEquals(resumableIdCount - 1, mService.mAutoResumableDownloadIds.size());
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
return mService.mResumed;
}
});
}
@Test
//@MediumTest
//@Feature({"Download"})
@DisabledTest // crbug.com/789931
public void testInterruptedUnmeteredDownloadCannotAutoResumeOnMeteredNetwork()
throws InterruptedException {
MockDownloadNotifier notifier = new MockDownloadNotifier();
createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
DownloadManagerService.disableNetworkListenerForTest();
DownloadInfo interrupted =
Builder.fromDownloadInfo(getDownloadInfo()).setIsResumable(true).build();
notifier.expect(MethodID.DOWNLOAD_PROGRESS, interrupted)
.andThen(MethodID.DOWNLOAD_INTERRUPTED, interrupted);
mService.onDownloadUpdated(interrupted);
Thread.sleep(DELAY_BETWEEN_CALLS);
mService.onDownloadInterrupted(interrupted, true);
notifier.waitTillExpectedCallsComplete();
DownloadManagerService.setIsNetworkMeteredForTest(true);
int resumableIdCount = mService.mAutoResumableDownloadIds.size();
mService.onConnectionTypeChanged(ConnectionType.CONNECTION_2G);
Assert.assertEquals(resumableIdCount, mService.mAutoResumableDownloadIds.size());
}
/**
* Test to make sure {@link DownloadUtils#shouldAutoOpenDownload}
* returns the right result for varying MIME types and Content-Dispositions.
*/
@Test
@SmallTest
@Feature({"Download"})
public void testshouldAutoOpenDownload() {
// Should not open any download type MIME types.
Assert.assertFalse(DownloadUtils.shouldAutoOpenDownload("application/download", true));
Assert.assertFalse(DownloadUtils.shouldAutoOpenDownload("application/x-download", true));
Assert.assertFalse(DownloadUtils.shouldAutoOpenDownload("application/octet-stream", true));
Assert.assertTrue(DownloadUtils.shouldAutoOpenDownload("application/pdf", true));
Assert.assertFalse(DownloadUtils.shouldAutoOpenDownload("application/pdf", false));
Assert.assertFalse(DownloadUtils.shouldAutoOpenDownload("application/x-download", true));
Assert.assertFalse(DownloadUtils.shouldAutoOpenDownload("application/x-download", true));
Assert.assertFalse(DownloadUtils.shouldAutoOpenDownload("application/x-download", true));
}
}