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