// 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 static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build.VERSION_CODES;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.AdvancedMockContext;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.download.DownloadManagerBridge.DownloadQueryResult;
import org.chromium.chrome.browser.download.OMADownloadHandler.OMAInfo;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.test.ChromeBrowserTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemState;
import org.chromium.components.offline_items_collection.UpdateDelta;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.url.GURL;
import java.io.ByteArrayInputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** Tests for OMADownloadHandler class. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class OMADownloadHandlerTest {
@ClassRule
public static final ChromeBrowserTestRule sBrowserTestRule = new ChromeBrowserTestRule();
private static final String INSTALL_NOTIFY_URI = "http://test/test";
private TestInfoBarController mTestInfoBarController;
@Before
public void before() {
ThreadUtils.runOnUiThreadBlocking(
() -> {
mTestInfoBarController = new TestInfoBarController();
DownloadManagerService.getDownloadManagerService()
.setInfoBarControllerForTesting(mTestInfoBarController);
});
}
private Context getTestContext() {
return new AdvancedMockContext(ApplicationProvider.getApplicationContext());
}
/** Mock implementation of the DownloadMessageUiController. */
static class TestInfoBarController implements DownloadMessageUiController {
public boolean mDownloadStarted;
public OfflineItem mLastUpdatedItem;
public TestInfoBarController() {}
@Override
public void onDownloadStarted() {
mDownloadStarted = true;
}
@Override
public void showIncognitoDownloadMessage(Callback<Boolean> callback) {}
@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 onItemUpdated(OfflineItem item, UpdateDelta updateDelta) {
mLastUpdatedItem = item;
}
@Override
public void onItemRemoved(ContentId id) {}
@Override
public void onNotificationShown(ContentId id, int notificationId) {}
@Override
public boolean isShowing() {
return false;
}
public OfflineItem getLastUpdatedItem() {
return mLastUpdatedItem;
}
}
private static class OMADownloadHandlerForTest extends OMADownloadHandler {
public String mNofityURI;
public long mDownloadId;
public OMADownloadHandlerForTest(Context context) {
super(context);
addObserverForTest(
downloadId -> {
mDownloadId = downloadId;
});
}
@Override
protected boolean sendNotification(
OMAInfo omaInfo, DownloadInfo downloadInfo, long downloadId, String statusMessage) {
mNofityURI = omaInfo.getValue(OMA_INSTALL_NOTIFY_URI);
return true;
}
}
/** Helper class to verify the result of {@DownloadManagerDelegate.queryDownloadResult}. */
private static class DownloadQueryResultVerifier implements Callback<DownloadQueryResult> {
private int mExpectedDownloadStatus;
public boolean mQueryCompleted;
public DownloadQueryResultVerifier(int expectedDownloadStatus) {
mExpectedDownloadStatus = expectedDownloadStatus;
}
@Override
public void onResult(DownloadQueryResult result) {
mQueryCompleted = true;
Assert.assertEquals(mExpectedDownloadStatus, result.downloadStatus);
}
}
private void waitForQueryCompletion(final DownloadQueryResultVerifier verifier) {
CriteriaHelper.pollUiThread(() -> verifier.mQueryCompleted);
}
/** Test to make sure {@link OMADownloadHandler#getSize} returns the right size for OMAInfo. */
@Test
@SmallTest
@Feature({"Download"})
public void testGetSize() {
OMADownloadHandler.OMAInfo info = new OMADownloadHandler.OMAInfo();
Assert.assertEquals(OMADownloadHandler.getSize(info), 0);
info.addAttributeValue("size", "100");
Assert.assertEquals(OMADownloadHandler.getSize(info), 100);
info.addAttributeValue("size", "100,000");
Assert.assertEquals(OMADownloadHandler.getSize(info), 100000);
info.addAttributeValue("size", "100000");
Assert.assertEquals(OMADownloadHandler.getSize(info), 100000);
}
/**
* Test to make sure {@link OMADownloadHandler.OMAInfo#getDrmType} returns the right DRM type.
*/
@Test
@SmallTest
@Feature({"Download"})
public void testGetDrmType() {
OMADownloadHandler.OMAInfo info = new OMADownloadHandler.OMAInfo();
Assert.assertEquals(info.getDrmType(), null);
info.addAttributeValue("type", "text/html");
Assert.assertEquals(info.getDrmType(), null);
info.addAttributeValue("type", MimeUtils.OMA_DRM_MESSAGE_MIME);
Assert.assertEquals(info.getDrmType(), MimeUtils.OMA_DRM_MESSAGE_MIME);
// Test that only the first DRM MIME type is returned.
info.addAttributeValue("type", MimeUtils.OMA_DRM_CONTENT_MIME);
Assert.assertEquals(info.getDrmType(), MimeUtils.OMA_DRM_MESSAGE_MIME);
}
/**
* Test to make sure {@link OMADownloadHandler#getOpennableType} returns the right MIME type.
*/
@Test
@SmallTest
@Feature({"Download"})
public void testGetOpennableType() {
OMADownloadHandler.OMAInfo info = new OMADownloadHandler.OMAInfo();
Assert.assertEquals(OMADownloadHandler.getOpennableType(info), null);
info.addAttributeValue(OMADownloadHandler.OMA_TYPE, "application/octet-stream");
info.addAttributeValue(OMADownloadHandler.OMA_TYPE, MimeUtils.OMA_DRM_MESSAGE_MIME);
info.addAttributeValue(OMADownloadHandler.OMA_TYPE, "text/html");
Assert.assertEquals(OMADownloadHandler.getOpennableType(info), null);
info.addAttributeValue(OMADownloadHandler.OMA_OBJECT_URI, "http://www.test.com/test.html");
Assert.assertEquals(OMADownloadHandler.getOpennableType(info), "text/html");
// Test that only the first opennable type is returned.
info.addAttributeValue(OMADownloadHandler.OMA_TYPE, "image/png");
Assert.assertEquals(OMADownloadHandler.getOpennableType(info), "text/html");
}
/**
* Test to make sure {@link OMADownloadHandler#parseDownloadDescriptor} returns the correct
* OMAInfo if the input is valid.
*/
@Test
@SmallTest
@Feature({"Download"})
public void testParseValidDownloadDescriptor() {
String downloadDescriptor =
"<media xmlns=\"http://www.openmobilealliance.org/xmlns/dd\">\r\n"
+ "<DDVersion>1.0</DDVersion>\r\n"
+ "<name>test.dm</name>\r\n"
+ "<size>1,000</size>\r\n"
+ "<type>image/jpeg</type>\r\n"
+ "<garbage>this is just garbage</garbage>\r\n"
+ "<type>application/vnd.oma.drm.message</type>\r\n"
+ "<vendor>testvendor</vendor>\r\n"
+ "<description>testjpg</description>\r\n"
+ "<objectURI>http://test/test.dm</objectURI>\r\n"
+ "<nextURL>http://nexturl.html</nextURL>\r\n"
+ "</media>";
OMADownloadHandler.OMAInfo info =
OMADownloadHandler.parseDownloadDescriptor(
new ByteArrayInputStream(
ApiCompatibilityUtils.getBytesUtf8(downloadDescriptor)));
Assert.assertFalse(info.isEmpty());
Assert.assertEquals(
info.getValue(OMADownloadHandler.OMA_OBJECT_URI), "http://test/test.dm");
Assert.assertEquals(info.getValue(OMADownloadHandler.OMA_DD_VERSION), "1.0");
Assert.assertEquals(info.getValue(OMADownloadHandler.OMA_NAME), "test.dm");
Assert.assertEquals(info.getValue(OMADownloadHandler.OMA_SIZE), "1,000");
Assert.assertEquals(info.getValue(OMADownloadHandler.OMA_VENDOR), "testvendor");
Assert.assertEquals(info.getValue(OMADownloadHandler.OMA_DESCRIPTION), "testjpg");
Assert.assertEquals(info.getValue(OMADownloadHandler.OMA_NEXT_URL), "http://nexturl.html");
List<String> types = info.getTypes();
assertThat(
types, Matchers.containsInAnyOrder("image/jpeg", MimeUtils.OMA_DRM_MESSAGE_MIME));
}
/**
* Test that {@link OMADownloadHandler#parseDownloadDescriptor} returns empty result on invalid
* input.
*/
@Test
@SmallTest
@Feature({"Download"})
public void testParseInvalidDownloadDescriptor() {
String downloadDescriptor =
"<media xmlns=\"http://www.openmobilealliance.org/xmlns/dd\">\r\n" + "</media>";
OMADownloadHandler.OMAInfo info =
OMADownloadHandler.parseDownloadDescriptor(
new ByteArrayInputStream(
ApiCompatibilityUtils.getBytesUtf8(downloadDescriptor)));
Assert.assertTrue(info.isEmpty());
downloadDescriptor =
"<media xmlns=\"http://www.openmobilealliance.org/xmlns/dd\">\r\n"
+ "<DDVersion>1.0</DDVersion>\r\n"
+ "<name>"
+ "<size>1,000</size>\r\n"
+ "test.dm"
+ "</name>\r\n"
+ "</media>";
info =
OMADownloadHandler.parseDownloadDescriptor(
new ByteArrayInputStream(
ApiCompatibilityUtils.getBytesUtf8(downloadDescriptor)));
Assert.assertNull(info);
downloadDescriptor =
"garbage"
+ "<media xmlns=\"http://www.openmobilealliance.org/xmlns/dd\">\r\n"
+ "<DDVersion>1.0</DDVersion>\r\n"
+ "</media>";
info =
OMADownloadHandler.parseDownloadDescriptor(
new ByteArrayInputStream(
ApiCompatibilityUtils.getBytesUtf8(downloadDescriptor)));
Assert.assertNull(info);
}
/**
* Test to make sure {@link DownloadManagerBridge#queryDownloadResult} will report correctly
* about the status of completed downloads and removed downloads.
*/
@Test
@MediumTest
@DisableIf.Build(sdk_is_greater_than = VERSION_CODES.Q) // https://crbug.com/338971643
@Feature({"Download"})
public void testQueryDownloadResult() {
Context context = getTestContext();
DownloadManager manager =
(DownloadManager) getTestContext().getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId1 =
manager.addCompletedDownload(
"test",
"test",
false,
"text/html",
UrlUtils.getIsolatedTestFilePath(
"chrome/test/data/android/download/download.txt"),
4,
true);
DownloadQueryResultVerifier verifier =
new DownloadQueryResultVerifier(DownloadStatus.COMPLETE);
DownloadManagerBridge.queryDownloadResult(downloadId1, verifier);
waitForQueryCompletion(verifier);
manager.remove(downloadId1);
verifier = new DownloadQueryResultVerifier(DownloadStatus.CANCELLED);
DownloadManagerBridge.queryDownloadResult(downloadId1, verifier);
waitForQueryCompletion(verifier);
}
/**
* Test to make sure {@link OMADownloadHandler#clearPendingOMADownloads} will clear the OMA
* notifications and pass the notification URI to {@link OMADownloadHandler}.
*/
@Test
@MediumTest
@DisableIf.Build(sdk_is_greater_than = VERSION_CODES.Q) // https://crbug.com/338971643
@Feature({"Download"})
public void testClearPendingOMADownloads() {
Context context = getTestContext();
DownloadManager manager =
(DownloadManager) getTestContext().getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId1 =
manager.addCompletedDownload(
"test",
"test",
false,
"text/html",
UrlUtils.getIsolatedTestFilePath(
"chrome/test/data/android/download/download.txt"),
4,
true);
final OMADownloadHandlerForTest omaHandler = new OMADownloadHandlerForTest(context);
// Write a few pending downloads into shared preferences.
Set<String> pendingOmaDownloads = new HashSet<>();
pendingOmaDownloads.add(String.valueOf(downloadId1) + "," + INSTALL_NOTIFY_URI);
DownloadManagerService.storeDownloadInfo(
ChromeSharedPreferences.getInstance(),
ChromePreferenceKeys.DOWNLOAD_PENDING_OMA_DOWNLOADS,
pendingOmaDownloads,
/* forceCommit= */ false);
pendingOmaDownloads =
DownloadManagerService.getStoredDownloadInfo(
ChromeSharedPreferences.getInstance(),
ChromePreferenceKeys.DOWNLOAD_PENDING_OMA_DOWNLOADS);
Assert.assertEquals(1, pendingOmaDownloads.size());
omaHandler.clearPendingOMADownloads();
// Wait for OMADownloadHandler to clear the pending downloads.
CriteriaHelper.pollUiThread(
() -> {
OfflineItem item = mTestInfoBarController.getLastUpdatedItem();
Criteria.checkThat(item, Matchers.notNullValue());
Criteria.checkThat(item.state, Matchers.is(OfflineItemState.COMPLETE));
});
// The pending downloads set in the shared prefs should be empty now.
pendingOmaDownloads =
DownloadManagerService.getStoredDownloadInfo(
ChromeSharedPreferences.getInstance(),
ChromePreferenceKeys.DOWNLOAD_PENDING_OMA_DOWNLOADS);
Assert.assertEquals(0, pendingOmaDownloads.size());
Assert.assertEquals(omaHandler.mNofityURI, INSTALL_NOTIFY_URI);
manager.remove(downloadId1);
}
/**
* Test that calling {@link OMADownloadHandler#enqueueDownloadManagerRequest} for an OMA
* download will enqueue a new DownloadManager request and insert an entry into the SharedPrefs.
*/
@Test
@MediumTest
@Feature({"Download"})
public void testEnqueueOMADownloads() {
EmbeddedTestServer testServer =
EmbeddedTestServer.createAndStartServer(
ApplicationProvider.getApplicationContext());
Context context = getTestContext();
OMADownloadHandler.OMAInfo omaInfo = new OMAInfo();
omaInfo.addAttributeValue(OMADownloadHandler.OMA_NAME, "test.gzip");
omaInfo.addAttributeValue(
OMADownloadHandler.OMA_OBJECT_URI,
testServer.getURL("/chrome/test/data/android/download/test.gzip"));
omaInfo.addAttributeValue(OMADownloadHandler.OMA_INSTALL_NOTIFY_URI, INSTALL_NOTIFY_URI);
DownloadInfo info = new DownloadInfo.Builder().build();
final OMADownloadHandlerForTest omaHandler =
ThreadUtils.runOnUiThreadBlocking(
() -> {
return new OMADownloadHandlerForTest(context) {
@Override
public void onReceive(Context context, Intent intent) {
// Ignore all the broadcasts.
}
};
});
omaHandler.clearPendingOMADownloads();
omaHandler.downloadOMAContent(0, info, omaInfo);
CriteriaHelper.pollUiThread(
() -> {
Criteria.checkThat(omaHandler.mDownloadId, Matchers.not(0));
Criteria.checkThat(mTestInfoBarController.mDownloadStarted, Matchers.is(true));
});
Set<String> downloads =
DownloadManagerService.getStoredDownloadInfo(
ChromeSharedPreferences.getInstance(),
ChromePreferenceKeys.DOWNLOAD_PENDING_OMA_DOWNLOADS);
Assert.assertEquals(1, downloads.size());
OMADownloadHandler.OMAEntry entry =
OMADownloadHandler.OMAEntry.parseOMAEntry((String) (downloads.toArray()[0]));
Assert.assertEquals(entry.mDownloadId, omaHandler.mDownloadId);
Assert.assertEquals(entry.mInstallNotifyURI, INSTALL_NOTIFY_URI);
DownloadManager manager =
(DownloadManager) getTestContext().getSystemService(Context.DOWNLOAD_SERVICE);
manager.remove(omaHandler.mDownloadId);
}
}