• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.libraries.mobiledatadownload;
17 
18 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_CHECKSUM;
19 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_GROUP_NAME;
20 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_ID;
21 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_SIZE;
22 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_URL;
23 import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.DownloaderConfigurationType;
24 import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.ExecutorType;
25 import static com.google.common.truth.Truth.assertThat;
26 import static java.util.concurrent.TimeUnit.SECONDS;
27 import static org.junit.Assert.assertThrows;
28 
29 import android.accounts.Account;
30 import android.content.Context;
31 import android.net.Uri;
32 import android.util.Log;
33 import androidx.test.core.app.ApplicationProvider;
34 import com.google.android.libraries.mobiledatadownload.account.AccountUtil;
35 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
36 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
37 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
38 import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend;
39 import com.google.android.libraries.mobiledatadownload.file.transforms.CompressTransform;
40 import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor;
41 import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor;
42 import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader;
43 import com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies;
44 import com.google.android.libraries.mobiledatadownload.testing.TestFileDownloader;
45 import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
46 import com.google.common.base.Optional;
47 import com.google.common.base.Supplier;
48 import com.google.common.collect.ImmutableList;
49 import com.google.common.util.concurrent.Futures;
50 import com.google.common.util.concurrent.ListenableFuture;
51 import com.google.common.util.concurrent.ListeningExecutorService;
52 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
53 import com.google.common.util.concurrent.MoreExecutors;
54 import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
55 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup;
56 import com.google.mobiledatadownload.DownloadConfigProto.DataFile;
57 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
58 import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy;
59 import com.google.mobiledatadownload.TransformProto;
60 import com.google.mobiledatadownload.TransformProto.Transform;
61 import com.google.mobiledatadownload.TransformProto.Transforms;
62 import com.google.testing.junit.testparameterinjector.TestParameter;
63 import com.google.testing.junit.testparameterinjector.TestParameterInjector;
64 import java.util.ArrayList;
65 import java.util.List;
66 import java.util.concurrent.ExecutionException;
67 import java.util.concurrent.Executors;
68 import org.junit.Before;
69 import org.junit.Rule;
70 import org.junit.Test;
71 import org.junit.runner.RunWith;
72 import org.mockito.Mock;
73 import org.mockito.junit.MockitoJUnit;
74 import org.mockito.junit.MockitoRule;
75 
76 /**
77  * Integration Tests that relate to {@link MobileDataDownload#downloadFileGroup}.
78  *
79  * <p>NOTE: Any tests related to cancellation should be added to {@link
80  * DownloadFileGroupCancellationIntegrationTest} instead.
81  */
82 @RunWith(TestParameterInjector.class)
83 public class DownloadFileGroupIntegrationTest {
84 
85   private static final String TAG = "DownloadFileGroupIntegrationTest";
86   private static final int MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS = 60;
87   private static final int MAX_MULTI_MDD_API_WAIT_TIME_SECS = 120;
88   private static final long MAX_MDD_API_WAIT_TIME_SECS = 5L;
89 
90   private static final ListeningScheduledExecutorService DOWNLOAD_EXECUTOR =
91       MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(4));
92 
93   private static final String FILE_GROUP_NAME_INSECURE_URL = "test-group-insecure-url";
94   private static final String FILE_GROUP_NAME_MULTIPLE_FILES = "test-group-multiple-files";
95 
96   private static final String FILE_ID_1 = "test-file-1";
97   private static final String FILE_ID_2 = "test-file-2";
98   private static final String FILE_CHECKSUM_1 = "a1cba9d87b1440f41ce9e7da38c43e1f6bd7d5df";
99   private static final String FILE_CHECKSUM_2 = "cb2459d9f1b508993aba36a5ffd942a7e0d49ed6";
100   private static final String FILE_NOT_EXIST_URL =
101       "https://www.gstatic.com/icing/idd/notexist/file.txt";
102 
103   private static final String TEST_DATA_RELATIVE_PATH =
104       "third_party/java_src/android_libs/mobiledatadownload/javatests/com/google/android/libraries/mobiledatadownload/testdata/";
105 
106   private static final String TEST_DATA_URL = "https://test.url/full_file.txt";
107   private static final String TEST_DATA_CHECKSUM = "0c4f1e55c4ec28d0305c5cfde8610b7e6e9f7d9a";
108   private static final int TEST_DATA_BYTE_SIZE = 110;
109 
110   private static final String TEST_DATA_COMPRESS_URL = "https://test.url/full_file.zlib";
111   private static final String TEST_DATA_COMPRESS_CHECKSUM =
112       "cbffcf480fd52a3c6bf9d21206d36f0a714bb97a";
113   private static final int TEST_DATA_COMPRESS_BYTE_SIZE = 92;
114 
115   private static final String VARIANT_1 = "test-variant-1";
116   private static final String VARIANT_2 = "test-variant-2";
117 
118   private static final Account ACCOUNT_1 = AccountUtil.create("account-name-1", "account-type");
119   private static final Account ACCOUNT_2 = AccountUtil.create("account-name-2", "account-type");
120 
121   private static final Context context = ApplicationProvider.getApplicationContext();
122 
123   @Mock private TaskScheduler mockTaskScheduler;
124   @Mock private NetworkUsageMonitor mockNetworkUsageMonitor;
125   @Mock private DownloadProgressMonitor mockDownloadProgressMonitor;
126 
127   private SynchronousFileStorage fileStorage;
128   private ListeningExecutorService controlExecutor;
129 
130   private final TestFlags flags = new TestFlags();
131 
132   @Rule(order = 1)
133   public final MockitoRule mocks = MockitoJUnit.rule();
134 
135   @TestParameter ExecutorType controlExecutorType;
136 
137   @Before
setUp()138   public void setUp() throws Exception {
139 
140     fileStorage =
141         new SynchronousFileStorage(
142             /* backends= */ ImmutableList.of(
143                 AndroidFileBackend.builder(context).build(), new JavaFileBackend()),
144             /* transforms= */ ImmutableList.of(new CompressTransform()),
145             /* monitors= */ ImmutableList.of(mockNetworkUsageMonitor, mockDownloadProgressMonitor));
146 
147     controlExecutor = controlExecutorType.executor();
148   }
149 
150   @Test
downloadAndRead( @estParameter DownloaderConfigurationType downloaderConfigurationType)151   public void downloadAndRead(
152       @TestParameter DownloaderConfigurationType downloaderConfigurationType) throws Exception {
153     Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId());
154     TestFileGroupPopulator testFileGroupPopulator = new TestFileGroupPopulator(context);
155     MobileDataDownload mobileDataDownload =
156         builderForTest()
157             .setInstanceIdOptional(instanceId)
158             .setFileDownloaderSupplier(
159                 downloaderConfigurationType.fileDownloaderSupplier(
160                     context,
161                     controlExecutor,
162                     DOWNLOAD_EXECUTOR,
163                     fileStorage,
164                     flags,
165                     Optional.of(mockDownloadProgressMonitor),
166                     instanceId))
167             .addFileGroupPopulator(testFileGroupPopulator)
168             .build();
169 
170     testFileGroupPopulator
171         .refreshFileGroups(mobileDataDownload)
172         .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
173 
174     mobileDataDownload
175         .downloadFileGroup(
176             DownloadFileGroupRequest.newBuilder()
177                 .setGroupName(FILE_GROUP_NAME)
178                 .setListenerOptional(
179                     Optional.of(
180                         new DownloadListener() {
181                           @Override
182                           public void onProgress(long currentSize) {
183                             Log.i(TAG, "onProgress " + currentSize);
184                           }
185 
186                           @Override
187                           public void onComplete(ClientFileGroup clientFileGroup) {
188                             Log.i(TAG, "onComplete " + clientFileGroup.getGroupName());
189                           }
190                         }))
191                 .build())
192         .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS);
193 
194     ClientFileGroup clientFileGroup =
195         mobileDataDownload
196             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
197             .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
198 
199     assertThat(clientFileGroup).isNotNull();
200     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME);
201     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
202 
203     ClientFile clientFile = clientFileGroup.getFileList().get(0);
204     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID);
205     Uri androidUri = Uri.parse(clientFile.getFileUri());
206     assertThat(fileStorage.fileSize(androidUri)).isEqualTo(FILE_SIZE);
207 
208     mobileDataDownload.clear().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
209 
210     switch (downloaderConfigurationType) {
211       case V2_PLATFORM:
212         // No-op
213     }
214   }
215 
216   @Test
downloadFailed()217   public void downloadFailed() throws Exception {
218     // NOTE: The test failures here are not network stack dependent, so there's
219     // no need to parameterize this test for different network stacks.
220     Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId());
221     MobileDataDownload mobileDataDownload =
222         builderForTest()
223             .setInstanceIdOptional(instanceId)
224             .setFileDownloaderSupplier(
225                 DownloaderConfigurationType.V2_PLATFORM.fileDownloaderSupplier(
226                     context,
227                     controlExecutor,
228                     DOWNLOAD_EXECUTOR,
229                     fileStorage,
230                     flags,
231                     Optional.of(mockDownloadProgressMonitor),
232                     instanceId))
233             .build();
234 
235     // The data file group has a file with insecure url.
236     DataFileGroup groupWithInsecureUrl =
237         TestFileGroupPopulator.createDataFileGroup(
238             FILE_GROUP_NAME_INSECURE_URL,
239             context.getPackageName(),
240             new String[] {FILE_ID},
241             new int[] {FILE_SIZE},
242             new String[] {FILE_CHECKSUM},
243             // Make the url insecure. This would lead to download failure.
244             new String[] {FILE_URL.replace("https", "http")},
245             DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK);
246 
247     // The data file group has a file with non-existent url, and a file with insecure url.
248     DataFileGroup groupWithMultipleFiles =
249         TestFileGroupPopulator.createDataFileGroup(
250             FILE_GROUP_NAME_MULTIPLE_FILES,
251             context.getPackageName(),
252             new String[] {FILE_ID_1, FILE_ID_2},
253             new int[] {FILE_SIZE, FILE_SIZE},
254             new String[] {FILE_CHECKSUM_1, FILE_CHECKSUM_2},
255             // The first file url doesn't exist and the second file url is insecure.
256             new String[] {FILE_NOT_EXIST_URL, FILE_URL.replace("https", "http")},
257             DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK);
258 
259     assertThat(
260             mobileDataDownload
261                 .addFileGroup(
262                     AddFileGroupRequest.newBuilder().setDataFileGroup(groupWithInsecureUrl).build())
263                 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS))
264         .isTrue();
265 
266     assertThat(
267             mobileDataDownload
268                 .addFileGroup(
269                     AddFileGroupRequest.newBuilder()
270                         .setDataFileGroup(groupWithMultipleFiles)
271                         .build())
272                 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS))
273         .isTrue();
274 
275     ExecutionException exception =
276         assertThrows(
277             ExecutionException.class,
278             () ->
279                 mobileDataDownload
280                     .downloadFileGroup(
281                         DownloadFileGroupRequest.newBuilder()
282                             .setGroupName(FILE_GROUP_NAME_INSECURE_URL)
283                             .build())
284                     .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS));
285     assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class);
286     AggregateException cause = (AggregateException) exception.getCause();
287     assertThat(cause).isNotNull();
288     ImmutableList<Throwable> failures = cause.getFailures();
289     assertThat(failures).hasSize(1);
290     assertThat(failures.get(0)).isInstanceOf(DownloadException.class);
291     assertThat(failures.get(0)).hasMessageThat().contains("INSECURE_URL_ERROR");
292 
293     ExecutionException exception2 =
294         assertThrows(
295             ExecutionException.class,
296             () ->
297                 mobileDataDownload
298                     .downloadFileGroup(
299                         DownloadFileGroupRequest.newBuilder()
300                             .setGroupName(FILE_GROUP_NAME_MULTIPLE_FILES)
301                             .build())
302                     .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS));
303     assertThat(exception2).hasCauseThat().isInstanceOf(AggregateException.class);
304     AggregateException cause2 = (AggregateException) exception2.getCause();
305     assertThat(cause2).isNotNull();
306     ImmutableList<Throwable> failures2 = cause2.getFailures();
307     assertThat(failures2).hasSize(2);
308     assertThat(failures2.get(0)).isInstanceOf(DownloadException.class);
309     assertThat(failures2.get(0))
310         .hasCauseThat()
311         .hasMessageThat()
312         .containsMatch("httpStatusCode=404");
313     assertThat(failures2.get(1)).isInstanceOf(DownloadException.class);
314     assertThat(failures2.get(1)).hasMessageThat().contains("INSECURE_URL_ERROR");
315 
316     AggregateException exception3 =
317         assertThrows(
318             AggregateException.class,
319             () -> {
320               try {
321                 ListenableFuture<ClientFileGroup> downloadFuture1 =
322                     mobileDataDownload.downloadFileGroup(
323                         DownloadFileGroupRequest.newBuilder()
324                             .setGroupName(FILE_GROUP_NAME_MULTIPLE_FILES)
325                             .build());
326                 ListenableFuture<ClientFileGroup> downloadFuture2 =
327                     mobileDataDownload.downloadFileGroup(
328                         DownloadFileGroupRequest.newBuilder()
329                             .setGroupName(FILE_GROUP_NAME_INSECURE_URL)
330                             .build());
331 
332                 Futures.successfulAsList(downloadFuture1, downloadFuture2)
333                     .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS);
334 
335                 AggregateException.throwIfFailed(
336                     ImmutableList.of(downloadFuture1, downloadFuture2),
337                     "Expected download failures");
338               } catch (ExecutionException e) {
339                 throw e;
340               }
341             });
342     assertThat(exception3.getFailures()).hasSize(2);
343   }
344 
345   @Test
removePartialDownloadThenDownloadAgain( @estParameter DownloaderConfigurationType downloaderConfigurationType)346   public void removePartialDownloadThenDownloadAgain(
347       @TestParameter DownloaderConfigurationType downloaderConfigurationType) throws Exception {
348     Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId());
349 
350     Supplier<FileDownloader> fileDownloaderSupplier =
351         downloaderConfigurationType.fileDownloaderSupplier(
352             context,
353             controlExecutor,
354             DOWNLOAD_EXECUTOR,
355             fileStorage,
356             flags,
357             Optional.of(mockDownloadProgressMonitor),
358             instanceId);
359     BlockingFileDownloader blockingFileDownloader =
360         new BlockingFileDownloader(DOWNLOAD_EXECUTOR, fileDownloaderSupplier.get());
361 
362     MobileDataDownload mobileDataDownload =
363         builderForTest()
364             .setInstanceIdOptional(instanceId)
365             .setFileDownloaderSupplier(() -> blockingFileDownloader)
366             .build();
367 
368     mobileDataDownload.clear().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
369 
370     // Add the filegroup, start downloading, then cancel while in progress.
371     TestFileGroupPopulator testFileGroupPopulator = new TestFileGroupPopulator(context);
372     testFileGroupPopulator
373         .refreshFileGroups(mobileDataDownload)
374         .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
375 
376     ListenableFuture<ClientFileGroup> downloadFuture =
377         mobileDataDownload.downloadFileGroup(
378             DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build());
379 
380     blockingFileDownloader.finishDownloading(); // Unblocks blockingFileDownloader
381     blockingFileDownloader.waitForDelegateStarted(); // Waits until offroadDownloader starts
382 
383     // NOTE: add a little wait to allow Downloader's listeners to run.
384     Thread.sleep(/* millis= */ 200);
385 
386     downloadFuture.cancel(true /* may interrupt */);
387 
388     // NOTE: add a little wait to allow Downloader's listeners to run.
389     Thread.sleep(/* millis= */ 200);
390 
391     // Remove the filegroup.
392     ListenableFuture<Boolean> removeFuture =
393         mobileDataDownload.removeFileGroup(
394             RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build());
395     removeFuture.get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
396 
397     // Add then try to download again.
398     blockingFileDownloader.resetState();
399     blockingFileDownloader.finishDownloading(); // Unblocks blockingFileDownloader
400 
401     testFileGroupPopulator
402         .refreshFileGroups(mobileDataDownload)
403         .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
404 
405     downloadFuture =
406         mobileDataDownload.downloadFileGroup(
407             DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build());
408 
409     downloadFuture.get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS);
410 
411     // The file should have downloaded as expected.
412     ClientFileGroup clientFileGroup =
413         mobileDataDownload
414             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build())
415             .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
416 
417     assertThat(clientFileGroup).isNotNull();
418     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
419     Uri androidUri = Uri.parse(clientFileGroup.getFileList().get(0).getFileUri());
420     assertThat(fileStorage.fileSize(androidUri)).isEqualTo(FILE_SIZE);
421   }
422 
423   @Test
downloadDifferentGroupsWithSameFileTest()424   public void downloadDifferentGroupsWithSameFileTest() throws Exception {
425     Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId());
426     MobileDataDownload mobileDataDownload =
427         builderForTest()
428             .setInstanceIdOptional(instanceId)
429             .setFileDownloaderSupplier(
430                 () ->
431                     new TestFileDownloader(TEST_DATA_RELATIVE_PATH, fileStorage, DOWNLOAD_EXECUTOR))
432             .build();
433 
434     DataFile.Builder dataFileBuilder =
435         DataFile.newBuilder()
436             .setUrlToDownload(TEST_DATA_URL)
437             .setChecksum(TEST_DATA_CHECKSUM)
438             .setByteSize(TEST_DATA_BYTE_SIZE);
439     DataFileGroup.Builder groupBuilder = DataFileGroup.newBuilder();
440 
441     // Add all groups concurrently
442     ArrayList<ListenableFuture<Boolean>> addFutures = new ArrayList<>();
443     for (int i = 0; i < 50; i++) {
444       String groupName = String.format("group%d", i);
445       String fileId = String.format("group%d_file", i);
446 
447       DataFile file = dataFileBuilder.setFileId(fileId).build();
448       DataFileGroup group =
449           DataFileGroup.newBuilder().setGroupName(groupName).addFile(file).build();
450 
451       addFutures.add(
452           mobileDataDownload.addFileGroup(
453               AddFileGroupRequest.newBuilder().setDataFileGroup(group).build()));
454     }
455     Futures.allAsList(addFutures).get(MAX_MULTI_MDD_API_WAIT_TIME_SECS, SECONDS);
456 
457     // Start all downloads concurrently
458     ArrayList<ListenableFuture<ClientFileGroup>> downloadFutures = new ArrayList<>();
459     for (int i = 0; i < 50; i++) {
460       String groupName = String.format("group%d", i);
461 
462       downloadFutures.add(
463           mobileDataDownload.downloadFileGroup(
464               DownloadFileGroupRequest.newBuilder().setGroupName(groupName).build()));
465     }
466     List<ClientFileGroup> groups =
467         Futures.allAsList(downloadFutures).get(MAX_MULTI_MDD_API_WAIT_TIME_SECS, SECONDS);
468 
469     assertThat(groups).doesNotContain(null);
470   }
471 
472   @Test
concurrentDownloads_withSameFile_withDifferentDownloadTransforms_completes( @estParameter boolean enableDedupByFileKey)473   public void concurrentDownloads_withSameFile_withDifferentDownloadTransforms_completes(
474       @TestParameter boolean enableDedupByFileKey) throws Exception {
475     flags.enableFileDownloadDedupByFileKey = Optional.of(enableDedupByFileKey);
476 
477     Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId());
478     MobileDataDownload mobileDataDownload =
479         builderForTest()
480             .setInstanceIdOptional(instanceId)
481             .setFileDownloaderSupplier(
482                 () ->
483                     new TestFileDownloader(TEST_DATA_RELATIVE_PATH, fileStorage, DOWNLOAD_EXECUTOR))
484             .build();
485 
486     // Create two groups which share the same file, but have different download transforms
487     DataFileGroup groupWithoutTransform =
488         DataFileGroup.newBuilder()
489             .setGroupName("groupWithoutTransform")
490             .addFile(
491                 DataFile.newBuilder()
492                     .setFileId("file_no_transform")
493                     .setUrlToDownload(TEST_DATA_URL)
494                     .setChecksum(TEST_DATA_CHECKSUM)
495                     .setByteSize(TEST_DATA_BYTE_SIZE))
496             .build();
497 
498     DataFileGroup groupWithTransform =
499         DataFileGroup.newBuilder()
500             .setGroupName("groupWithTransform")
501             .addFile(
502                 DataFile.newBuilder()
503                     .setFileId("file_no_transform")
504                     .setUrlToDownload(TEST_DATA_COMPRESS_URL)
505                     .setChecksum(TEST_DATA_CHECKSUM)
506                     .setByteSize(TEST_DATA_BYTE_SIZE)
507                     .setDownloadedFileChecksum(TEST_DATA_COMPRESS_CHECKSUM)
508                     .setDownloadedFileByteSize(TEST_DATA_COMPRESS_BYTE_SIZE)
509                     .setDownloadTransforms(
510                         Transforms.newBuilder()
511                             .addTransform(
512                                 Transform.newBuilder()
513                                     .setCompress(
514                                         TransformProto.CompressTransform.getDefaultInstance())
515                                     .build())
516                             .build())
517                     .build())
518             .build();
519 
520     // Add both groups, then attempt to download both concurrently
521     mobileDataDownload
522         .addFileGroup(
523             AddFileGroupRequest.newBuilder().setDataFileGroup(groupWithoutTransform).build())
524         .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
525     mobileDataDownload
526         .addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(groupWithTransform).build())
527         .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS);
528 
529     ListenableFuture<ClientFileGroup> downloadWithoutTransform =
530         mobileDataDownload.downloadFileGroup(
531             DownloadFileGroupRequest.newBuilder().setGroupName("groupWithoutTransform").build());
532     ListenableFuture<ClientFileGroup> downloadWithTransform =
533         mobileDataDownload.downloadFileGroup(
534             DownloadFileGroupRequest.newBuilder().setGroupName("groupWithTransform").build());
535 
536     List<ClientFileGroup> downloadedGroups =
537         Futures.allAsList(ImmutableList.of(downloadWithoutTransform, downloadWithTransform))
538             .get(MAX_MULTI_MDD_API_WAIT_TIME_SECS, SECONDS);
539 
540     // Both groups are downloaded and both files point to the same on-device uri.
541     assertThat(downloadedGroups).doesNotContain(null);
542     assertThat(downloadedGroups.get(0).getFile(0).getFileUri())
543         .isEqualTo(downloadedGroups.get(1).getFile(0).getFileUri());
544   }
545 
546   /**
547    * Returns MDD Builder with common dependencies set -- additional dependencies are added in each
548    * test as needed.
549    */
builderForTest()550   private MobileDataDownloadBuilder builderForTest() {
551 
552     return MobileDataDownloadBuilder.newBuilder()
553         .setContext(context)
554         .setControlExecutor(controlExecutor)
555         .setFileStorage(fileStorage)
556         .setTaskScheduler(Optional.of(mockTaskScheduler))
557         .setDeltaDecoderOptional(Optional.absent())
558         .setNetworkUsageMonitor(mockNetworkUsageMonitor)
559         .setDownloadMonitorOptional(Optional.of(mockDownloadProgressMonitor))
560         .setFlagsOptional(Optional.of(flags));
561   }
562 }
563