blob: c94532e8d3c13dbeea5193b64485ecbbd9cd32ee [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.libraries.mobiledatadownload;
import static android.system.Os.readlink;
import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_CHECKSUM;
import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_GROUP_NAME;
import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_ID;
import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_SIZE;
import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_URL;
import static com.google.android.libraries.mobiledatadownload.tracing.TracePropagation.propagateCallable;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import android.accounts.Account;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
import com.google.android.libraries.mobiledatadownload.account.AccountUtil;
import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri;
import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUriAdapter;
import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend;
import com.google.android.libraries.mobiledatadownload.file.openers.ReadStringOpener;
import com.google.android.libraries.mobiledatadownload.file.openers.WriteStringOpener;
import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil;
import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor;
import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader;
import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
import com.google.android.libraries.mobiledatadownload.testing.TestFileDownloader;
import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup;
import com.google.mobiledatadownload.DownloadConfigProto.DataFile;
import com.google.mobiledatadownload.DownloadConfigProto.DataFile.ChecksumType;
import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions;
import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy;
import java.io.IOException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeoutException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@RunWith(AndroidJUnit4.class)
public class MobileDataDownloadIntegrationTest {
private static final String TAG = "MobileDataDownloadIntegrationTest";
private static final int MAX_HANDLE_TASK_WAIT_TIME_SECS = 300;
private static final String TEST_DATA_RELATIVE_PATH =
"third_party/java_src/android_libs/mobiledatadownload/javatests/com/google/android/libraries/mobiledatadownload/testdata/";
// Note: Control Executor must not be a single thread executor.
private static final ListeningExecutorService CONTROL_EXECUTOR =
MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
private static final ScheduledExecutorService DOWNLOAD_EXECUTOR =
Executors.newScheduledThreadPool(2);
private static final Context context = ApplicationProvider.getApplicationContext();
private final NetworkUsageMonitor networkUsageMonitor =
new NetworkUsageMonitor(context, new FakeTimeSource());
private final SynchronousFileStorage fileStorage =
new SynchronousFileStorage(
ImmutableList.of(AndroidFileBackend.builder(context).build(), new JavaFileBackend()),
ImmutableList.of(),
ImmutableList.of(networkUsageMonitor));
private final TestFlags flags = new TestFlags();
@Mock private Logger mockLogger;
@Mock private TaskScheduler mockTaskScheduler;
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
@Before
public void setUp() throws Exception {
flags.enableZipFolder = Optional.of(true);
}
@Test
public void download_success_fileGroupDownloaded() throws Exception {
MobileDataDownload mobileDataDownload =
getMobileDataDownloadAfterDownload(
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)),
new TestFileGroupPopulator(context));
// This will trigger refreshing of FileGroupPopulators and downloading.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
String debugString = mobileDataDownload.getDebugInfoAsString();
Log.i(TAG, "MDD Lib dump:");
for (String line : debugString.split("\n", -1)) {
Log.i(TAG, line);
}
ClientFileGroup clientFileGroup =
getAndVerifyClientFileGroup(mobileDataDownload, FILE_GROUP_NAME, 1);
verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE);
mobileDataDownload.clear().get();
}
@Test
public void download_withCustomValidator() throws Exception {
CustomFileGroupValidator validator =
fileGroup -> {
if (!fileGroup.getGroupName().equals(FILE_GROUP_NAME)) {
return Futures.immediateFuture(true);
}
return MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)
.submit(
propagateCallable(
() -> {
SynchronousFileStorage storage =
new SynchronousFileStorage(
ImmutableList.of(AndroidFileBackend.builder(context).build()));
for (ClientFile file : fileGroup.getFileList()) {
if (!storage.exists(Uri.parse(file.getFileUri()))) {
return false;
}
}
return true;
}));
};
MobileDataDownload mobileDataDownload =
getMobileDataDownloadBuilder(
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)),
new TestFileGroupPopulator(context))
.setCustomFileGroupValidatorOptional(Optional.of(validator))
.build();
// This will trigger refreshing of FileGroupPopulators and downloading.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
ClientFileGroup clientFileGroup =
mobileDataDownload
.getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE);
mobileDataDownload.clear().get();
}
@Test
public void download_success_maintenanceLogsNetworkUsage() throws Exception {
flags.networkStatsLoggingSampleInterval = Optional.of(1);
MobileDataDownload mobileDataDownload =
getMobileDataDownload(
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)),
new TestFileGroupPopulator(context));
// This will trigger refreshing of FileGroupPopulators and downloading.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// This should flush the logs from NetworkLogger.
mobileDataDownload
.handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
ClientFileGroup clientFileGroup =
getAndVerifyClientFileGroup(mobileDataDownload, FILE_GROUP_NAME, 1);
verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE);
mobileDataDownload.clear().get();
}
@Test
public void corrupted_files_detectedDuringMaintenance() throws Exception {
flags.mddDefaultSampleInterval = Optional.of(1);
MobileDataDownload mobileDataDownload =
getMobileDataDownloadAfterDownload(
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)),
new TestFileGroupPopulator(context));
ClientFileGroup clientFileGroup =
getAndVerifyClientFileGroup(mobileDataDownload, FILE_GROUP_NAME, 1);
fileStorage.open(
Uri.parse(clientFileGroup.getFile(0).getFileUri()), WriteStringOpener.create("c0rrupt3d"));
// Bad file is detected during maintenance.
mobileDataDownload
.handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// File group is re-downloaded.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// Re-load the file group since the on-disk URIs will have changed.
clientFileGroup = getAndVerifyClientFileGroup(mobileDataDownload, FILE_GROUP_NAME, 1);
assertThat(
fileStorage.open(
Uri.parse(clientFileGroup.getFile(0).getFileUri()), ReadStringOpener.create()))
.isNotEqualTo("c0rrupt3d");
mobileDataDownload.clear().get();
}
@Test
public void delete_files_detectedDuringMaintenance() throws Exception {
flags.mddDefaultSampleInterval = Optional.of(1);
MobileDataDownload mobileDataDownload =
getMobileDataDownloadAfterDownload(
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)),
new TestFileGroupPopulator(context));
ClientFileGroup clientFileGroup =
getAndVerifyClientFileGroup(mobileDataDownload, FILE_GROUP_NAME, 1);
fileStorage.deleteFile(Uri.parse(clientFileGroup.getFile(0).getFileUri()));
// Bad file is detected during maintenance.
mobileDataDownload
.handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// File group is re-downloaded.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// Re-load the file group since the on-disk URIs will have changed.
clientFileGroup = getAndVerifyClientFileGroup(mobileDataDownload, FILE_GROUP_NAME, 1);
assertThat(fileStorage.exists(Uri.parse(clientFileGroup.getFile(0).getFileUri()))).isTrue();
mobileDataDownload.clear().get();
}
@Test
public void remove_withAccount_fileGroupRemains() throws Exception {
Supplier<FileDownloader> fileDownloaderSupplier =
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR));
MobileDataDownload mobileDataDownload =
getMobileDataDownloadAfterDownload(
fileDownloaderSupplier, new TestFileGroupPopulator(context));
// This will trigger refreshing of FileGroupPopulators and downloading.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// Remove the file group with account doesn't change anything, because the test group is not
// associated with any account.
Account account = AccountUtil.create("name", "google");
assertThat(account).isNotNull();
assertThat(
mobileDataDownload
.removeFileGroup(
RemoveFileGroupRequest.newBuilder()
.setGroupName(FILE_GROUP_NAME)
.setAccountOptional(Optional.of(account))
.build())
.get())
.isTrue();
ClientFileGroup clientFileGroup =
getAndVerifyClientFileGroup(mobileDataDownload, FILE_GROUP_NAME, 1);
verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE);
mobileDataDownload.clear().get();
}
@Test
public void remove_withoutAccount_fileGroupRemoved() throws Exception {
Supplier<FileDownloader> fileDownloaderSupplier =
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR));
MobileDataDownload mobileDataDownload =
getMobileDataDownloadAfterDownload(
fileDownloaderSupplier, new TestFileGroupPopulator(context));
// This will trigger refreshing of FileGroupPopulators and downloading.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// Remove the file group will make the file group not accessible from clients.
assertThat(
mobileDataDownload
.removeFileGroup(
RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get())
.isTrue();
ClientFileGroup clientFileGroup =
mobileDataDownload
.getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
assertThat(clientFileGroup).isNull();
mobileDataDownload.clear().get();
}
@Test
public void
removeFileGroupsByFilter_whenAccountNotSpecified_removesMatchingAccountIndependentGroups()
throws Exception {
Supplier<FileDownloader> fileDownloaderSupplier =
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR));
MobileDataDownload mobileDataDownload =
getMobileDataDownload(fileDownloaderSupplier, unused -> Futures.immediateVoidFuture());
// Remove all groups
mobileDataDownload
.removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build())
.get();
// Setup account
Account account = AccountUtil.create("name", "google");
// Setup two groups, 1 with account and 1 without an account
DataFileGroup fileGroupWithoutAccount =
TestFileGroupPopulator.createDataFileGroup(
FILE_GROUP_NAME,
context.getPackageName(),
new String[] {FILE_ID},
new int[] {FILE_SIZE},
new String[] {FILE_CHECKSUM},
new String[] {FILE_URL},
DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK)
.toBuilder()
.build();
DataFileGroup fileGroupWithAccount =
fileGroupWithoutAccount.toBuilder().setGroupName(FILE_GROUP_NAME + "_2").build();
// Add both groups to MDD
mobileDataDownload
.addFileGroup(
AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroupWithoutAccount).build())
.get();
mobileDataDownload
.addFileGroup(
AddFileGroupRequest.newBuilder()
.setDataFileGroup(fileGroupWithAccount)
.setAccountOptional(Optional.of(account))
.build())
.get();
// Verify that both groups are present
assertThat(
mobileDataDownload
.getFileGroupsByFilter(
GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build())
.get())
.hasSize(2);
// Remove file groups with given source only
mobileDataDownload
.removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build())
.get();
// Check that only account-dependent group remains
ImmutableList<ClientFileGroup> remainingGroups =
mobileDataDownload
.getFileGroupsByFilter(
GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build())
.get();
assertThat(remainingGroups).hasSize(1);
assertThat(remainingGroups.get(0).getGroupName()).isEqualTo(FILE_GROUP_NAME + "_2");
// Tear down: remove remaining group to prevent cross test errors
mobileDataDownload
.removeFileGroupsByFilter(
RemoveFileGroupsByFilterRequest.newBuilder()
.setAccountOptional(Optional.of(account))
.build())
.get();
}
@Test
public void download_failure_throwsDownloadException() throws Exception {
flags.mddDefaultSampleInterval = Optional.of(1);
Supplier<FileDownloader> fileDownloaderSupplier =
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR));
MobileDataDownload mobileDataDownload =
getMobileDataDownload(fileDownloaderSupplier, new TestFileGroupPopulator(context));
DataFileGroup dataFileGroup =
TestFileGroupPopulator.createDataFileGroup(
FILE_GROUP_NAME,
context.getPackageName(),
new String[] {"one", "two"},
new int[] {1000, 2000},
new String[] {"checksum1", "checksum2"},
new String[] {
"http://www.gstatic.com/", // This url is not secure.
"https://www.gstatic.com/does_not_exist" // This url does not exist.
},
DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK);
assertThat(
mobileDataDownload
.addFileGroup(
AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build())
.get())
.isTrue();
ListenableFuture<ClientFileGroup> downloadFuture =
mobileDataDownload.downloadFileGroup(
DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build());
ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get);
assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class);
AggregateException cause = (AggregateException) exception.getCause();
assertThat(cause).isNotNull();
ImmutableList<Throwable> failures = cause.getFailures();
assertThat(failures).hasSize(2);
assertThat(failures.get(0)).isInstanceOf(DownloadException.class);
assertThat(failures.get(1)).isInstanceOf(DownloadException.class);
}
@Test
public void download_failure_logsEvent() throws Exception {
flags.mddDefaultSampleInterval = Optional.of(1);
Supplier<FileDownloader> fileDownloaderSupplier =
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR));
MobileDataDownload mobileDataDownload =
getMobileDataDownload(fileDownloaderSupplier, new TestFileGroupPopulator(context));
DataFileGroup dataFileGroup =
TestFileGroupPopulator.createDataFileGroup(
FILE_GROUP_NAME,
context.getPackageName(),
new String[] {"one", "two"},
new int[] {1000, 2000},
new String[] {"checksum1", "checksum2"},
new String[] {
"http://www.gstatic.com/", // This url is not secure.
"https://www.gstatic.com/does_not_exist" // This url does not exist.
},
DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK);
assertThat(
mobileDataDownload
.addFileGroup(
AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build())
.get())
.isTrue();
ListenableFuture<ClientFileGroup> downloadFuture =
mobileDataDownload.downloadFileGroup(
DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build());
assertThrows(ExecutionException.class, downloadFuture::get);
}
@Test
public void download_zipFile_unzippedAfterDownload() throws Exception {
Supplier<FileDownloader> fileDownloaderSupplier =
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR));
MobileDataDownload mobileDataDownload =
getMobileDataDownloadAfterDownload(
fileDownloaderSupplier, new ZipFolderFileGroupPopulator(context));
ClientFileGroup clientFileGroup =
getAndVerifyClientFileGroup(
mobileDataDownload, ZipFolderFileGroupPopulator.FILE_GROUP_NAME, 3);
for (ClientFile clientFile : clientFileGroup.getFileList()) {
if ("/zip1.txt".equals(clientFile.getFileId())) {
verifyClientFile(clientFile, "/zip1.txt", 11);
} else if ("/zip2.txt".equals(clientFile.getFileId())) {
verifyClientFile(clientFile, "/zip2.txt", 11);
} else if ("/sub_folder/zip3.txt".equals(clientFile.getFileId())) {
verifyClientFile(clientFile, "/sub_folder/zip3.txt", 25);
} else {
fail("Unexpect file:" + clientFile.getFileId());
}
}
}
@Test
public void download_cancelDuringDownload_downloadCancelled() throws Exception {
BlockingFileDownloader blockingFileDownloader = new BlockingFileDownloader(CONTROL_EXECUTOR);
Supplier<FileDownloader> fakeFileDownloaderSupplier = () -> blockingFileDownloader;
MobileDataDownload mobileDataDownload =
getMobileDataDownload(fakeFileDownloaderSupplier, new TestFileGroupPopulator(context));
// Register the file group and trigger download.
mobileDataDownload
.addFileGroup(
AddFileGroupRequest.newBuilder()
.setDataFileGroup(
TestFileGroupPopulator.createDataFileGroup(
FILE_GROUP_NAME,
context.getPackageName(),
new String[] {FILE_ID},
new int[] {FILE_SIZE},
new String[] {FILE_CHECKSUM},
new String[] {FILE_URL},
DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK))
.build())
.get();
ListenableFuture<ClientFileGroup> downloadFuture =
mobileDataDownload.downloadFileGroup(
DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build());
// Wait for download to be scheduled. The future shouldn't be done yet.
blockingFileDownloader.waitForDownloadStarted();
assertThat(downloadFuture.isDone()).isFalse();
// Now remove the file group from MDD, which would cancel any ongoing download.
mobileDataDownload
.removeFileGroup(RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
// Now let the download future finish.
blockingFileDownloader.finishDownloading();
// Make sure that the download has been canceled and leads to cancelled future.
ExecutionException exception =
assertThrows(
ExecutionException.class,
() -> downloadFuture.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS));
assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class);
AggregateException cause = (AggregateException) exception.getCause();
assertThat(cause).isNotNull();
ImmutableList<Throwable> failures = cause.getFailures();
assertThat(failures).hasSize(1);
assertThat(failures.get(0)).isInstanceOf(CancellationException.class);
}
@Test
public void download_twoStepDownload_targetFileDownloaded() throws Exception {
Supplier<FileDownloader> fileDownloaderSupplier =
() ->
new TestFileDownloader(
TEST_DATA_RELATIVE_PATH,
fileStorage,
MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR));
MobileDataDownload mobileDataDownload =
getMobileDataDownload(fileDownloaderSupplier, new TwoStepPopulator(context, fileStorage));
// Add step1 file group to MDD.
DataFileGroup step1FileGroup =
createDataFileGroup(
"step1-file-group",
context.getPackageName(),
new String[] {"step1_id"},
new int[] {57},
new String[] {""},
new ChecksumType[] {ChecksumType.NONE},
new String[] {"https://www.gstatic.com/icing/idd/sample_group/step1.txt"},
DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK);
ListenableFuture<Boolean> unused =
mobileDataDownload.addFileGroup(
AddFileGroupRequest.newBuilder().setDataFileGroup(step1FileGroup).build());
// This will trigger refreshing of FileGroupPopulators and downloading.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
// Now verify that the step1-file-group is downloaded and then TwoStepPopulator will add
// step2-file-group and it was downloaded too in one cycle (one call of handleTask).
// Verify step1-file-group.
ClientFileGroup clientFileGroup =
getAndVerifyClientFileGroup(mobileDataDownload, "step1-file-group", 1);
verifyClientFile(clientFileGroup.getFile(0), "step1_id", 57);
// Verify step2-file-group.
clientFileGroup = getAndVerifyClientFileGroup(mobileDataDownload, "step2-file-group", 1);
verifyClientFile(clientFileGroup.getFile(0), "step2_id", 13);
mobileDataDownload.clear().get();
}
@Test
public void download_relativeFilePaths_createsSymlinks() throws Exception {
AndroidUriAdapter adapter = AndroidUriAdapter.forContext(context);
MobileDataDownload mobileDataDownload =
getMobileDataDownload(
() -> new TestFileDownloader(TEST_DATA_RELATIVE_PATH, fileStorage, CONTROL_EXECUTOR),
new TestFileGroupPopulator(context));
DataFileGroup fileGroup =
DataFileGroup.newBuilder()
.setGroupName(FILE_GROUP_NAME)
.setOwnerPackage(context.getPackageName())
.setPreserveFilenamesAndIsolateFiles(true)
.addFile(
DataFile.newBuilder()
.setFileId(FILE_ID)
.setByteSize(FILE_SIZE)
.setChecksumType(DataFile.ChecksumType.DEFAULT)
.setChecksum(FILE_CHECKSUM)
.setUrlToDownload(FILE_URL)
.setRelativeFilePath("relative_path")
.build())
.build();
mobileDataDownload
.addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroup).build())
.get();
mobileDataDownload
.downloadFileGroup(
DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
// verify symlink structure
Uri expectedFileUri =
DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent())
.buildUpon()
.appendPath(DirectoryUtil.MDD_STORAGE_SYMLINKS)
.appendPath(DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS)
.appendPath(FILE_GROUP_NAME)
.appendPath("relative_path")
.build();
// we can't get access to the full internal target file uri, but we know the start of it
Uri expectedStartTargetUri =
DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent())
.buildUpon()
.appendPath(DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS)
.appendPath("datadownloadfile_")
.build();
ClientFileGroup clientFileGroup =
mobileDataDownload
.getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
Uri fileUri = Uri.parse(clientFileGroup.getFile(0).getFileUri());
Uri targetUri =
AndroidUri.builder(context)
.fromAbsolutePath(readlink(adapter.toFile(fileUri).getAbsolutePath()))
.build();
assertThat(fileUri).isEqualTo(expectedFileUri);
assertThat(targetUri.toString()).contains(expectedStartTargetUri.toString());
assertThat(fileStorage.exists(fileUri)).isTrue();
assertThat(fileStorage.exists(targetUri)).isTrue();
}
@Test
public void remove_relativeFilePaths_removesSymlinks() throws Exception {
MobileDataDownload mobileDataDownload =
getMobileDataDownload(
() -> new TestFileDownloader(TEST_DATA_RELATIVE_PATH, fileStorage, CONTROL_EXECUTOR),
new TestFileGroupPopulator(context));
DataFileGroup fileGroup =
DataFileGroup.newBuilder()
.setGroupName(FILE_GROUP_NAME)
.setOwnerPackage(context.getPackageName())
.setPreserveFilenamesAndIsolateFiles(true)
.addFile(
DataFile.newBuilder()
.setFileId(FILE_ID)
.setByteSize(FILE_SIZE)
.setChecksumType(DataFile.ChecksumType.DEFAULT)
.setChecksum(FILE_CHECKSUM)
.setUrlToDownload(FILE_URL)
.setRelativeFilePath("relative_path")
.build())
.build();
mobileDataDownload
.addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroup).build())
.get();
mobileDataDownload
.downloadFileGroup(
DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
ClientFileGroup clientFileGroup =
mobileDataDownload
.getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
Uri fileUri = Uri.parse(clientFileGroup.getFile(0).getFileUri());
// Verify that file uri gets created
assertThat(fileStorage.exists(fileUri)).isTrue();
mobileDataDownload
.removeFileGroup(RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
.get();
// Verify that file uri still exists even though file group is stale
assertThat(fileStorage.exists(fileUri)).isTrue();
mobileDataDownload.maintenance().get();
// Verify that file uri gets removed, once maintenance runs
assertThat(fileStorage.exists(fileUri)).isFalse();
}
// TODO: Improve this helper by getting rid of the need to new arrays when invoking
// and unnamed params. Something along this line:
// createDataFileGroup(name,package).addFile(..).addFile()...
// A helper function to create a DataFilegroup.
public static DataFileGroup createDataFileGroup(
String groupName,
String ownerPackage,
String[] fileId,
int[] byteSize,
String[] checksum,
ChecksumType[] checksumType,
String[] url,
DeviceNetworkPolicy deviceNetworkPolicy) {
if (fileId.length != byteSize.length
|| fileId.length != checksum.length
|| fileId.length != url.length
|| checksumType.length != fileId.length) {
throw new IllegalArgumentException();
}
DataFileGroup.Builder dataFileGroupBuilder =
DataFileGroup.newBuilder()
.setGroupName(groupName)
.setOwnerPackage(ownerPackage)
.setDownloadConditions(
DownloadConditions.newBuilder().setDeviceNetworkPolicy(deviceNetworkPolicy));
for (int i = 0; i < fileId.length; ++i) {
DataFile file =
DataFile.newBuilder()
.setFileId(fileId[i])
.setByteSize(byteSize[i])
.setChecksum(checksum[i])
.setChecksumType(checksumType[i])
.setUrlToDownload(url[i])
.build();
dataFileGroupBuilder.addFile(file);
}
return dataFileGroupBuilder.build();
}
private MobileDataDownload getMobileDataDownload(
Supplier<FileDownloader> fileDownloaderSupplier, FileGroupPopulator fileGroupPopulator) {
return getMobileDataDownloadBuilder(fileDownloaderSupplier, fileGroupPopulator).build();
}
private MobileDataDownloadBuilder getMobileDataDownloadBuilder(
Supplier<FileDownloader> fileDownloaderSupplier, FileGroupPopulator fileGroupPopulator) {
return MobileDataDownloadBuilder.newBuilder()
.setContext(context)
.setControlExecutor(CONTROL_EXECUTOR)
.setFileDownloaderSupplier(fileDownloaderSupplier)
.addFileGroupPopulator(fileGroupPopulator)
.setTaskScheduler(Optional.of(mockTaskScheduler))
.setLoggerOptional(Optional.of(mockLogger))
.setDeltaDecoderOptional(Optional.absent())
.setFileStorage(fileStorage)
.setNetworkUsageMonitor(networkUsageMonitor)
.setFlagsOptional(Optional.of(flags));
}
/** Creates MDD object and triggers handleTask to refresh and download file groups. */
private MobileDataDownload getMobileDataDownloadAfterDownload(
Supplier<FileDownloader> fileDownloaderSupplier, FileGroupPopulator fileGroupPopulator)
throws InterruptedException, ExecutionException, TimeoutException {
MobileDataDownload mobileDataDownload =
getMobileDataDownload(fileDownloaderSupplier, fileGroupPopulator);
// This will trigger refreshing of FileGroupPopulators and downloading.
mobileDataDownload
.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK)
.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS);
String debugString = mobileDataDownload.getDebugInfoAsString();
Log.i(TAG, "MDD Lib dump:");
for (String line : debugString.split("\n", -1)) {
Log.i(TAG, line);
}
return mobileDataDownload;
}
private static ClientFileGroup getAndVerifyClientFileGroup(
MobileDataDownload mobileDataDownload, String fileGroupName, int fileCount)
throws ExecutionException, InterruptedException {
ClientFileGroup clientFileGroup =
mobileDataDownload
.getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(fileGroupName).build())
.get();
assertThat(clientFileGroup).isNotNull();
assertThat(clientFileGroup.getGroupName()).isEqualTo(fileGroupName);
assertThat(clientFileGroup.getFileCount()).isEqualTo(fileCount);
return clientFileGroup;
}
private void verifyClientFile(ClientFile clientFile, String fileId, int fileSize)
throws IOException {
assertThat(clientFile.getFileId()).isEqualTo(fileId);
Uri androidUri = Uri.parse(clientFile.getFileUri());
assertThat(fileStorage.fileSize(androidUri)).isEqualTo(fileSize);
}
}