• 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.DownloadException.DownloadResultCode.ANDROID_DOWNLOADER_UNKNOWN;
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertThrows;
21 import static org.junit.Assert.assertTrue;
22 import static org.mockito.ArgumentMatchers.any;
23 import static org.mockito.Mockito.times;
24 import static org.mockito.Mockito.verify;
25 import static org.mockito.Mockito.when;
26 
27 import android.content.Context;
28 import android.net.Uri;
29 import android.util.Log;
30 import androidx.test.core.app.ApplicationProvider;
31 import com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints;
32 import com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest;
33 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
34 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
35 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
36 import com.google.android.libraries.mobiledatadownload.foreground.ForegroundDownloadKey;
37 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil;
38 import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor;
39 import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor;
40 import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader;
41 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
42 import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
43 import com.google.common.base.Optional;
44 import com.google.common.base.Supplier;
45 import com.google.common.base.Suppliers;
46 import com.google.common.collect.ImmutableList;
47 import com.google.common.labs.concurrent.LabsFutures;
48 import com.google.common.util.concurrent.Futures;
49 import com.google.common.util.concurrent.ListenableFuture;
50 import com.google.common.util.concurrent.ListeningExecutorService;
51 import com.google.common.util.concurrent.MoreExecutors;
52 import java.util.concurrent.ExecutionException;
53 import java.util.concurrent.Executors;
54 import org.junit.After;
55 import org.junit.Before;
56 import org.junit.Rule;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 import org.mockito.ArgumentCaptor;
60 import org.mockito.Captor;
61 import org.mockito.Mock;
62 import org.mockito.junit.MockitoJUnit;
63 import org.mockito.junit.MockitoRule;
64 import org.robolectric.RobolectricTestRunner;
65 import org.robolectric.shadows.ShadowLog;
66 
67 @RunWith(RobolectricTestRunner.class)
68 public final class DownloadFileTest {
69   @Rule public final MockitoRule mocks = MockitoJUnit.rule();
70 
71   // 1MB file.
72   private static final String FILE_URL =
73       "https://www.gstatic.com/icing/idd/sample_group/sample_file_3_1519240701";
74   private static final Uri DESTINATION_FILE_URI =
75       Uri.parse(
76           "android://com.google.android.libraries.mobiledatadownload/files/datadownload/shared/public/file_1");
77 
78   private static final Context context = ApplicationProvider.getApplicationContext();
79 
80   private final TestFlags flags = new TestFlags();
81   private final FakeTimeSource clock = new FakeTimeSource();
82 
83   private DownloadProgressMonitor downloadProgressMonitor;
84   private SynchronousFileStorage fileStorage;
85 
86   @Mock private SingleFileDownloadListener mockDownloadListener;
87   @Mock private NetworkUsageMonitor mockNetworkUsageMonitor;
88   @Mock private FileDownloader mockFileDownloader;
89   @Mock private DownloadProgressMonitor mockDownloadMonitor;
90 
91   private BlockingFileDownloader blockingFileDownloader;
92   private SingleFileDownloadRequest singleFileDownloadRequest;
93   private MobileDataDownload mobileDataDownload;
94 
95   @Captor ArgumentCaptor<DownloadListener> downloadListenerCaptor;
96 
97   @Captor
98   ArgumentCaptor<com.google.android.libraries.mobiledatadownload.lite.DownloadListener>
99       liteDownloadListenerCaptor;
100 
101   @Captor ArgumentCaptor<DownloadRequest> singleFileDownloadRequestCaptor;
102 
103   ListeningExecutorService controlExecutor =
104       MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
105 
106   @Before
setUp()107   public void setUp() {
108     ShadowLog.setLoggable(LogUtil.TAG, Log.DEBUG);
109 
110     downloadProgressMonitor = new DownloadProgressMonitor(clock, controlExecutor);
111 
112     fileStorage =
113         new SynchronousFileStorage(
114             /* backends= */ ImmutableList.of(AndroidFileBackend.builder(context).build()),
115             /* transforms= */ ImmutableList.of(),
116             /* monitors= */ ImmutableList.of(downloadProgressMonitor));
117 
118     singleFileDownloadRequest =
119         SingleFileDownloadRequest.newBuilder()
120             .setDestinationFileUri(DESTINATION_FILE_URI)
121             .setUrlToDownload(FILE_URL)
122             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
123             .setNotificationContentTitle("File url: " + FILE_URL)
124             .build();
125 
126     blockingFileDownloader = new BlockingFileDownloader(controlExecutor);
127 
128     when(mockDownloadListener.onComplete()).thenReturn(Futures.immediateVoidFuture());
129     when(mockFileDownloader.startDownloading(singleFileDownloadRequestCaptor.capture()))
130         .thenReturn(Futures.immediateVoidFuture());
131   }
132 
133   @After
tearDown()134   public void tearDown() {
135     // Reset state of blockingFileDownloader to prevent deadlocks
136     blockingFileDownloader.resetState();
137   }
138 
139   @Test
downloadFile_whenRequestAlreadyMade_dedups()140   public void downloadFile_whenRequestAlreadyMade_dedups() throws Exception {
141     // Use BlockingFileDownloader to ensure first download is in progress.
142     mobileDataDownload = getMobileDataDownload(() -> blockingFileDownloader);
143 
144     ListenableFuture<Void> downloadFuture1 =
145         mobileDataDownload.downloadFile(singleFileDownloadRequest);
146     ListenableFuture<Void> downloadFuture2 =
147         mobileDataDownload.downloadFile(singleFileDownloadRequest);
148 
149     // Allow blocking download to finish
150     blockingFileDownloader.finishDownloading();
151 
152     // Finish future 2 and assert that future 1 has completed as well
153     downloadFuture2.get();
154 
155     awaitAllExecutorsIdle();
156 
157     assertThat(downloadFuture1.isDone()).isTrue();
158   }
159 
160   @Test
downloadFile_whenRequestAlreadyMadeUsingForegroundService_dedups()161   public void downloadFile_whenRequestAlreadyMadeUsingForegroundService_dedups() throws Exception {
162     // Use BlockingFileDownloader to ensure first download is in progress.
163     mobileDataDownload = getMobileDataDownload(() -> blockingFileDownloader);
164 
165     ListenableFuture<Void> downloadFuture1 =
166         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
167     ListenableFuture<Void> downloadFuture2 =
168         mobileDataDownload.downloadFile(singleFileDownloadRequest);
169 
170     // Allow blocking download to finish
171     blockingFileDownloader.finishDownloading();
172 
173     // Finish future 2 and assert that future 1 has completed as well
174     downloadFuture2.get();
175 
176     awaitAllExecutorsIdle();
177     assertThat(downloadFuture1.isDone()).isTrue();
178   }
179 
180   @Test
downloadFile_beginsDownload()181   public void downloadFile_beginsDownload() throws Exception {
182     mobileDataDownload =
183         getMobileDataDownload(
184             () -> mockFileDownloader,
185             /* foregroundDownloadServiceClassOptional= */ Optional.absent(),
186             Optional.of(mockDownloadMonitor));
187 
188     singleFileDownloadRequest =
189         SingleFileDownloadRequest.newBuilder()
190             .setDestinationFileUri(DESTINATION_FILE_URI)
191             .setUrlToDownload(FILE_URL)
192             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
193             .setListenerOptional(Optional.of(mockDownloadListener))
194             .build();
195 
196     ListenableFuture<Void> downloadFuture =
197         mobileDataDownload.downloadFile(singleFileDownloadRequest);
198     downloadFuture.get();
199 
200     awaitAllExecutorsIdle();
201 
202     // Verify that correct DownloadRequest is sent to underlying FileDownloader
203     DownloadRequest actualDownloadRequest = singleFileDownloadRequestCaptor.getValue();
204     assertThat(actualDownloadRequest.fileUri()).isEqualTo(DESTINATION_FILE_URI);
205     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
206     assertThat(actualDownloadRequest.downloadConstraints())
207         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
208 
209     // Verify that downloadMonitor adds the listener
210     verify(mockDownloadMonitor).addDownloadListener(any(), liteDownloadListenerCaptor.capture());
211     verify(mockFileDownloader).startDownloading(any());
212 
213     verify(mockDownloadMonitor).removeDownloadListener(DESTINATION_FILE_URI);
214     verify(mockDownloadListener).onComplete();
215 
216     // Ensure that given download listener is the same one passed to download monitor
217     com.google.android.libraries.mobiledatadownload.lite.DownloadListener capturedDownloadListener =
218         liteDownloadListenerCaptor.getValue();
219     DownloadException testException =
220         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
221 
222     capturedDownloadListener.onProgress(10);
223     capturedDownloadListener.onFailure(testException);
224     capturedDownloadListener.onPausedForConnectivity();
225 
226     verify(mockDownloadListener).onProgress(10);
227     verify(mockDownloadListener).onFailure(testException);
228     verify(mockDownloadListener).onPausedForConnectivity();
229   }
230 
231   @Test
download_whenListenerProvided_handlesOnCompleteFailed()232   public void download_whenListenerProvided_handlesOnCompleteFailed() throws Exception {
233     Exception failureException = new Exception("test failure");
234     when(mockDownloadListener.onComplete())
235         .thenReturn(Futures.immediateFailedFuture(failureException));
236     mobileDataDownload =
237         getMobileDataDownload(
238             createSuccessfulFileDownloaderSupplier(),
239             /* foregroundDownloadServiceClassOptional= */ Optional.absent(),
240             Optional.of(downloadProgressMonitor));
241 
242     singleFileDownloadRequest =
243         SingleFileDownloadRequest.newBuilder()
244             .setDestinationFileUri(DESTINATION_FILE_URI)
245             .setUrlToDownload(FILE_URL)
246             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
247             .setListenerOptional(Optional.of(mockDownloadListener))
248             .build();
249 
250     mobileDataDownload.downloadFile(singleFileDownloadRequest).get();
251 
252     awaitAllExecutorsIdle();
253 
254     // Verify the DownloadListeners onComplete was invoked
255     verify(mockDownloadListener).onComplete();
256   }
257 
258   @Test
downloadFile_whenDownloadFails_reportsFailure()259   public void downloadFile_whenDownloadFails_reportsFailure() throws Exception {
260     DownloadException downloadException =
261         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
262 
263     mobileDataDownload =
264         getMobileDataDownload(
265             createFailingFileDownloaderSupplier(downloadException),
266             /* foregroundDownloadServiceClassOptional= */ Optional.absent(),
267             Optional.of(downloadProgressMonitor));
268 
269     singleFileDownloadRequest =
270         SingleFileDownloadRequest.newBuilder()
271             .setDestinationFileUri(DESTINATION_FILE_URI)
272             .setUrlToDownload(FILE_URL)
273             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
274             .setListenerOptional(Optional.of(mockDownloadListener))
275             .build();
276 
277     ListenableFuture<Void> downloadFuture =
278         mobileDataDownload.downloadFile(singleFileDownloadRequest);
279     assertThrows(ExecutionException.class, downloadFuture::get);
280     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
281     assertThat(e.getDownloadResultCode()).isEqualTo(ANDROID_DOWNLOADER_UNKNOWN);
282 
283     // Verify that DownloadListener.onFailure was invoked with failure
284     verify(mockDownloadListener).onFailure(downloadException);
285     verify(mockDownloadListener, times(0)).onComplete();
286   }
287 
288   @Test
downloadFile_whenReturnedFutureIsCanceled_cancelsDownload()289   public void downloadFile_whenReturnedFutureIsCanceled_cancelsDownload() throws Exception {
290     // Wrap mock around BlockingFileDownloader to simulate long download
291     mobileDataDownload = getMobileDataDownload(() -> blockingFileDownloader);
292 
293     ListenableFuture<Void> downloadFuture =
294         mobileDataDownload.downloadFile(singleFileDownloadRequest);
295 
296     // Wait for download to start and confirm download future is still running.
297     blockingFileDownloader.waitForDownloadStarted();
298     assertThat(downloadFuture.isDone()).isFalse();
299 
300     // Cancel download future.
301     downloadFuture.cancel(true);
302 
303     // Check that future is now cancelled.
304     assertThat(downloadFuture.isCancelled()).isTrue();
305   }
306 
307   @Test
downloadFile_whenMonitorNotProvided_whenDownloadFails_reportsFailure()308   public void downloadFile_whenMonitorNotProvided_whenDownloadFails_reportsFailure()
309       throws Exception {
310     DownloadException downloadException =
311         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
312 
313     mobileDataDownload =
314         getMobileDataDownload(createFailingFileDownloaderSupplier(downloadException));
315 
316     singleFileDownloadRequest =
317         SingleFileDownloadRequest.newBuilder()
318             .setDestinationFileUri(DESTINATION_FILE_URI)
319             .setUrlToDownload(FILE_URL)
320             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
321             .setListenerOptional(Optional.of(mockDownloadListener))
322             .build();
323 
324     ListenableFuture<Void> downloadFuture =
325         mobileDataDownload.downloadFile(singleFileDownloadRequest);
326     assertThrows(ExecutionException.class, downloadFuture::get);
327     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
328     assertThat(e.getDownloadResultCode()).isEqualTo(ANDROID_DOWNLOADER_UNKNOWN);
329 
330     // Verify that DownloadListener.onFailure was invoked with failure
331     verify(mockDownloadListener).onFailure(downloadException);
332     verify(mockDownloadListener, times(0)).onComplete();
333   }
334 
335   @Test
downloadFileWithWithForegroundService_requiresForegroundDownloadService()336   public void downloadFileWithWithForegroundService_requiresForegroundDownloadService()
337       throws Exception {
338     // Create downloader without providing foreground service
339     mobileDataDownload =
340         getMobileDataDownload(
341             () -> mockFileDownloader,
342             /* foregroundDownloadServiceClassOptional= */ Optional.absent(),
343             Optional.of(downloadProgressMonitor));
344 
345     // Without foreground service, download call should fail with IllegalStateException
346     ListenableFuture<Void> downloadFuture =
347         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
348     ExecutionException e = assertThrows(ExecutionException.class, downloadFuture::get);
349     assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
350 
351     // Verify that underlying download is not started
352     verify(mockFileDownloader, times(0)).startDownloading(any());
353   }
354 
355   @Test
downloadFileWithForegroundService_requiresDownloadMonitor()356   public void downloadFileWithForegroundService_requiresDownloadMonitor() throws Exception {
357     // Create downloader without providing DownloadMonitor
358     mobileDataDownload =
359         getMobileDataDownload(
360             () -> mockFileDownloader,
361             Optional.of(this.getClass()),
362             /* downloadProgressMonitorOptional= */ Optional.absent());
363 
364     // Without monitor, download call should fail with IllegalStateException
365     ListenableFuture<Void> downloadFuture =
366         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
367     ExecutionException e = assertThrows(ExecutionException.class, downloadFuture::get);
368     assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
369 
370     // Verify that underlying download is not started
371     verify(mockFileDownloader, times(0)).startDownloading(any());
372   }
373 
374   @Test
downloadFileWithForegroundService_whenRequestAlreadyMade_dedups()375   public void downloadFileWithForegroundService_whenRequestAlreadyMade_dedups() throws Exception {
376     // Use BlockingFileDownloader to control when the download will finish.
377     mobileDataDownload = getMobileDataDownload(() -> blockingFileDownloader);
378 
379     ListenableFuture<Void> downloadFuture1 =
380         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
381     ListenableFuture<Void> downloadFuture2 =
382         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
383 
384     // Now we let the 2 futures downloadFuture1 downloadFuture2 to run by opening the latch.
385     blockingFileDownloader.finishDownloading();
386 
387     // Now finish future 2, future 1 should finish too and the cache clears the future.
388     downloadFuture2.get();
389 
390     awaitAllExecutorsIdle();
391 
392     assertThat(downloadFuture1.isDone()).isTrue();
393   }
394 
395   @Test
396   public void
downloadFileWithForegroundService_whenRequestAlreadyMadeWithoutForegroundService_dedups()397       downloadFileWithForegroundService_whenRequestAlreadyMadeWithoutForegroundService_dedups()
398           throws Exception {
399     // Use BlockingFileDownloader to control when the download will finish.
400     mobileDataDownload =
401         getMobileDataDownload(
402             () -> blockingFileDownloader,
403             Optional.of(this.getClass()),
404             Optional.of(downloadProgressMonitor));
405 
406     ListenableFuture<Void> downloadFuture1 =
407         mobileDataDownload.downloadFile(singleFileDownloadRequest);
408     ListenableFuture<Void> downloadFuture2 =
409         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
410 
411     // Now we let the 2 futures downloadFuture1 downloadFuture2 to run by opening the latch.
412     blockingFileDownloader.finishDownloading();
413 
414     // Now finish future 2, future 1 should finish too and the cache clears the future.
415     downloadFuture2.get();
416 
417     awaitAllExecutorsIdle();
418 
419     assertThat(downloadFuture1.isDone()).isTrue();
420   }
421 
422   @Test
downloadFileWithForegroundService()423   public void downloadFileWithForegroundService() throws Exception {
424     when(mockFileDownloader.startDownloading(singleFileDownloadRequestCaptor.capture()))
425         .thenReturn(Futures.immediateVoidFuture());
426     mobileDataDownload =
427         getMobileDataDownload(
428             () -> mockFileDownloader,
429             Optional.of(this.getClass()),
430             Optional.of(mockDownloadMonitor));
431 
432     singleFileDownloadRequest =
433         SingleFileDownloadRequest.newBuilder()
434             .setDestinationFileUri(DESTINATION_FILE_URI)
435             .setUrlToDownload(FILE_URL)
436             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
437             .setNotificationContentTitle("File url: " + FILE_URL)
438             .setListenerOptional(Optional.of(mockDownloadListener))
439             .build();
440 
441     ListenableFuture<Void> downloadFuture =
442         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
443     downloadFuture.get();
444 
445     awaitAllExecutorsIdle();
446 
447     // Verify that the correct DownloadRequest is sent to underderlying FileDownloader.
448     DownloadRequest actualDownloadRequest = singleFileDownloadRequestCaptor.getValue();
449     assertThat(actualDownloadRequest.fileUri()).isEqualTo(DESTINATION_FILE_URI);
450     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
451     assertThat(actualDownloadRequest.downloadConstraints())
452         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
453 
454     // Verify that downloadMonitor will add a DownloadListener.
455     verify(mockDownloadMonitor).addDownloadListener(any(), liteDownloadListenerCaptor.capture());
456     verify(mockFileDownloader).startDownloading(any());
457 
458     verify(mockDownloadMonitor).removeDownloadListener(DESTINATION_FILE_URI);
459 
460     verify(mockDownloadListener).onComplete();
461 
462     com.google.android.libraries.mobiledatadownload.lite.DownloadListener capturedListener =
463         liteDownloadListenerCaptor.getValue();
464 
465     // Now simulate other DownloadListener's callbacks:
466     capturedListener.onProgress(10);
467     capturedListener.onPausedForConnectivity();
468     DownloadException downloadException =
469         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
470     capturedListener.onFailure(downloadException);
471 
472     awaitAllExecutorsIdle();
473 
474     verify(mockDownloadListener).onProgress(10);
475     verify(mockDownloadListener).onPausedForConnectivity();
476     verify(mockDownloadListener).onFailure(downloadException);
477   }
478 
479   @Test
downloadFileWithForegroundService_clientOnCompleteFailed()480   public void downloadFileWithForegroundService_clientOnCompleteFailed() throws Exception {
481     Exception failureException = new Exception("test failure");
482 
483     when(mockFileDownloader.startDownloading(singleFileDownloadRequestCaptor.capture()))
484         .thenReturn(Futures.immediateVoidFuture());
485     mobileDataDownload =
486         getMobileDataDownload(
487             () -> mockFileDownloader,
488             Optional.of(this.getClass()),
489             Optional.of(downloadProgressMonitor));
490 
491     // Client's provided DownloadListener.onComplete failed.
492     when(mockDownloadListener.onComplete())
493         .thenReturn(Futures.immediateFailedFuture(failureException));
494 
495     singleFileDownloadRequest =
496         SingleFileDownloadRequest.newBuilder()
497             .setDestinationFileUri(DESTINATION_FILE_URI)
498             .setUrlToDownload(FILE_URL)
499             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
500             .setNotificationContentTitle("File url: " + FILE_URL)
501             .setListenerOptional(Optional.of(mockDownloadListener))
502             .build();
503 
504     ListenableFuture<Void> downloadFuture =
505         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
506     downloadFuture.get();
507 
508     awaitAllExecutorsIdle();
509 
510     // Verify that the correct DownloadRequest is sent to underderlying FileDownloader.
511     DownloadRequest actualDownloadRequest = singleFileDownloadRequestCaptor.getValue();
512     assertThat(actualDownloadRequest.fileUri()).isEqualTo(DESTINATION_FILE_URI);
513     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
514     assertThat(actualDownloadRequest.downloadConstraints())
515         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
516 
517     verify(mockFileDownloader).startDownloading(any());
518     verify(mockDownloadListener).onComplete();
519   }
520 
521   @Test
downloadFileWithForegroundService_failure()522   public void downloadFileWithForegroundService_failure() throws Exception {
523     DownloadException downloadException =
524         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
525 
526     when(mockFileDownloader.startDownloading(singleFileDownloadRequestCaptor.capture()))
527         .thenReturn(Futures.immediateFailedFuture(downloadException));
528 
529     mobileDataDownload =
530         getMobileDataDownload(
531             () -> mockFileDownloader,
532             Optional.of(this.getClass()),
533             Optional.of(downloadProgressMonitor));
534 
535     singleFileDownloadRequest =
536         SingleFileDownloadRequest.newBuilder()
537             .setDestinationFileUri(DESTINATION_FILE_URI)
538             .setUrlToDownload(FILE_URL)
539             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
540             .setNotificationContentTitle("File url: " + FILE_URL)
541             .setListenerOptional(Optional.of(mockDownloadListener))
542             .build();
543 
544     ListenableFuture<Void> downloadFuture =
545         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
546     assertThrows(ExecutionException.class, downloadFuture::get);
547     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
548     assertThat(e.getDownloadResultCode()).isEqualTo(ANDROID_DOWNLOADER_UNKNOWN);
549 
550     awaitAllExecutorsIdle();
551 
552     // Verify that the correct DownloadRequest is sent to underderlying FileDownloader.
553     DownloadRequest actualDownloadRequest = singleFileDownloadRequestCaptor.getValue();
554     assertThat(actualDownloadRequest.fileUri()).isEqualTo(DESTINATION_FILE_URI);
555     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
556     assertThat(actualDownloadRequest.downloadConstraints())
557         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
558 
559     // Since the download failed, onComplete will not be called but onFailure.
560     verify(mockDownloadListener, times(0)).onComplete();
561     verify(mockDownloadListener).onFailure(downloadException);
562   }
563 
564   @Test
cancelDownloadFileWithForegroundService()565   public void cancelDownloadFileWithForegroundService() throws Exception {
566     // Use BlockingFileDownloader to control when the download will finish.
567     mobileDataDownload = getMobileDataDownload(() -> blockingFileDownloader);
568 
569     ForegroundDownloadKey foregroundDownloadKey =
570         ForegroundDownloadKey.ofSingleFile(DESTINATION_FILE_URI);
571 
572     ListenableFuture<Void> downloadFuture =
573         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
574 
575     blockingFileDownloader.waitForDownloadStarted();
576 
577     mobileDataDownload.cancelForegroundDownload(foregroundDownloadKey.toString());
578 
579     awaitAllExecutorsIdle();
580 
581     assertTrue(downloadFuture.isCancelled());
582   }
583 
584   @Test
cancelListenableFuture()585   public void cancelListenableFuture() throws Exception {
586     // Use BlockingFileDownloader to control when the download will finish.
587     mobileDataDownload = getMobileDataDownload(() -> blockingFileDownloader);
588 
589     ListenableFuture<Void> downloadFuture =
590         mobileDataDownload.downloadFileWithForegroundService(singleFileDownloadRequest);
591 
592     // Wait for download to start and confirm download future is still running.
593     blockingFileDownloader.waitForDownloadStarted();
594     assertThat(downloadFuture.isDone()).isFalse();
595 
596     // Cancel download future.
597     downloadFuture.cancel(true);
598 
599     // Check that future is now cancelled.
600     assertThat(downloadFuture.isCancelled()).isTrue();
601   }
602 
createFailingFileDownloaderSupplier(Throwable throwable)603   private Supplier<FileDownloader> createFailingFileDownloaderSupplier(Throwable throwable) {
604     return Suppliers.ofInstance(
605         new FileDownloader() {
606           @Override
607           public ListenableFuture<Void> startDownloading(DownloadRequest request) {
608             return Futures.immediateFailedFuture(throwable);
609           }
610         });
611   }
612 
613   private Supplier<FileDownloader> createSuccessfulFileDownloaderSupplier() {
614     return Suppliers.ofInstance(
615         new FileDownloader() {
616           @Override
617           public ListenableFuture<Void> startDownloading(DownloadRequest request) {
618             return Futures.immediateVoidFuture();
619           }
620         });
621   }
622 
623   private MobileDataDownload getMobileDataDownload(
624       Supplier<FileDownloader> fileDownloaderSupplier) {
625     return getMobileDataDownload(
626         fileDownloaderSupplier, Optional.of(this.getClass()), Optional.of(downloadProgressMonitor));
627   }
628 
629   private MobileDataDownload getMobileDataDownload(
630       Supplier<FileDownloader> fileDownloaderSupplier,
631       Optional<Class<?>> foregroundDownloadServiceClassOptional,
632       Optional<DownloadProgressMonitor> downloadProgressMonitorOptional) {
633     return MobileDataDownloadBuilder.newBuilder()
634         .setContext(context)
635         .setControlExecutor(controlExecutor)
636         .setFileDownloaderSupplier(fileDownloaderSupplier)
637         .setFileStorage(fileStorage)
638         .setDownloadMonitorOptional(downloadProgressMonitorOptional)
639         .setNetworkUsageMonitor(mockNetworkUsageMonitor)
640         .setForegroundDownloadServiceOptional(foregroundDownloadServiceClassOptional)
641         .setFlagsOptional(Optional.of(flags))
642         .build();
643   }
644 
645   /** Waits long enough for async operations to finish running. */
646   // TODO(b/217551873): investigate ways to make this more robust
647   private static void awaitAllExecutorsIdle() throws Exception {
648     Thread.sleep(/* millis= */ 100);
649   }
650 }
651