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 android.system.Os.readlink; 19 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_CHECKSUM; 20 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_GROUP_NAME; 21 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_ID; 22 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_SIZE; 23 import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_URL; 24 import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.ExecutorType; 25 import static com.google.android.libraries.mobiledatadownload.tracing.TracePropagation.propagateCallable; 26 import static com.google.common.truth.Truth.assertThat; 27 import static java.util.concurrent.TimeUnit.SECONDS; 28 import static org.junit.Assert.assertThrows; 29 import static org.junit.Assert.fail; 30 import static org.mockito.Mockito.eq; 31 import static org.mockito.Mockito.times; 32 import static org.mockito.Mockito.verify; 33 34 import android.accounts.Account; 35 import android.content.Context; 36 import android.net.Uri; 37 import android.util.Log; 38 import androidx.test.core.app.ApplicationProvider; 39 import com.google.android.libraries.mobiledatadownload.account.AccountUtil; 40 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; 41 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 42 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; 43 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri; 44 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUriAdapter; 45 import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend; 46 import com.google.android.libraries.mobiledatadownload.file.openers.ReadStringOpener; 47 import com.google.android.libraries.mobiledatadownload.file.openers.WriteStringOpener; 48 import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil; 49 import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor; 50 import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader; 51 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource; 52 import com.google.android.libraries.mobiledatadownload.testing.TestFileDownloader; 53 import com.google.android.libraries.mobiledatadownload.testing.TestFlags; 54 import com.google.common.base.Optional; 55 import com.google.common.base.Supplier; 56 import com.google.common.collect.ImmutableList; 57 import com.google.common.util.concurrent.Futures; 58 import com.google.common.util.concurrent.ListenableFuture; 59 import com.google.common.util.concurrent.ListeningExecutorService; 60 import com.google.common.util.concurrent.ListeningScheduledExecutorService; 61 import com.google.common.util.concurrent.MoreExecutors; 62 import com.google.mobiledatadownload.ClientConfigProto.ClientFile; 63 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup; 64 import com.google.mobiledatadownload.DownloadConfigProto.DataFile; 65 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; 66 import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy; 67 import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; 68 import com.google.mobiledatadownload.LogProto.MddDownloadResultLog; 69 import com.google.mobiledatadownload.LogProto.MddLogData; 70 import java.io.IOException; 71 import java.util.Arrays; 72 import java.util.Collection; 73 import java.util.List; 74 import java.util.concurrent.CancellationException; 75 import java.util.concurrent.ExecutionException; 76 import java.util.concurrent.Executors; 77 import java.util.concurrent.TimeoutException; 78 import org.junit.After; 79 import org.junit.Before; 80 import org.junit.Rule; 81 import org.junit.Test; 82 import org.junit.runner.RunWith; 83 import org.junit.runners.Parameterized; 84 import org.junit.runners.Parameterized.Parameter; 85 import org.junit.runners.Parameterized.Parameters; 86 import org.mockito.ArgumentCaptor; 87 import org.mockito.Mock; 88 import org.mockito.junit.MockitoJUnit; 89 import org.mockito.junit.MockitoRule; 90 91 // NOTE: TestParameterInjector is preferred for parameterized tests, but it has a API 92 // level constraint of >= 24 while MDD has a constraint of >= 16. To prevent basic regressions, run 93 // this test using junit's Parameterized TestRunner, which supports all API levels. 94 @RunWith(Parameterized.class) 95 public class MobileDataDownloadIntegrationTest { 96 97 private static final String TAG = "MobileDataDownloadIntegrationTest"; 98 private static final int MAX_HANDLE_TASK_WAIT_TIME_SECS = 300; 99 private static final int MAX_MDD_API_WAIT_TIME_SECS = 5; 100 101 private static final String TEST_DATA_RELATIVE_PATH = 102 "third_party/java_src/android_libs/mobiledatadownload/javatests/com/google/android/libraries/mobiledatadownload/testdata/"; 103 104 private static final ListeningScheduledExecutorService DOWNLOAD_EXECUTOR = 105 MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(2)); 106 107 private static final Context context = ApplicationProvider.getApplicationContext(); 108 private final NetworkUsageMonitor networkUsageMonitor = 109 new NetworkUsageMonitor(context, new FakeTimeSource()); 110 111 private final SynchronousFileStorage fileStorage = 112 new SynchronousFileStorage( 113 ImmutableList.of(AndroidFileBackend.builder(context).build(), new JavaFileBackend()), 114 ImmutableList.of(), 115 ImmutableList.of(networkUsageMonitor)); 116 117 private final TestFlags flags = new TestFlags(); 118 119 private ListeningExecutorService controlExecutor; 120 121 private MobileDataDownload mobileDataDownload; 122 123 @Mock private Logger mockLogger; 124 @Mock private TaskScheduler mockTaskScheduler; 125 126 @Rule public final MockitoRule mocks = MockitoJUnit.rule(); 127 128 @Parameter public ExecutorType controlExecutorType; 129 130 @Parameters data()131 public static Collection<Object[]> data() { 132 return Arrays.asList( 133 new Object[][] { 134 {ExecutorType.SINGLE_THREADED}, {ExecutorType.MULTI_THREADED}, 135 }); 136 } 137 138 @Before setUp()139 public void setUp() throws Exception { 140 141 flags.enableZipFolder = Optional.of(true); 142 143 controlExecutor = controlExecutorType.executor(); 144 } 145 146 @After tearDown()147 public void tearDown() throws Exception { 148 mobileDataDownload.clear().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 149 } 150 151 @Test download_success_fileGroupDownloaded()152 public void download_success_fileGroupDownloaded() throws Exception { 153 mobileDataDownload = 154 builderForTest() 155 .setFileDownloaderSupplier( 156 () -> 157 new TestFileDownloader( 158 TEST_DATA_RELATIVE_PATH, 159 fileStorage, 160 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 161 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 162 .build(); 163 164 waitForHandleTask(); 165 166 String debugString = mobileDataDownload.getDebugInfoAsString(); 167 Log.i(TAG, "MDD Lib dump:"); 168 for (String line : debugString.split("\n", -1)) { 169 Log.i(TAG, line); 170 } 171 172 ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 173 verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); 174 } 175 176 @Test download_withCustomValidator()177 public void download_withCustomValidator() throws Exception { 178 CustomFileGroupValidator validator = 179 fileGroup -> { 180 if (!fileGroup.getGroupName().equals(FILE_GROUP_NAME)) { 181 return Futures.immediateFuture(true); 182 } 183 return MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR) 184 .submit( 185 propagateCallable( 186 () -> { 187 SynchronousFileStorage storage = 188 new SynchronousFileStorage( 189 ImmutableList.of(AndroidFileBackend.builder(context).build())); 190 for (ClientFile file : fileGroup.getFileList()) { 191 if (!storage.exists(Uri.parse(file.getFileUri()))) { 192 return false; 193 } 194 } 195 return true; 196 })); 197 }; 198 199 mobileDataDownload = 200 builderForTest() 201 .setFileDownloaderSupplier( 202 () -> 203 new TestFileDownloader( 204 TEST_DATA_RELATIVE_PATH, 205 fileStorage, 206 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 207 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 208 .setCustomFileGroupValidatorOptional(Optional.of(validator)) 209 .build(); 210 211 waitForHandleTask(); 212 213 ClientFileGroup clientFileGroup = 214 mobileDataDownload 215 .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 216 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 217 verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); 218 } 219 220 @Test download_success_maintenanceLogsNetworkUsage()221 public void download_success_maintenanceLogsNetworkUsage() throws Exception { 222 flags.networkStatsLoggingSampleInterval = Optional.of(1); 223 224 mobileDataDownload = 225 builderForTest() 226 .setFileDownloaderSupplier( 227 () -> 228 new TestFileDownloader( 229 TEST_DATA_RELATIVE_PATH, 230 fileStorage, 231 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 232 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 233 .build(); 234 235 waitForHandleTask(); 236 237 // This should flush the logs from NetworkLogger. 238 mobileDataDownload 239 .handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK) 240 .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 241 242 ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 243 verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); 244 245 ArgumentCaptor<MddLogData> logDataCaptor = ArgumentCaptor.forClass(MddLogData.class); 246 // 1056 is the tag number for MddClientEvent.Code.EVENT_CODE_UNSPECIFIED. 247 verify(mockLogger, times(1)).log(logDataCaptor.capture(), /* eventCode= */ eq(1056)); 248 249 List<MddLogData> logDataList = logDataCaptor.getAllValues(); 250 assertThat(logDataList).hasSize(1); 251 MddLogData logData = logDataList.get(0); 252 253 Void mddNetworkStats = null; 254 255 // Network status changes depending on emulator: 256 boolean isCellular = NetworkUsageMonitor.isCellular(context); 257 } 258 259 @Test corrupted_files_detectedDuringMaintenance()260 public void corrupted_files_detectedDuringMaintenance() throws Exception { 261 flags.mddDefaultSampleInterval = Optional.of(1); 262 263 mobileDataDownload = 264 builderForTest() 265 .setFileDownloaderSupplier( 266 () -> 267 new TestFileDownloader( 268 TEST_DATA_RELATIVE_PATH, 269 fileStorage, 270 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 271 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 272 .build(); 273 274 waitForHandleTask(); 275 276 ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 277 fileStorage.open( 278 Uri.parse(clientFileGroup.getFile(0).getFileUri()), WriteStringOpener.create("c0rrupt3d")); 279 280 // Bad file is detected during maintenance. 281 mobileDataDownload 282 .handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK) 283 .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 284 285 // File group is re-downloaded. 286 mobileDataDownload 287 .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) 288 .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 289 290 // Re-load the file group since the on-disk URIs will have changed. 291 clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 292 assertThat( 293 fileStorage.open( 294 Uri.parse(clientFileGroup.getFile(0).getFileUri()), ReadStringOpener.create())) 295 .isNotEqualTo("c0rrupt3d"); 296 } 297 298 @Test delete_files_detectedDuringMaintenance()299 public void delete_files_detectedDuringMaintenance() throws Exception { 300 flags.mddDefaultSampleInterval = Optional.of(1); 301 302 mobileDataDownload = 303 builderForTest() 304 .setFileDownloaderSupplier( 305 () -> 306 new TestFileDownloader( 307 TEST_DATA_RELATIVE_PATH, 308 fileStorage, 309 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 310 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 311 .build(); 312 313 waitForHandleTask(); 314 315 ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 316 fileStorage.deleteFile(Uri.parse(clientFileGroup.getFile(0).getFileUri())); 317 318 // Bad file is detected during maintenance. 319 mobileDataDownload 320 .handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK) 321 .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 322 323 // File group is re-downloaded. 324 mobileDataDownload 325 .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) 326 .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 327 328 // Re-load the file group since the on-disk URIs will have changed. 329 clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 330 assertThat(fileStorage.exists(Uri.parse(clientFileGroup.getFile(0).getFileUri()))).isTrue(); 331 } 332 333 @Test remove_withAccount_fileGroupRemains()334 public void remove_withAccount_fileGroupRemains() throws Exception { 335 mobileDataDownload = 336 builderForTest() 337 .setFileDownloaderSupplier( 338 () -> 339 new TestFileDownloader( 340 TEST_DATA_RELATIVE_PATH, 341 fileStorage, 342 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 343 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 344 .build(); 345 346 waitForHandleTask(); 347 348 // Remove the file group with account doesn't change anything, because the test group is not 349 // associated with any account. 350 Account account = AccountUtil.create("name", "google"); 351 assertThat(account).isNotNull(); 352 assertThat( 353 mobileDataDownload 354 .removeFileGroup( 355 RemoveFileGroupRequest.newBuilder() 356 .setGroupName(FILE_GROUP_NAME) 357 .setAccountOptional(Optional.of(account)) 358 .build()) 359 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) 360 .isTrue(); 361 362 ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 363 verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); 364 } 365 366 @Test remove_withoutAccount_fileGroupRemoved()367 public void remove_withoutAccount_fileGroupRemoved() throws Exception { 368 mobileDataDownload = 369 builderForTest() 370 .setFileDownloaderSupplier( 371 () -> 372 new TestFileDownloader( 373 TEST_DATA_RELATIVE_PATH, 374 fileStorage, 375 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 376 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 377 .build(); 378 379 waitForHandleTask(); 380 381 // Remove the file group will make the file group not accessible from clients. 382 assertThat( 383 mobileDataDownload 384 .removeFileGroup( 385 RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 386 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) 387 .isTrue(); 388 389 ClientFileGroup clientFileGroup = 390 mobileDataDownload 391 .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 392 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 393 assertThat(clientFileGroup).isNull(); 394 } 395 396 @Test removeFileGroupsByFilter_removesMatchingGroups()397 public void removeFileGroupsByFilter_removesMatchingGroups() throws Exception { 398 mobileDataDownload = 399 builderForTest() 400 .setFileDownloaderSupplier( 401 () -> 402 new TestFileDownloader( 403 TEST_DATA_RELATIVE_PATH, 404 fileStorage, 405 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 406 .build(); 407 408 // Remove All Groups to clear state 409 mobileDataDownload 410 .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) 411 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 412 413 // Tear down: remove remaining group to prevent cross test errors 414 mobileDataDownload 415 .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) 416 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 417 } 418 419 @Test removeFileGroupsByFilter_whenAccountSpecified_removesMatchingAccountDependentGroups()420 public void removeFileGroupsByFilter_whenAccountSpecified_removesMatchingAccountDependentGroups() 421 throws Exception { 422 mobileDataDownload = 423 builderForTest() 424 .setFileDownloaderSupplier( 425 () -> 426 new TestFileDownloader( 427 TEST_DATA_RELATIVE_PATH, 428 fileStorage, 429 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 430 .build(); 431 432 // Remove all groups 433 mobileDataDownload 434 .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) 435 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 436 437 // Setup account 438 Account account = AccountUtil.create("name", "google"); 439 440 // Setup two groups, 1 with account and 1 without an account 441 DataFileGroup fileGroupWithoutAccount = 442 TestFileGroupPopulator.createDataFileGroup( 443 FILE_GROUP_NAME, 444 context.getPackageName(), 445 new String[] {FILE_ID}, 446 new int[] {FILE_SIZE}, 447 new String[] {FILE_CHECKSUM}, 448 new String[] {FILE_URL}, 449 DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK) 450 .toBuilder() 451 .build(); 452 DataFileGroup fileGroupWithAccount = 453 fileGroupWithoutAccount.toBuilder().setGroupName(FILE_GROUP_NAME + "_2").build(); 454 455 // Add both groups to MDD 456 mobileDataDownload 457 .addFileGroup( 458 AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroupWithoutAccount).build()) 459 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 460 mobileDataDownload 461 .addFileGroup( 462 AddFileGroupRequest.newBuilder() 463 .setDataFileGroup(fileGroupWithAccount) 464 .setAccountOptional(Optional.of(account)) 465 .build()) 466 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 467 468 // Verify that both groups are present 469 assertThat( 470 mobileDataDownload 471 .getFileGroupsByFilter( 472 GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) 473 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) 474 .hasSize(2); 475 476 // Remove file groups with given account and source 477 mobileDataDownload 478 .removeFileGroupsByFilter( 479 RemoveFileGroupsByFilterRequest.newBuilder() 480 .setAccountOptional(Optional.of(account)) 481 .build()) 482 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 483 484 // Check that only account-independent group remains 485 ImmutableList<ClientFileGroup> remainingGroups = 486 mobileDataDownload 487 .getFileGroupsByFilter( 488 GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) 489 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 490 assertThat(remainingGroups).hasSize(1); 491 assertThat(remainingGroups.get(0).getGroupName()).isEqualTo(FILE_GROUP_NAME); 492 493 // Tear down: remove remaining group to prevent cross test errors 494 mobileDataDownload 495 .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) 496 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 497 } 498 499 @Test 500 public void removeFileGroupsByFilter_whenAccountNotSpecified_removesMatchingAccountIndependentGroups()501 removeFileGroupsByFilter_whenAccountNotSpecified_removesMatchingAccountIndependentGroups() 502 throws Exception { 503 mobileDataDownload = 504 builderForTest() 505 .setFileDownloaderSupplier( 506 () -> 507 new TestFileDownloader( 508 TEST_DATA_RELATIVE_PATH, 509 fileStorage, 510 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 511 .build(); 512 513 waitForHandleTask(); 514 515 // Remove all groups 516 mobileDataDownload 517 .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) 518 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 519 520 // Setup account 521 Account account = AccountUtil.create("name", "google"); 522 523 // Setup two groups, 1 with account and 1 without an account 524 DataFileGroup fileGroupWithoutAccount = 525 TestFileGroupPopulator.createDataFileGroup( 526 FILE_GROUP_NAME, 527 context.getPackageName(), 528 new String[] {FILE_ID}, 529 new int[] {FILE_SIZE}, 530 new String[] {FILE_CHECKSUM}, 531 new String[] {FILE_URL}, 532 DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK) 533 .toBuilder() 534 .build(); 535 DataFileGroup fileGroupWithAccount = 536 fileGroupWithoutAccount.toBuilder().setGroupName(FILE_GROUP_NAME + "_2").build(); 537 538 // Add both groups to MDD 539 mobileDataDownload 540 .addFileGroup( 541 AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroupWithoutAccount).build()) 542 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 543 mobileDataDownload 544 .addFileGroup( 545 AddFileGroupRequest.newBuilder() 546 .setDataFileGroup(fileGroupWithAccount) 547 .setAccountOptional(Optional.of(account)) 548 .build()) 549 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 550 551 // Verify that both groups are present 552 assertThat( 553 mobileDataDownload 554 .getFileGroupsByFilter( 555 GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) 556 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) 557 .hasSize(2); 558 559 // Remove file groups with given source only 560 mobileDataDownload 561 .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) 562 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 563 564 // Check that only account-dependent group remains 565 ImmutableList<ClientFileGroup> remainingGroups = 566 mobileDataDownload 567 .getFileGroupsByFilter( 568 GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) 569 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 570 assertThat(remainingGroups).hasSize(1); 571 assertThat(remainingGroups.get(0).getGroupName()).isEqualTo(FILE_GROUP_NAME + "_2"); 572 573 // Tear down: remove remaining group to prevent cross test errors 574 mobileDataDownload 575 .removeFileGroupsByFilter( 576 RemoveFileGroupsByFilterRequest.newBuilder() 577 .setAccountOptional(Optional.of(account)) 578 .build()) 579 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 580 } 581 582 @Test download_failure_throwsDownloadException()583 public void download_failure_throwsDownloadException() throws Exception { 584 flags.mddDefaultSampleInterval = Optional.of(1); 585 586 mobileDataDownload = 587 builderForTest() 588 .setFileDownloaderSupplier( 589 () -> 590 new TestFileDownloader( 591 TEST_DATA_RELATIVE_PATH, 592 fileStorage, 593 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 594 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 595 .build(); 596 597 waitForHandleTask(); 598 599 DataFileGroup dataFileGroup = 600 TestFileGroupPopulator.createDataFileGroup( 601 FILE_GROUP_NAME, 602 context.getPackageName(), 603 new String[] {"one", "two"}, 604 new int[] {1000, 2000}, 605 new String[] {"checksum1", "checksum2"}, 606 new String[] { 607 "http://www.gstatic.com/", // This url is not secure. 608 "https://www.gstatic.com/does_not_exist" // This url does not exist. 609 }, 610 DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK); 611 612 assertThat( 613 mobileDataDownload 614 .addFileGroup( 615 AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build()) 616 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) 617 .isTrue(); 618 619 ListenableFuture<ClientFileGroup> downloadFuture = 620 mobileDataDownload.downloadFileGroup( 621 DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); 622 623 ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get); 624 assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class); 625 AggregateException cause = (AggregateException) exception.getCause(); 626 assertThat(cause).isNotNull(); 627 ImmutableList<Throwable> failures = cause.getFailures(); 628 assertThat(failures).hasSize(2); 629 assertThat(failures.get(0)).isInstanceOf(DownloadException.class); 630 assertThat(failures.get(1)).isInstanceOf(DownloadException.class); 631 } 632 633 @Test download_failure_logsEvent()634 public void download_failure_logsEvent() throws Exception { 635 flags.mddDefaultSampleInterval = Optional.of(1); 636 637 mobileDataDownload = 638 builderForTest() 639 .setFileDownloaderSupplier( 640 () -> 641 new TestFileDownloader( 642 TEST_DATA_RELATIVE_PATH, 643 fileStorage, 644 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 645 .addFileGroupPopulator(new TestFileGroupPopulator(context)) 646 .build(); 647 648 DataFileGroup dataFileGroup = 649 TestFileGroupPopulator.createDataFileGroup( 650 FILE_GROUP_NAME, 651 context.getPackageName(), 652 new String[] {"one", "two"}, 653 new int[] {1000, 2000}, 654 new String[] {"checksum1", "checksum2"}, 655 new String[] { 656 "http://www.gstatic.com/", // This url is not secure. 657 "https://www.gstatic.com/does_not_exist" // This url does not exist. 658 }, 659 DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK); 660 661 assertThat( 662 mobileDataDownload 663 .addFileGroup( 664 AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build()) 665 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) 666 .isTrue(); 667 668 ListenableFuture<ClientFileGroup> downloadFuture = 669 mobileDataDownload.downloadFileGroup( 670 DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); 671 672 assertThrows(ExecutionException.class, downloadFuture::get); 673 674 if (controlExecutorType.equals(ExecutorType.SINGLE_THREADED)) { 675 // Single-threaded executor step requires some time to allow logging to finish. 676 // TODO: Investigate whether TestingTaskBarrier can be used here to wait for 677 // executor become idle. 678 Thread.sleep(500); 679 } 680 681 ArgumentCaptor<MddLogData> logDataCaptor = ArgumentCaptor.forClass(MddLogData.class); 682 // 1068 is the tag number for MddClientEvent.Code.DATA_DOWNLOAD_RESULT_LOG. 683 verify(mockLogger, times(2)).log(logDataCaptor.capture(), /* eventCode= */ eq(1068)); 684 685 List<MddLogData> logData = logDataCaptor.getAllValues(); 686 assertThat(logData).hasSize(2); 687 688 MddDownloadResultLog downloadResultLog1 = logData.get(0).getMddDownloadResultLog(); 689 MddDownloadResultLog downloadResultLog2 = logData.get(1).getMddDownloadResultLog(); 690 assertThat(downloadResultLog1.getResult()).isEqualTo(MddDownloadResult.Code.INSECURE_URL_ERROR); 691 assertThat(downloadResultLog1.getDataDownloadFileGroupStats().getFileGroupName()) 692 .isEqualTo(FILE_GROUP_NAME); 693 assertThat(downloadResultLog1.getDataDownloadFileGroupStats().getOwnerPackage()) 694 .isEqualTo(context.getPackageName()); 695 assertThat(downloadResultLog2.getResult()) 696 .isEqualTo(MddDownloadResult.Code.ANDROID_DOWNLOADER_HTTP_ERROR); 697 assertThat(downloadResultLog2.getDataDownloadFileGroupStats().getFileGroupName()) 698 .isEqualTo(FILE_GROUP_NAME); 699 assertThat(downloadResultLog2.getDataDownloadFileGroupStats().getOwnerPackage()) 700 .isEqualTo(context.getPackageName()); 701 } 702 703 @Test download_zipFile_unzippedAfterDownload()704 public void download_zipFile_unzippedAfterDownload() throws Exception { 705 mobileDataDownload = 706 builderForTest() 707 .setFileDownloaderSupplier( 708 () -> 709 new TestFileDownloader( 710 TEST_DATA_RELATIVE_PATH, 711 fileStorage, 712 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 713 .addFileGroupPopulator(new ZipFolderFileGroupPopulator(context)) 714 .build(); 715 716 waitForHandleTask(); 717 718 ClientFileGroup clientFileGroup = 719 getAndVerifyClientFileGroup(ZipFolderFileGroupPopulator.FILE_GROUP_NAME, 3); 720 721 for (ClientFile clientFile : clientFileGroup.getFileList()) { 722 if ("/zip1.txt".equals(clientFile.getFileId())) { 723 verifyClientFile(clientFile, "/zip1.txt", 11); 724 } else if ("/zip2.txt".equals(clientFile.getFileId())) { 725 verifyClientFile(clientFile, "/zip2.txt", 11); 726 } else if ("/sub_folder/zip3.txt".equals(clientFile.getFileId())) { 727 verifyClientFile(clientFile, "/sub_folder/zip3.txt", 25); 728 } else { 729 fail("Unexpect file:" + clientFile.getFileId()); 730 } 731 } 732 } 733 734 @Test download_cancelDuringDownload_downloadCancelled()735 public void download_cancelDuringDownload_downloadCancelled() throws Exception { 736 BlockingFileDownloader blockingFileDownloader = new BlockingFileDownloader(DOWNLOAD_EXECUTOR); 737 738 Supplier<FileDownloader> fakeFileDownloaderSupplier = () -> blockingFileDownloader; 739 740 mobileDataDownload = 741 builderForTest().setFileDownloaderSupplier(fakeFileDownloaderSupplier).build(); 742 743 // Register the file group and trigger download. 744 mobileDataDownload 745 .addFileGroup( 746 AddFileGroupRequest.newBuilder() 747 .setDataFileGroup( 748 TestFileGroupPopulator.createDataFileGroup( 749 FILE_GROUP_NAME, 750 context.getPackageName(), 751 new String[] {FILE_ID}, 752 new int[] {FILE_SIZE}, 753 new String[] {FILE_CHECKSUM}, 754 new String[] {FILE_URL}, 755 DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK)) 756 .build()) 757 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 758 ListenableFuture<ClientFileGroup> downloadFuture = 759 mobileDataDownload.downloadFileGroup( 760 DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); 761 762 // Wait for download to be scheduled. The future shouldn't be done yet. 763 blockingFileDownloader.waitForDownloadStarted(); 764 assertThat(downloadFuture.isDone()).isFalse(); 765 766 // Now remove the file group from MDD, which would cancel any ongoing download. 767 mobileDataDownload 768 .removeFileGroup(RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 769 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 770 // Now let the download future finish. 771 blockingFileDownloader.finishDownloading(); 772 773 // Make sure that the download has been canceled and leads to cancelled future. 774 ExecutionException exception = 775 assertThrows( 776 ExecutionException.class, 777 () -> downloadFuture.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS)); 778 assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class); 779 AggregateException cause = (AggregateException) exception.getCause(); 780 assertThat(cause).isNotNull(); 781 ImmutableList<Throwable> failures = cause.getFailures(); 782 assertThat(failures).hasSize(1); 783 assertThat(failures.get(0)).isInstanceOf(CancellationException.class); 784 } 785 786 @Test download_twoStepDownload_targetFileDownloaded()787 public void download_twoStepDownload_targetFileDownloaded() throws Exception { 788 mobileDataDownload = 789 builderForTest() 790 .setFileDownloaderSupplier( 791 () -> 792 new TestFileDownloader( 793 TEST_DATA_RELATIVE_PATH, 794 fileStorage, 795 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 796 .addFileGroupPopulator(new TwoStepPopulator(context, fileStorage)) 797 .build(); 798 799 // Add step1 file group to MDD. 800 DataFileGroup step1FileGroup = 801 TestFileGroupPopulator.createDataFileGroup( 802 "step1-file-group", 803 context.getPackageName(), 804 new String[] {"step1_id"}, 805 new int[] {57}, 806 new String[] {""}, 807 new String[] {"https://www.gstatic.com/icing/idd/sample_group/step1.txt"}, 808 DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK); 809 810 ListenableFuture<Boolean> unused = 811 mobileDataDownload.addFileGroup( 812 AddFileGroupRequest.newBuilder().setDataFileGroup(step1FileGroup).build()); 813 814 // This will trigger refreshing of FileGroupPopulators and downloading. 815 mobileDataDownload 816 .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) 817 .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 818 819 // Now verify that the step1-file-group is downloaded and then TwoStepPopulator will add 820 // step2-file-group and it was downloaded too in one cycle (one call of handleTask). 821 822 // Verify step1-file-group. 823 ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup("step1-file-group", 1); 824 verifyClientFile(clientFileGroup.getFile(0), "step1_id", 57); 825 826 // Verify step2-file-group. 827 clientFileGroup = getAndVerifyClientFileGroup("step2-file-group", 1); 828 verifyClientFile(clientFileGroup.getFile(0), "step2_id", 13); 829 } 830 831 @Test download_relativeFilePaths_createsSymlinks()832 public void download_relativeFilePaths_createsSymlinks() throws Exception { 833 AndroidUriAdapter adapter = AndroidUriAdapter.forContext(context); 834 mobileDataDownload = 835 builderForTest() 836 .setFileDownloaderSupplier( 837 () -> 838 new TestFileDownloader( 839 TEST_DATA_RELATIVE_PATH, 840 fileStorage, 841 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 842 .build(); 843 844 DataFileGroup fileGroup = 845 DataFileGroup.newBuilder() 846 .setGroupName(FILE_GROUP_NAME) 847 .setOwnerPackage(context.getPackageName()) 848 .setPreserveFilenamesAndIsolateFiles(true) 849 .addFile( 850 DataFile.newBuilder() 851 .setFileId(FILE_ID) 852 .setByteSize(FILE_SIZE) 853 .setChecksumType(DataFile.ChecksumType.DEFAULT) 854 .setChecksum(FILE_CHECKSUM) 855 .setUrlToDownload(FILE_URL) 856 .setRelativeFilePath("relative_path") 857 .build()) 858 .build(); 859 860 mobileDataDownload 861 .addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroup).build()) 862 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 863 864 mobileDataDownload 865 .downloadFileGroup( 866 DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 867 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 868 869 // verify symlink structure, we can't get access to the full internal file uri, but we can tell 870 // the start of it 871 Uri expectedFileUri = 872 DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent()) 873 .buildUpon() 874 .appendPath(DirectoryUtil.MDD_STORAGE_SYMLINKS) 875 .appendPath(DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS) 876 .appendPath(FILE_GROUP_NAME) 877 .build(); 878 // we can't get access to the full internal target file uri, but we know the start of it 879 Uri expectedStartTargetUri = 880 DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent()) 881 .buildUpon() 882 .appendPath(DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS) 883 .appendPath("datadownloadfile_") 884 .build(); 885 886 ClientFileGroup clientFileGroup = 887 mobileDataDownload 888 .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 889 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 890 891 Uri fileUri = Uri.parse(clientFileGroup.getFile(0).getFileUri()); 892 Uri targetUri = 893 AndroidUri.builder(context) 894 .fromAbsolutePath(readlink(adapter.toFile(fileUri).getAbsolutePath())) 895 .build(); 896 897 assertThat(fileUri.toString()).contains(expectedFileUri.toString()); 898 assertThat(targetUri.toString()).contains(expectedStartTargetUri.toString()); 899 assertThat(fileStorage.exists(fileUri)).isTrue(); 900 assertThat(fileStorage.exists(targetUri)).isTrue(); 901 } 902 903 @Test remove_relativeFilePaths_removesSymlinks()904 public void remove_relativeFilePaths_removesSymlinks() throws Exception { 905 mobileDataDownload = 906 builderForTest() 907 .setFileDownloaderSupplier( 908 () -> 909 new TestFileDownloader( 910 TEST_DATA_RELATIVE_PATH, 911 fileStorage, 912 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) 913 .build(); 914 915 DataFileGroup fileGroup = 916 DataFileGroup.newBuilder() 917 .setGroupName(FILE_GROUP_NAME) 918 .setOwnerPackage(context.getPackageName()) 919 .setPreserveFilenamesAndIsolateFiles(true) 920 .addFile( 921 DataFile.newBuilder() 922 .setFileId(FILE_ID) 923 .setByteSize(FILE_SIZE) 924 .setChecksumType(DataFile.ChecksumType.DEFAULT) 925 .setChecksum(FILE_CHECKSUM) 926 .setUrlToDownload(FILE_URL) 927 .setRelativeFilePath("relative_path") 928 .build()) 929 .build(); 930 931 mobileDataDownload 932 .addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroup).build()) 933 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 934 935 mobileDataDownload 936 .downloadFileGroup( 937 DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 938 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 939 940 ClientFileGroup clientFileGroup = 941 mobileDataDownload 942 .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 943 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 944 945 Uri fileUri = Uri.parse(clientFileGroup.getFile(0).getFileUri()); 946 947 // Verify that file uri gets created 948 assertThat(fileStorage.exists(fileUri)).isTrue(); 949 950 mobileDataDownload 951 .removeFileGroup(RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) 952 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 953 954 // Verify that file uri still exists even though file group is stale 955 assertThat(fileStorage.exists(fileUri)).isTrue(); 956 957 mobileDataDownload.maintenance().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 958 959 // Verify that file uri gets removed, once maintenance runs 960 if (flags.mddEnableGarbageCollection()) { 961 // cl/439051122 created a temporary FALSE override targeted to ASGA devices. This test only 962 // makes sense if the flag is true, but all_on testing doesn't respect diversion criteria in 963 // the launch. So we skip it for now. 964 // TODO(b/226551373): remove this once AsgaDisableMddLibGcLaunch is turned down 965 assertThat(fileStorage.exists(fileUri)).isFalse(); 966 } 967 } 968 969 @Test handleTask_duplicateInvocations_logsDownloadCompleteOnce()970 public void handleTask_duplicateInvocations_logsDownloadCompleteOnce() throws Exception { 971 // Override the feature flag to log at 100%. 972 flags.mddDefaultSampleInterval = Optional.of(1); 973 974 TestFileDownloader testFileDownloader = 975 new TestFileDownloader( 976 TEST_DATA_RELATIVE_PATH, 977 fileStorage, 978 MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)); 979 BlockingFileDownloader blockingFileDownloader = 980 new BlockingFileDownloader(DOWNLOAD_EXECUTOR, testFileDownloader); 981 982 Supplier<FileDownloader> fakeFileDownloaderSupplier = () -> blockingFileDownloader; 983 984 mobileDataDownload = 985 builderForTest().setFileDownloaderSupplier(fakeFileDownloaderSupplier).build(); 986 987 // Use test populator to add the group as pending. 988 TestFileGroupPopulator populator = new TestFileGroupPopulator(context); 989 populator.refreshFileGroups(mobileDataDownload).get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 990 991 // Call handle task in non-blocking way and use blocking file downloader to let handleTask1 wait 992 // at the download stage 993 ListenableFuture<Void> handleTask1Future = 994 mobileDataDownload.handleTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK); 995 996 blockingFileDownloader.waitForDownloadStarted(); 997 998 ListenableFuture<Void> handleTask2Future = 999 mobileDataDownload.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK); 1000 1001 // Trigger a complete so the download "completes" after both tasks have been started. 1002 blockingFileDownloader.finishDownloading(); 1003 1004 // Wait for both futures to complete so we can make assertions about the events logged 1005 handleTask2Future.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 1006 handleTask1Future.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 1007 1008 // Check that group is downloaded. 1009 ClientFileGroup unused = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); 1010 1011 if (controlExecutorType.equals(ExecutorType.SINGLE_THREADED)) { 1012 // Single-threaded executor step requires some time to allow logging to finish. 1013 // TODO: Investigate whether TestingTaskBarrier can be used here to wait for 1014 // executor become idle. 1015 Thread.sleep(500); 1016 } 1017 1018 // Check that logger only logged 1 download complete event 1019 ArgumentCaptor<MddLogData> logDataCompleteCaptor = ArgumentCaptor.forClass(MddLogData.class); 1020 // 1007 is the tag number for MddClientEvent.Code.EVENT_CODE_UNSPECIFIED. 1021 verify(mockLogger, times(1)).log(logDataCompleteCaptor.capture(), /* eventCode= */ eq(1007)); 1022 } 1023 builderForTest()1024 private MobileDataDownloadBuilder builderForTest() { 1025 return MobileDataDownloadBuilder.newBuilder() 1026 .setContext(context) 1027 .setControlExecutor(controlExecutor) 1028 .setTaskScheduler(Optional.of(mockTaskScheduler)) 1029 .setLoggerOptional(Optional.of(mockLogger)) 1030 .setDeltaDecoderOptional(Optional.absent()) 1031 .setFileStorage(fileStorage) 1032 .setNetworkUsageMonitor(networkUsageMonitor) 1033 .setFlagsOptional(Optional.of(flags)); 1034 } 1035 1036 /** Creates MDD object and triggers handleTask to refresh and download file groups. */ waitForHandleTask()1037 private void waitForHandleTask() 1038 throws InterruptedException, ExecutionException, TimeoutException { 1039 // This will trigger refreshing of FileGroupPopulators and downloading. 1040 mobileDataDownload 1041 .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) 1042 .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); 1043 1044 String debugString = mobileDataDownload.getDebugInfoAsString(); 1045 Log.i(TAG, "MDD Lib dump:"); 1046 for (String line : debugString.split("\n", -1)) { 1047 Log.i(TAG, line); 1048 } 1049 } 1050 getAndVerifyClientFileGroup(String fileGroupName, int fileCount)1051 private ClientFileGroup getAndVerifyClientFileGroup(String fileGroupName, int fileCount) 1052 throws ExecutionException, TimeoutException, InterruptedException { 1053 ClientFileGroup clientFileGroup = 1054 mobileDataDownload 1055 .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(fileGroupName).build()) 1056 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 1057 assertThat(clientFileGroup).isNotNull(); 1058 assertThat(clientFileGroup.getGroupName()).isEqualTo(fileGroupName); 1059 assertThat(clientFileGroup.getFileCount()).isEqualTo(fileCount); 1060 1061 return clientFileGroup; 1062 } 1063 verifyClientFile(ClientFile clientFile, String fileId, int fileSize)1064 private void verifyClientFile(ClientFile clientFile, String fileId, int fileSize) 1065 throws IOException { 1066 assertThat(clientFile.getFileId()).isEqualTo(fileId); 1067 Uri androidUri = Uri.parse(clientFile.getFileUri()); 1068 assertThat(fileStorage.fileSize(androidUri)).isEqualTo(fileSize); 1069 } 1070 } 1071