• 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.lite;
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.ArgumentMatchers.eq;
24 import static org.mockito.Mockito.times;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
27 
28 import android.content.Context;
29 import android.net.Uri;
30 import androidx.test.core.app.ApplicationProvider;
31 import com.google.android.libraries.mobiledatadownload.DownloadException;
32 import com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints;
33 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
34 import com.google.android.libraries.mobiledatadownload.foreground.ForegroundDownloadKey;
35 import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader;
36 import com.google.common.base.Optional;
37 import com.google.common.base.Supplier;
38 import com.google.common.labs.concurrent.LabsFutures;
39 import com.google.common.util.concurrent.Futures;
40 import com.google.common.util.concurrent.ListenableFuture;
41 import com.google.common.util.concurrent.ListeningExecutorService;
42 import com.google.common.util.concurrent.MoreExecutors;
43 import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.Executor;
46 import java.util.concurrent.Executors;
47 import java.util.concurrent.ScheduledExecutorService;
48 import org.junit.Before;
49 import org.junit.Rule;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.ArgumentCaptor;
53 import org.mockito.Captor;
54 import org.mockito.Mock;
55 import org.mockito.junit.MockitoJUnit;
56 import org.mockito.junit.MockitoRule;
57 import org.robolectric.RobolectricTestRunner;
58 
59 @RunWith(RobolectricTestRunner.class)
60 public final class DownloaderImplTest {
61   @Rule public final MockitoRule mocks = MockitoJUnit.rule();
62 
63   // Use directExecutor to ensure the order of test verification.
64   private static final Executor CONTROL_EXECUTOR = MoreExecutors.directExecutor();
65   private static final Executor BACKGROUND_EXECUTOR = Executors.newCachedThreadPool();
66   private static final ScheduledExecutorService DOWNLOAD_EXECUTOR =
67       Executors.newScheduledThreadPool(2);
68   ListeningExecutorService listeningExecutorService =
69       MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR);
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 
75   @Mock private SingleFileDownloadProgressMonitor mockDownloadMonitor;
76   @Mock private DownloadListener mockDownloadListener;
77   private Downloader downloader;
78   private Context context;
79   private DownloadRequest downloadRequest;
80   private ForegroundDownloadKey foregroundDownloadKey;
81   private final Uri destinationFileUri =
82       Uri.parse(
83           "android://com.google.android.libraries.mobiledatadownload/files/datadownload/shared/public/file_1");
84 
85   @Mock private FileDownloader fileDownloader;
86 
87   @Captor ArgumentCaptor<DownloadListener> downloadListenerCaptor;
88 
89   @Captor
90   ArgumentCaptor<com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest>
91       downloadRequestCaptor;
92 
93   @Before
setUp()94   public void setUp() {
95     context = ApplicationProvider.getApplicationContext();
96     downloader =
97         Downloader.newBuilder()
98             .setContext(context)
99             .setControlExecutor(CONTROL_EXECUTOR)
100             .setDownloadMonitor(mockDownloadMonitor)
101             .setFileDownloaderSupplier(() -> fileDownloader)
102             .setForegroundDownloadService(this.getClass()) // don't need to use the real one.
103             .build();
104 
105     downloadRequest =
106         DownloadRequest.newBuilder()
107             .setDestinationFileUri(destinationFileUri)
108             .setUrlToDownload(FILE_URL)
109             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
110             .setNotificationContentTitle("File url: " + FILE_URL)
111             .build();
112 
113     foregroundDownloadKey = ForegroundDownloadKey.ofSingleFile(destinationFileUri);
114 
115     when(mockDownloadListener.onComplete()).thenReturn(Futures.immediateFuture(null));
116   }
117 
118   @Test
download_whenRequestAlreadyMade_dedups()119   public void download_whenRequestAlreadyMade_dedups() throws Exception {
120     // Use BlockingFileDownloader to ensure first download is in progress.
121     BlockingFileDownloader blockingFileDownloader =
122         new BlockingFileDownloader(listeningExecutorService);
123     Supplier<FileDownloader> blockingDownloaderSupplier = () -> blockingFileDownloader;
124 
125     DownloaderImpl downloaderImpl =
126         new DownloaderImpl(
127             context,
128             Optional.absent(),
129             CONTROL_EXECUTOR,
130             Optional.of(mockDownloadMonitor),
131             blockingDownloaderSupplier);
132 
133     int downloadFuturesInFlightCountBefore = getInProgressFuturesCount(downloaderImpl);
134 
135     ListenableFuture<Void> downloadFuture1 = downloaderImpl.download(downloadRequest);
136     ListenableFuture<Void> downloadFuture2 = downloaderImpl.download(downloadRequest);
137 
138     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
139     assertThat(getInProgressFuturesCount(downloaderImpl) - downloadFuturesInFlightCountBefore)
140         .isEqualTo(1);
141 
142     // Allow blocking download to finish
143     blockingFileDownloader.finishDownloading();
144 
145     // Finish future 2 and assert that future 1 has completed as well
146     downloadFuture2.get();
147     assertThat(downloadFuture1.isDone()).isTrue();
148 
149     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
150     // Sleep for 1 sec to wait for the Future's callback to finish.
151     Thread.sleep(/* millis= */ 1000);
152 
153     // The completed download should be removed from downloadFutureMap map.
154     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
155         .isFalse();
156     assertThat(getInProgressFuturesCount(downloaderImpl))
157         .isEqualTo(downloadFuturesInFlightCountBefore);
158 
159     // Reset state of blockingFileDownloader to prevent deadlocks
160     blockingFileDownloader.resetState();
161   }
162 
163   @Test
download_whenRequestAlreadyMadeUsingForegroundService_dedups()164   public void download_whenRequestAlreadyMadeUsingForegroundService_dedups() throws Exception {
165     // Use BlockingFileDownloader to ensure first download is in progress.
166     BlockingFileDownloader blockingFileDownloader =
167         new BlockingFileDownloader(listeningExecutorService);
168     Supplier<FileDownloader> blockingDownloaderSupplier = () -> blockingFileDownloader;
169 
170     DownloaderImpl downloaderImpl =
171         new DownloaderImpl(
172             context,
173             Optional.of(this.getClass()), // don't need to use the real foreground download service.
174             CONTROL_EXECUTOR,
175             Optional.of(mockDownloadMonitor),
176             blockingDownloaderSupplier);
177 
178     int downloadFuturesInFlightCountBefore = getInProgressFuturesCount(downloaderImpl);
179 
180     ListenableFuture<Void> downloadFuture1 =
181         downloaderImpl.downloadWithForegroundService(downloadRequest);
182     ListenableFuture<Void> downloadFuture2 = downloaderImpl.download(downloadRequest);
183 
184     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
185     assertThat(getInProgressFuturesCount(downloaderImpl) - downloadFuturesInFlightCountBefore)
186         .isEqualTo(1);
187 
188     // Allow blocking download to finish
189     blockingFileDownloader.finishDownloading();
190 
191     // Finish future 2 and assert that future 1 has completed as well
192     downloadFuture2.get();
193     assertThat(downloadFuture1.isDone()).isTrue();
194 
195     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
196     // Sleep for 1 sec to wait for the Future's callback to finish.
197     Thread.sleep(/* millis= */ 1000);
198 
199     // The completed download should be removed from downloadFutureMap map.
200     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
201         .isFalse();
202     assertThat(getInProgressFuturesCount(downloaderImpl))
203         .isEqualTo(downloadFuturesInFlightCountBefore);
204 
205     // Reset state of blockingFileDownloader to prevent deadlocks
206     blockingFileDownloader.resetState();
207   }
208 
209   @Test
download_beginsDownload()210   public void download_beginsDownload() throws Exception {
211     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
212         .thenReturn(Futures.immediateVoidFuture());
213 
214     DownloaderImpl downloaderImpl =
215         new DownloaderImpl(
216             context,
217             Optional.absent(),
218             CONTROL_EXECUTOR,
219             Optional.of(mockDownloadMonitor),
220             () -> fileDownloader);
221 
222     downloadRequest =
223         DownloadRequest.newBuilder()
224             .setDestinationFileUri(destinationFileUri)
225             .setUrlToDownload(FILE_URL)
226             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
227             .setListenerOptional(Optional.of(mockDownloadListener))
228             .build();
229 
230     ListenableFuture<Void> downloadFuture = downloaderImpl.download(downloadRequest);
231     downloadFuture.get();
232 
233     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
234     // Sleep for 1 sec to wait for the Future's callback to finish.
235     Thread.sleep(/* millis= */ 1000);
236 
237     // Verify that correct DownloadRequest is sent to underlying FileDownloader
238     com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
239         actualDownloadRequest = downloadRequestCaptor.getValue();
240     assertThat(actualDownloadRequest.fileUri()).isEqualTo(destinationFileUri);
241     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
242     assertThat(actualDownloadRequest.downloadConstraints())
243         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
244 
245     // Verify that downloadMonitor adds the listener
246     verify(mockDownloadMonitor)
247         .addDownloadListener(any(Uri.class), downloadListenerCaptor.capture());
248     verify(fileDownloader)
249         .startDownloading(
250             any(com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class));
251 
252     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
253     verify(mockDownloadListener).onComplete();
254 
255     // Ensure that given download listener is the same one passed to download monitor
256     DownloadListener capturedDownloadListener = downloadListenerCaptor.getValue();
257     DownloadException testException =
258         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
259 
260     capturedDownloadListener.onProgress(10);
261     capturedDownloadListener.onFailure(testException);
262     capturedDownloadListener.onPausedForConnectivity();
263 
264     verify(mockDownloadListener).onProgress(10);
265     verify(mockDownloadListener).onFailure(testException);
266     verify(mockDownloadListener).onPausedForConnectivity();
267   }
268 
269   @Test
download_whenListenerProvided_handlesOnCompleteFailed()270   public void download_whenListenerProvided_handlesOnCompleteFailed() throws Exception {
271     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
272         .thenReturn(Futures.immediateVoidFuture());
273     when(mockDownloadListener.onComplete())
274         .thenReturn(Futures.immediateFailedFuture(new Exception("test failure")));
275 
276     DownloaderImpl downloaderImpl =
277         new DownloaderImpl(
278             context,
279             Optional.absent(),
280             CONTROL_EXECUTOR,
281             Optional.of(mockDownloadMonitor),
282             () -> fileDownloader);
283 
284     downloadRequest =
285         DownloadRequest.newBuilder()
286             .setDestinationFileUri(destinationFileUri)
287             .setUrlToDownload(FILE_URL)
288             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
289             .setListenerOptional(Optional.of(mockDownloadListener))
290             .build();
291 
292     downloader.download(downloadRequest).get();
293 
294     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
295     // Sleep for 1 sec to wait for the Future's callback to finish.
296     Thread.sleep(/* millis= */ 1000);
297 
298     // Ensure that future is still removed from internal map
299     assertThat(containsInProgressFuture(downloaderImpl, destinationFileUri.toString())).isFalse();
300 
301     // Verify that DownloadMonitor handled DownloadListener properly
302     verify(mockDownloadMonitor).addDownloadListener(destinationFileUri, mockDownloadListener);
303     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
304 
305     // Verify that download was started
306     verify(fileDownloader).startDownloading(any());
307 
308     // Verify the DownloadListeners onComplete was invoked
309     verify(mockDownloadListener).onComplete();
310   }
311 
312   @Test
download_whenListenerProvided_waitsForOnCompleteToFinish()313   public void download_whenListenerProvided_waitsForOnCompleteToFinish() throws Exception {
314     when(fileDownloader.startDownloading(any())).thenReturn(Futures.immediateVoidFuture());
315 
316     // Use a latch to simulate a long running DownloadListener.onComplete
317     CountDownLatch blockingOnCompleteLatch = new CountDownLatch(1);
318 
319     DownloaderImpl downloaderImpl =
320         new DownloaderImpl(
321             context,
322             Optional.absent(),
323             CONTROL_EXECUTOR,
324             Optional.of(mockDownloadMonitor),
325             () -> fileDownloader);
326 
327     downloadRequest =
328         DownloadRequest.newBuilder()
329             .setDestinationFileUri(destinationFileUri)
330             .setUrlToDownload(FILE_URL)
331             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
332             .setListenerOptional(
333                 Optional.of(
334                     new DownloadListener() {
335                       @Override
336                       public void onProgress(long currentSize) {}
337 
338                       @Override
339                       public void onPausedForConnectivity() {}
340 
341                       @Override
342                       public void onFailure(Throwable t) {}
343 
344                       @Override
345                       public ListenableFuture<Void> onComplete() {
346                         return Futures.submitAsync(
347                             () -> {
348                               try {
349                                 // Verify that future map still contains download future.
350                                 assertThat(
351                                         containsInProgressFuture(
352                                             downloaderImpl, foregroundDownloadKey.toString()))
353                                     .isTrue();
354                                 blockingOnCompleteLatch.await();
355                               } catch (InterruptedException e) {
356                                 // Ignore.
357                               }
358                               return Futures.immediateVoidFuture();
359                             },
360                             BACKGROUND_EXECUTOR);
361                       }
362                     }))
363             .build();
364 
365     ListenableFuture<Void> downloadFuture = downloaderImpl.download(downloadRequest);
366     downloadFuture.get();
367 
368     // Verify that the download future map still contains the download future.
369     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
370 
371     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
372     // Sleep for 1 sec to wait for the Future's callback to finish.
373     Thread.sleep(/* millis= */ 1000);
374 
375     // Finish the onComplete method.
376     blockingOnCompleteLatch.countDown();
377 
378     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
379     // Sleep for 1 sec to wait for the Future's callback to finish.
380     Thread.sleep(/* millis= */ 1000);
381 
382     // The completed download should be removed from keyToListenableFuture map.
383     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
384         .isFalse();
385     assertThat(getInProgressFuturesCount(downloaderImpl)).isEqualTo(0);
386 
387     // Verify DownloadListener was added/removed
388     verify(mockDownloadMonitor).addDownloadListener(eq(destinationFileUri), any());
389     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
390   }
391 
392   @Test
download_whenDownloadFails_reportsFailure()393   public void download_whenDownloadFails_reportsFailure() throws Exception {
394     DownloadException downloadException =
395         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
396     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
397         .thenReturn(Futures.immediateFailedFuture(downloadException));
398 
399     DownloaderImpl downloaderImpl =
400         new DownloaderImpl(
401             context,
402             Optional.absent(),
403             CONTROL_EXECUTOR,
404             Optional.of(mockDownloadMonitor),
405             () -> fileDownloader);
406 
407     downloadRequest =
408         DownloadRequest.newBuilder()
409             .setDestinationFileUri(destinationFileUri)
410             .setUrlToDownload(FILE_URL)
411             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
412             .setListenerOptional(Optional.of(mockDownloadListener))
413             .build();
414 
415     ListenableFuture<Void> downloadFuture = downloaderImpl.download(downloadRequest);
416     assertThrows(ExecutionException.class, downloadFuture::get);
417     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
418     assertThat(e.getDownloadResultCode()).isEqualTo(ANDROID_DOWNLOADER_UNKNOWN);
419 
420     // Verify that file download was started and failed
421     verify(fileDownloader).startDownloading(any());
422     com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
423         actualDownloadRequest = downloadRequestCaptor.getValue();
424     assertThat(actualDownloadRequest.fileUri()).isEqualTo(destinationFileUri);
425     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
426     assertThat(actualDownloadRequest.downloadConstraints())
427         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
428 
429     // Verify that DownloadMonitor added/removed DownloadListener
430     verify(mockDownloadMonitor).addDownloadListener(destinationFileUri, mockDownloadListener);
431     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
432 
433     // Verify that DownloadListener.onFailure was invoked with failure
434     verify(mockDownloadListener).onFailure(downloadException);
435     verify(mockDownloadListener, times(0)).onComplete();
436   }
437 
438   @Test
download_whenReturnedFutureIsCanceled_cancelsDownload()439   public void download_whenReturnedFutureIsCanceled_cancelsDownload() throws Exception {
440     // Use BlockingFileDownloader to simulate long download
441     BlockingFileDownloader blockingFileDownloader =
442         new BlockingFileDownloader(listeningExecutorService);
443 
444     DownloaderImpl downloaderImpl =
445         new DownloaderImpl(
446             context,
447             Optional.absent(),
448             CONTROL_EXECUTOR,
449             Optional.of(mockDownloadMonitor),
450             () -> blockingFileDownloader);
451 
452     ListenableFuture<Void> downloadFuture = downloaderImpl.download(downloadRequest);
453 
454     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
455 
456     downloadFuture.cancel(true);
457 
458     // The download future should no longer be included in the future map
459     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
460         .isFalse();
461 
462     // Reset state of blocking file downloader to prevent deadlocks
463     blockingFileDownloader.resetState();
464   }
465 
466   @Test
download_whenMonitorNotProvided_whenDownloadSucceeds_waitsForListenerOnComplete()467   public void download_whenMonitorNotProvided_whenDownloadSucceeds_waitsForListenerOnComplete()
468       throws Exception {
469     when(fileDownloader.startDownloading(any())).thenReturn(Futures.immediateVoidFuture());
470 
471     // Use a latch to simulate a long running DownloadListener.onComplete
472     CountDownLatch blockingOnCompleteLatch = new CountDownLatch(1);
473 
474     DownloaderImpl downloaderImpl =
475         new DownloaderImpl(
476             context, Optional.absent(), CONTROL_EXECUTOR, Optional.absent(), () -> fileDownloader);
477 
478     downloadRequest =
479         DownloadRequest.newBuilder()
480             .setDestinationFileUri(destinationFileUri)
481             .setUrlToDownload(FILE_URL)
482             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
483             .setListenerOptional(
484                 Optional.of(
485                     new DownloadListener() {
486                       @Override
487                       public void onProgress(long currentSize) {}
488 
489                       @Override
490                       public void onPausedForConnectivity() {}
491 
492                       @Override
493                       public void onFailure(Throwable t) {}
494 
495                       @Override
496                       public ListenableFuture<Void> onComplete() {
497                         return Futures.submitAsync(
498                             () -> {
499                               try {
500                                 // Verify that future map still contains download future.
501                                 assertThat(
502                                         containsInProgressFuture(
503                                             downloaderImpl, foregroundDownloadKey.toString()))
504                                     .isTrue();
505                                 blockingOnCompleteLatch.await();
506                               } catch (InterruptedException e) {
507                                 // Ignore.
508                               }
509                               return Futures.immediateVoidFuture();
510                             },
511                             BACKGROUND_EXECUTOR);
512                       }
513                     }))
514             .build();
515 
516     ListenableFuture<Void> downloadFuture = downloaderImpl.download(downloadRequest);
517     downloadFuture.get();
518 
519     // Verify that the download future map still contains the download future.
520     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
521 
522     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
523     // Sleep for 1 sec to wait for the Future's callback to finish.
524     Thread.sleep(/* millis= */ 1000);
525 
526     // Finish the onComplete method.
527     blockingOnCompleteLatch.countDown();
528 
529     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
530     // Sleep for 1 sec to wait for the Future's callback to finish.
531     Thread.sleep(/* millis= */ 1000);
532 
533     // The completed download should be removed from download future map.
534     assertThat(containsInProgressFuture(downloaderImpl, destinationFileUri.toString())).isFalse();
535     assertThat(getInProgressFuturesCount(downloaderImpl)).isEqualTo(0);
536   }
537 
538   @Test
download_whenMonitorNotProvided_whenDownloadFails_reportsFailure()539   public void download_whenMonitorNotProvided_whenDownloadFails_reportsFailure() throws Exception {
540     DownloadException downloadException =
541         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
542     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
543         .thenReturn(Futures.immediateFailedFuture(downloadException));
544 
545     DownloaderImpl downloaderImpl =
546         new DownloaderImpl(
547             context, Optional.absent(), CONTROL_EXECUTOR, Optional.absent(), () -> fileDownloader);
548 
549     downloadRequest =
550         DownloadRequest.newBuilder()
551             .setDestinationFileUri(destinationFileUri)
552             .setUrlToDownload(FILE_URL)
553             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
554             .setListenerOptional(Optional.of(mockDownloadListener))
555             .build();
556 
557     ListenableFuture<Void> downloadFuture = downloaderImpl.download(downloadRequest);
558     assertThrows(ExecutionException.class, downloadFuture::get);
559     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
560     assertThat(e.getDownloadResultCode()).isEqualTo(ANDROID_DOWNLOADER_UNKNOWN);
561 
562     // Verify that file download was started and failed
563     verify(fileDownloader).startDownloading(any());
564     com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
565         actualDownloadRequest = downloadRequestCaptor.getValue();
566     assertThat(actualDownloadRequest.fileUri()).isEqualTo(destinationFileUri);
567     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
568     assertThat(actualDownloadRequest.downloadConstraints())
569         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
570 
571     // Verify that DownloadListener.onFailure was invoked with failure
572     verify(mockDownloadListener).onFailure(downloadException);
573     verify(mockDownloadListener, times(0)).onComplete();
574   }
575 
576   @Test
downloadWithForegroundService_requiresForegroundDownloadService()577   public void downloadWithForegroundService_requiresForegroundDownloadService() throws Exception {
578     // Create downloader without providing foreground service
579     downloader =
580         Downloader.newBuilder()
581             .setContext(context)
582             .setControlExecutor(CONTROL_EXECUTOR)
583             .setDownloadMonitor(mockDownloadMonitor)
584             .setFileDownloaderSupplier(() -> fileDownloader)
585             .build();
586 
587     // Without foreground service, download call should fail with IllegalStateException
588     ListenableFuture<Void> downloadFuture =
589         downloader.downloadWithForegroundService(downloadRequest);
590     ExecutionException e = assertThrows(ExecutionException.class, downloadFuture::get);
591     assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
592 
593     // Verify that underlying download is not started
594     verify(mockDownloadMonitor, times(0))
595         .addDownloadListener(any(Uri.class), any(DownloadListener.class));
596     verify(fileDownloader, times(0))
597         .startDownloading(
598             any(com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class));
599   }
600 
601   @Test
downloadWithForegroundService_requiresDownloadMonitor()602   public void downloadWithForegroundService_requiresDownloadMonitor() throws Exception {
603     // Create downloader without providing DownloadMonitor
604     downloader =
605         Downloader.newBuilder()
606             .setContext(context)
607             .setControlExecutor(CONTROL_EXECUTOR)
608             .setForegroundDownloadService(
609                 this.getClass()) // don't need to use the real foreground download service.
610             .setFileDownloaderSupplier(() -> fileDownloader)
611             .build();
612 
613     // Without foreground service, download call should fail with IllegalStateException
614     ListenableFuture<Void> downloadFuture =
615         downloader.downloadWithForegroundService(downloadRequest);
616     ExecutionException e = assertThrows(ExecutionException.class, downloadFuture::get);
617     assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
618 
619     // Verify that underlying download is not started
620     verify(mockDownloadMonitor, times(0))
621         .addDownloadListener(any(Uri.class), any(DownloadListener.class));
622     verify(fileDownloader, times(0))
623         .startDownloading(
624             any(com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class));
625   }
626 
627   @Test
downloadWithForegroundService_whenRequestAlreadyMade_dedups()628   public void downloadWithForegroundService_whenRequestAlreadyMade_dedups() throws Exception {
629     // Use BlockingFileDownloader to control when the download will finish.
630     BlockingFileDownloader blockingFileDownloader =
631         new BlockingFileDownloader(listeningExecutorService);
632     Supplier<FileDownloader> blockingDownloaderSupplier = () -> blockingFileDownloader;
633 
634     DownloaderImpl downloaderImpl =
635         new DownloaderImpl(
636             context,
637             Optional.of(this.getClass()), // don't need to use the real foreground download service.
638             CONTROL_EXECUTOR,
639             Optional.of(mockDownloadMonitor),
640             blockingDownloaderSupplier);
641 
642     int downloadFuturesInFlightCountBefore = getInProgressFuturesCount(downloaderImpl);
643 
644     ListenableFuture<Void> downloadFuture1 =
645         downloaderImpl.downloadWithForegroundService(downloadRequest);
646     ListenableFuture<Void> downloadFuture2 =
647         downloaderImpl.downloadWithForegroundService(downloadRequest);
648 
649     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
650 
651     assertThat(getInProgressFuturesCount(downloaderImpl) - downloadFuturesInFlightCountBefore)
652         .isEqualTo(1);
653 
654     // Now we let the 2 futures downloadFuture1 downloadFuture2 to run by opening the latch.
655     blockingFileDownloader.finishDownloading();
656 
657     // Now finish future 2, future 1 should finish too and the cache clears the future.
658     downloadFuture2.get();
659     assertThat(downloadFuture1.isDone()).isTrue();
660 
661     // TODO(b/147583059): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
662     // Sleep for 1 sec to wait for the Future's callback to finish.
663     Thread.sleep(/* millis= */ 1000);
664 
665     // The completed download is removed from the download future  Map.
666     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
667         .isFalse();
668     assertThat(getInProgressFuturesCount(downloaderImpl))
669         .isEqualTo(downloadFuturesInFlightCountBefore);
670 
671     // Reset state of blockingFileDownloader to prevent deadlocks
672     blockingFileDownloader.resetState();
673   }
674 
675   @Test
downloadWithForegroundService_whenRequestAlreadyMadeWithoutForegroundService_dedups()676   public void downloadWithForegroundService_whenRequestAlreadyMadeWithoutForegroundService_dedups()
677       throws Exception {
678     // Use BlockingFileDownloader to control when the download will finish.
679     BlockingFileDownloader blockingFileDownloader =
680         new BlockingFileDownloader(listeningExecutorService);
681     Supplier<FileDownloader> blockingDownloaderSupplier = () -> blockingFileDownloader;
682 
683     DownloaderImpl downloaderImpl =
684         new DownloaderImpl(
685             context,
686             Optional.of(this.getClass()), // don't need to use the real foreground download service.
687             CONTROL_EXECUTOR,
688             Optional.of(mockDownloadMonitor),
689             blockingDownloaderSupplier);
690 
691     int downloadFuturesInFlightCountBefore = getInProgressFuturesCount(downloaderImpl);
692 
693     ListenableFuture<Void> downloadFuture1 = downloaderImpl.download(downloadRequest);
694     ListenableFuture<Void> downloadFuture2 =
695         downloaderImpl.downloadWithForegroundService(downloadRequest);
696 
697     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
698     assertThat(getInProgressFuturesCount(downloaderImpl) - downloadFuturesInFlightCountBefore)
699         .isEqualTo(1);
700 
701     // Now we let the 2 futures downloadFuture1 downloadFuture2 to run by opening the latch.
702     blockingFileDownloader.finishDownloading();
703 
704     // Now finish future 2, future 1 should finish too and the cache clears the future.
705     downloadFuture2.get();
706     assertThat(downloadFuture1.isDone()).isTrue();
707 
708     // TODO(b/155918406): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
709     // Sleep for 1 sec to wait for the Future's callback to finish.
710     Thread.sleep(/* millis= */ 1000);
711 
712     // The completed download is removed from the download future Map.
713     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
714         .isFalse();
715     assertThat(getInProgressFuturesCount(downloaderImpl))
716         .isEqualTo(downloadFuturesInFlightCountBefore);
717 
718     // Reset state of blockingFileDownloader to prevent deadlocks
719     blockingFileDownloader.resetState();
720   }
721 
722   @Test
downloadWithForegroundService()723   public void downloadWithForegroundService() throws ExecutionException, InterruptedException {
724     ArgumentCaptor<DownloadListener> downloadListenerCaptor =
725         ArgumentCaptor.forClass(DownloadListener.class);
726     ArgumentCaptor<com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest>
727         downloadRequestCaptor =
728             ArgumentCaptor.forClass(
729                 com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class);
730     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
731         .thenReturn(Futures.immediateFuture(null));
732 
733     downloadRequest =
734         DownloadRequest.newBuilder()
735             .setDestinationFileUri(destinationFileUri)
736             .setUrlToDownload(FILE_URL)
737             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
738             .setNotificationContentTitle("File url: " + FILE_URL)
739             .setListenerOptional(Optional.of(mockDownloadListener))
740             .build();
741 
742     ListenableFuture<Void> downloadFuture =
743         downloader.downloadWithForegroundService(downloadRequest);
744     downloadFuture.get();
745 
746     // TODO(b/147583059): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
747     // Sleep for 1 sec to wait for the Future's callback to finish.
748     Thread.sleep(/* millis= */ 1000);
749 
750     // Verify that the correct DownloadRequest is sent to underderlying FileDownloader.
751     com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
752         actualDownloadRequest = downloadRequestCaptor.getValue();
753     assertThat(actualDownloadRequest.fileUri()).isEqualTo(destinationFileUri);
754     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
755     assertThat(actualDownloadRequest.downloadConstraints())
756         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
757 
758     // Verify that downloadMonitor will add a DownloadListener.
759     verify(mockDownloadMonitor)
760         .addDownloadListener(any(Uri.class), downloadListenerCaptor.capture());
761     verify(fileDownloader)
762         .startDownloading(
763             any(com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class));
764 
765     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
766 
767     verify(mockDownloadListener).onComplete();
768 
769     // Now simulate other DownloadListener's callbacks:
770     downloadListenerCaptor.getValue().onProgress(10);
771     verify(mockDownloadListener).onProgress(10);
772     downloadListenerCaptor.getValue().onPausedForConnectivity();
773     DownloadException downloadException =
774         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
775     downloadListenerCaptor.getValue().onFailure(downloadException);
776 
777     verify(mockDownloadListener).onPausedForConnectivity();
778     verify(mockDownloadListener).onFailure(downloadException);
779   }
780 
781   @Test
downloadWithForegroundService_clientOnCompleteFailed()782   public void downloadWithForegroundService_clientOnCompleteFailed()
783       throws ExecutionException, InterruptedException {
784     ArgumentCaptor<DownloadListener> downloadListenerCaptor =
785         ArgumentCaptor.forClass(DownloadListener.class);
786     ArgumentCaptor<com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest>
787         downloadRequestCaptor =
788             ArgumentCaptor.forClass(
789                 com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class);
790     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
791         .thenReturn(Futures.immediateFuture(null));
792 
793     // Client's provided DownloadListener.onComplete failed.
794     when(mockDownloadListener.onComplete())
795         .thenReturn(Futures.immediateFailedFuture(new Exception()));
796 
797     downloadRequest =
798         DownloadRequest.newBuilder()
799             .setDestinationFileUri(destinationFileUri)
800             .setUrlToDownload(FILE_URL)
801             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
802             .setNotificationContentTitle("File url: " + FILE_URL)
803             .setListenerOptional(Optional.of(mockDownloadListener))
804             .build();
805 
806     ListenableFuture<Void> downloadFuture =
807         downloader.downloadWithForegroundService(downloadRequest);
808     downloadFuture.get();
809 
810     // TODO(b/147583059): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
811     // Sleep for 1 sec to wait for the Future's callback to finish.
812     Thread.sleep(/* millis= */ 1000);
813 
814     // Verify that the correct DownloadRequest is sent to underderlying FileDownloader.
815     com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
816         actualDownloadRequest = downloadRequestCaptor.getValue();
817     assertThat(actualDownloadRequest.fileUri()).isEqualTo(destinationFileUri);
818     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
819     assertThat(actualDownloadRequest.downloadConstraints())
820         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
821 
822     // Verify that downloadMonitor will add a DownloadListener.
823     verify(mockDownloadMonitor)
824         .addDownloadListener(any(Uri.class), downloadListenerCaptor.capture());
825     verify(fileDownloader)
826         .startDownloading(
827             any(com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class));
828 
829     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
830 
831     verify(mockDownloadListener).onComplete();
832   }
833 
834   @Test
downloadWithForegroundService_clientOnCompleteBlocked()835   public void downloadWithForegroundService_clientOnCompleteBlocked()
836       throws ExecutionException, InterruptedException {
837     ArgumentCaptor<com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest>
838         downloadRequestCaptor =
839             ArgumentCaptor.forClass(
840                 com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class);
841     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
842         .thenReturn(Futures.immediateFuture(null));
843 
844     // Using latch to block on client's onComplete to simulate a very long running onComplete.
845     CountDownLatch blockingOnCompleteLatch = new CountDownLatch(1);
846 
847     DownloaderImpl downloaderImpl =
848         new DownloaderImpl(
849             context,
850             Optional.of(this.getClass()), // don't need to use the real foreground download service
851             CONTROL_EXECUTOR,
852             Optional.of(mockDownloadMonitor),
853             () -> fileDownloader);
854 
855     downloadRequest =
856         DownloadRequest.newBuilder()
857             .setDestinationFileUri(destinationFileUri)
858             .setUrlToDownload(FILE_URL)
859             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
860             .setNotificationContentTitle("File url: " + FILE_URL)
861             .setListenerOptional(
862                 Optional.of(
863                     new DownloadListener() {
864                       @Override
865                       public void onProgress(long currentSize) {}
866 
867                       @Override
868                       public ListenableFuture<Void> onComplete() {
869                         return Futures.submitAsync(
870                             () -> {
871                               try {
872                                 // Block the onComplete task.
873                                 // Verify that before client's onComplete finishes, the on-going
874                                 // download future map still contain this download. This means
875                                 // the Foreground Download Service has not be shut down yet.
876                                 assertThat(
877                                         containsInProgressFuture(
878                                             downloaderImpl, foregroundDownloadKey.toString()))
879                                     .isTrue();
880                                 blockingOnCompleteLatch.await();
881                               } catch (InterruptedException e) {
882                                 // Ignore.
883                               }
884                               return Futures.immediateVoidFuture();
885                             },
886                             BACKGROUND_EXECUTOR);
887                       }
888 
889                       @Override
890                       public void onFailure(Throwable t) {}
891 
892                       @Override
893                       public void onPausedForConnectivity() {}
894                     }))
895             .build();
896 
897     ListenableFuture<Void> downloadFuture =
898         downloaderImpl.downloadWithForegroundService(downloadRequest);
899     downloadFuture.get();
900 
901     // TODO(b/147583059): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
902     // Sleep for 1 sec to wait for the Future's callback to finish.
903     Thread.sleep(/* millis= */ 1000);
904 
905     // Verify that this download future has not been removed from the download future map yet.
906     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
907 
908     // Now let's the onComplete finishes.
909     blockingOnCompleteLatch.countDown();
910 
911     // TODO(b/147583059): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
912     // Sleep for 1 sec to wait for the Future's callback on onComplete to finish.
913     Thread.sleep(/* millis= */ 1000);
914 
915     // The completed download is removed from the download future Map.
916     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
917         .isFalse();
918     assertThat(getInProgressFuturesCount(downloaderImpl)).isEqualTo(0);
919 
920     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
921   }
922 
923   @Test
downloadWithForegroundService_failure()924   public void downloadWithForegroundService_failure()
925       throws ExecutionException, InterruptedException {
926     ArgumentCaptor<DownloadListener> downloadListenerCaptor =
927         ArgumentCaptor.forClass(DownloadListener.class);
928     ArgumentCaptor<com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest>
929         downloadRequestCaptor =
930             ArgumentCaptor.forClass(
931                 com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class);
932     DownloadException downloadException =
933         DownloadException.builder().setDownloadResultCode(ANDROID_DOWNLOADER_UNKNOWN).build();
934 
935     when(fileDownloader.startDownloading(downloadRequestCaptor.capture()))
936         .thenReturn(Futures.immediateFailedFuture(downloadException));
937 
938     downloadRequest =
939         DownloadRequest.newBuilder()
940             .setDestinationFileUri(destinationFileUri)
941             .setUrlToDownload(FILE_URL)
942             .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED)
943             .setNotificationContentTitle("File url: " + FILE_URL)
944             .setListenerOptional(Optional.of(mockDownloadListener))
945             .build();
946 
947     ListenableFuture<Void> downloadFuture =
948         downloader.downloadWithForegroundService(downloadRequest);
949     assertThrows(ExecutionException.class, downloadFuture::get);
950     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
951     assertThat(e.getDownloadResultCode()).isEqualTo(ANDROID_DOWNLOADER_UNKNOWN);
952 
953     // TODO(b/147583059): Convert to Framework test and use TestingTaskBarrier to avoid sleep.
954     // Sleep for 1 sec to wait for the listener to finish.
955     Thread.sleep(/* millis= */ 1000);
956 
957     // Verify that the correct DownloadRequest is sent to underderlying FileDownloader.
958     com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest
959         actualDownloadRequest = downloadRequestCaptor.getValue();
960     assertThat(actualDownloadRequest.fileUri()).isEqualTo(destinationFileUri);
961     assertThat(actualDownloadRequest.urlToDownload()).isEqualTo(FILE_URL);
962     assertThat(actualDownloadRequest.downloadConstraints())
963         .isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
964 
965     // Verify that downloadMonitor will add a DownloadListener.
966     verify(mockDownloadMonitor)
967         .addDownloadListener(any(Uri.class), downloadListenerCaptor.capture());
968     verify(fileDownloader)
969         .startDownloading(
970             any(com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest.class));
971 
972     verify(mockDownloadMonitor).removeDownloadListener(destinationFileUri);
973 
974     // Since the download failed, onComplete will not be called but onFailure.
975     verify(mockDownloadListener, times(0)).onComplete();
976     verify(mockDownloadListener).onFailure(downloadException);
977   }
978 
979   @Test
cancelDownloadWithForegroundService()980   public void cancelDownloadWithForegroundService() {
981     // Use BlockingFileDownloader to control when the download will finish.
982     BlockingFileDownloader blockingFileDownloader =
983         new BlockingFileDownloader(listeningExecutorService);
984     Supplier<FileDownloader> blockingDownloaderSupplier = () -> blockingFileDownloader;
985 
986     DownloaderImpl downloaderImpl =
987         new DownloaderImpl(
988             context,
989             Optional.of(this.getClass()), // don't need to use the real foreground download service
990             CONTROL_EXECUTOR,
991             Optional.of(mockDownloadMonitor),
992             blockingDownloaderSupplier);
993 
994     int downloadFuturesInFlightCountBefore = getInProgressFuturesCount(downloaderImpl);
995 
996     ListenableFuture<Void> downloadFuture =
997         downloaderImpl.downloadWithForegroundService(downloadRequest);
998 
999     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
1000     assertThat(getInProgressFuturesCount(downloaderImpl) - downloadFuturesInFlightCountBefore)
1001         .isEqualTo(1);
1002 
1003     downloaderImpl.cancelForegroundDownload(foregroundDownloadKey.toString());
1004     assertTrue(downloadFuture.isCancelled());
1005 
1006     // The completed download is removed from the download future Map.
1007     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
1008         .isFalse();
1009     assertThat(getInProgressFuturesCount(downloaderImpl))
1010         .isEqualTo(downloadFuturesInFlightCountBefore);
1011 
1012     // Reset state of blockingFileDownloader to prevent deadlocks
1013     blockingFileDownloader.resetState();
1014   }
1015 
1016   @Test
cancelListenableFuture()1017   public void cancelListenableFuture() {
1018     // Use BlockingFileDownloader to control when the download will finish.
1019     BlockingFileDownloader blockingFileDownloader =
1020         new BlockingFileDownloader(listeningExecutorService);
1021     Supplier<FileDownloader> blockingDownloaderSupplier = () -> blockingFileDownloader;
1022 
1023     DownloaderImpl downloaderImpl =
1024         new DownloaderImpl(
1025             context,
1026             Optional.of(this.getClass()), // don't need to use the real foreground download service
1027             CONTROL_EXECUTOR,
1028             Optional.of(mockDownloadMonitor),
1029             blockingDownloaderSupplier);
1030 
1031     ListenableFuture<Void> downloadFuture =
1032         downloaderImpl.downloadWithForegroundService(downloadRequest);
1033 
1034     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString())).isTrue();
1035 
1036     downloadFuture.cancel(true);
1037 
1038     // The completed download is removed from the uriToListenableFuture Map.
1039     assertThat(containsInProgressFuture(downloaderImpl, foregroundDownloadKey.toString()))
1040         .isFalse();
1041 
1042     // Reset state of blockingFileDownloader to prevent deadlocks
1043     blockingFileDownloader.resetState();
1044   }
1045 
getInProgressFuturesCount(DownloaderImpl downloaderImpl)1046   private static int getInProgressFuturesCount(DownloaderImpl downloaderImpl) {
1047     return downloaderImpl.downloadFutureMap.keyToDownloadFutureMap.size()
1048         + downloaderImpl.foregroundDownloadFutureMap.keyToDownloadFutureMap.size();
1049   }
1050 
containsInProgressFuture(DownloaderImpl downloaderImpl, String key)1051   private static boolean containsInProgressFuture(DownloaderImpl downloaderImpl, String key) {
1052     return downloaderImpl.downloadFutureMap.keyToDownloadFutureMap.containsKey(key)
1053         || downloaderImpl.foregroundDownloadFutureMap.keyToDownloadFutureMap.containsKey(key);
1054   }
1055 }
1056