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