• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.libraries.mobiledatadownload;
17 
18 import static com.google.common.labs.truth.FutureSubject.assertThat;
19 import static com.google.common.truth.Truth.assertThat;
20 import static java.nio.charset.StandardCharsets.UTF_8;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertThrows;
23 import static org.junit.Assert.fail;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyBoolean;
26 import static org.mockito.ArgumentMatchers.anyList;
27 import static org.mockito.ArgumentMatchers.anyLong;
28 import static org.mockito.Mockito.doThrow;
29 import static org.mockito.Mockito.eq;
30 import static org.mockito.Mockito.never;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoInteractions;
34 import static org.mockito.Mockito.verifyNoMoreInteractions;
35 import static org.mockito.Mockito.when;
36 
37 import android.accounts.Account;
38 import android.content.Context;
39 import android.net.Uri;
40 import androidx.test.core.app.ApplicationProvider;
41 import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
42 import com.google.android.libraries.mobiledatadownload.TaskScheduler.ConstraintOverrides;
43 import com.google.android.libraries.mobiledatadownload.TaskScheduler.NetworkState;
44 import com.google.android.libraries.mobiledatadownload.account.AccountUtil;
45 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
46 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
47 import com.google.android.libraries.mobiledatadownload.file.openers.WriteStreamOpener;
48 import com.google.android.libraries.mobiledatadownload.internal.MobileDataDownloadManager;
49 import com.google.android.libraries.mobiledatadownload.internal.collect.GroupKeyAndGroup;
50 import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger;
51 import com.google.android.libraries.mobiledatadownload.internal.logging.testing.FakeEventLogger;
52 import com.google.android.libraries.mobiledatadownload.internal.util.ProtoConversionUtil;
53 import com.google.android.libraries.mobiledatadownload.lite.Downloader;
54 import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor;
55 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
56 import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
57 import com.google.common.base.Optional;
58 import com.google.common.collect.ImmutableList;
59 import com.google.common.collect.ImmutableMap;
60 import com.google.common.labs.concurrent.LabsFutures;
61 import com.google.common.util.concurrent.FutureCallback;
62 import com.google.common.util.concurrent.Futures;
63 import com.google.common.util.concurrent.ListenableFuture;
64 import com.google.common.util.concurrent.ListeningExecutorService;
65 import com.google.common.util.concurrent.MoreExecutors;
66 import com.google.mobiledatadownload.ClientConfigProto.ClientFile;
67 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup;
68 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup.Status;
69 import com.google.mobiledatadownload.DownloadConfigProto.DataFile;
70 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
71 import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions;
72 import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy;
73 import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats;
74 import com.google.mobiledatadownload.internal.MetadataProto;
75 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
76 import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
77 import com.google.protobuf.Any;
78 import com.google.protobuf.ByteString;
79 import com.google.protobuf.StringValue;
80 import java.io.IOException;
81 import java.io.OutputStream;
82 import java.util.ArrayList;
83 import java.util.HashMap;
84 import java.util.List;
85 import java.util.Map;
86 import java.util.concurrent.CancellationException;
87 import java.util.concurrent.ExecutionException;
88 import java.util.concurrent.Executors;
89 import java.util.concurrent.TimeUnit;
90 import java.util.concurrent.atomic.AtomicBoolean;
91 import org.junit.After;
92 import org.junit.Before;
93 import org.junit.Rule;
94 import org.junit.Test;
95 import org.junit.runner.RunWith;
96 import org.mockito.ArgumentCaptor;
97 import org.mockito.Captor;
98 import org.mockito.Mock;
99 import org.mockito.junit.MockitoJUnit;
100 import org.mockito.junit.MockitoRule;
101 import org.robolectric.RobolectricTestRunner;
102 
103 /** Tests for {@link com.google.android.libraries.mobiledatadownload.MobileDataDownload}. */
104 @RunWith(RobolectricTestRunner.class)
105 public class MobileDataDownloadTest {
106   private static final Context context = ApplicationProvider.getApplicationContext();
107 
108   private static final String FILE_GROUP_NAME_1 = "test-group-1";
109   private static final String FILE_GROUP_NAME_2 = "test-group-2";
110   private static final String FILE_ID_1 = "test-file-1";
111   private static final String FILE_ID_2 = "test-file-2";
112   private static final String FILE_CHECKSUM_1 = "c1ef7864c76a99ae738ddad33882ed65972c99cc";
113   private static final String FILE_URL_1 = "https://www.gstatic.com/suggest-dev/odws1_test_4.jar";
114   private static final int FILE_SIZE_1 = 85769;
115 
116   private static final String FILE_CHECKSUM_2 = "a1cba9d87b1440f41ce9e7da38c43e1f6bd7d5df";
117   private static final String FILE_URL_2 = "https://www.gstatic.com/suggest-dev/odws1_empty.jar";
118   private static final int FILE_SIZE_2 = 554;
119 
120   private static final DataFileGroup FILE_GROUP_1 =
121       createDataFileGroup(
122           FILE_GROUP_NAME_1,
123           context.getPackageName(),
124           /* versionNumber= */ 1,
125           new String[] {FILE_ID_1},
126           new int[] {FILE_SIZE_1},
127           new String[] {FILE_CHECKSUM_1},
128           new String[] {FILE_URL_1},
129           DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
130 
131   private static final DataFileGroupInternal FILE_GROUP_INTERNAL_1 =
132       createDataFileGroupInternal(
133           FILE_GROUP_NAME_1,
134           context.getPackageName(),
135           /* versionNumber= */ 5,
136           new String[] {FILE_ID_1},
137           new int[] {FILE_SIZE_1},
138           new String[] {FILE_CHECKSUM_1},
139           new String[] {FILE_URL_1},
140           DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
141 
142   private final Uri onDeviceUri1 =
143       Uri.parse(
144           "android://com.google.android.libraries.mobiledatadownload/files/datadownload/shared/public/file_1");
145   private final Uri onDeviceDirUri =
146       Uri.parse(
147           "android://com.google.android.libraries.mobiledatadownload/files/datadownload/shared/public/dir");
148   private final Uri onDeviceDirFileUri1 =
149       Uri.parse(
150           "android://com.google.android.libraries.mobiledatadownload/files/datadownload/shared/public/dir/file_1");
151   private final String onDeviceDirFile1Content = "Test file 1.";
152   private final Uri onDeviceDirFileUri2 =
153       Uri.parse(
154           "android://com.google.android.libraries.mobiledatadownload/files/datadownload/shared/public/dir/file_2");
155   private final String onDeviceDirFile2Content = "Test file 2.";
156   private final Uri onDeviceDirFileUri3 =
157       Uri.parse(
158           "android://com.google.android.libraries.mobiledatadownload/files/datadownload/shared/public/dir/sub/file");
159   private final String onDeviceDirFile3Content = "Test file 3 in sub-dir.";
160 
161   private final TestFlags flags = new TestFlags();
162   private SynchronousFileStorage fileStorage;
163   private FakeTimeSource timeSource;
164   private FakeEventLogger fakeEventLogger;
165 
166   @Mock EventLogger mockEventLogger;
167   @Mock MobileDataDownloadManager mockMobileDataDownloadManager;
168   @Mock TaskScheduler mockTaskScheduler;
169   @Mock FileGroupPopulator mockFileGroupPopulator;
170   @Mock DownloadProgressMonitor mockDownloadMonitor;
171   @Mock Downloader singleFileDownloader;
172 
173   @Captor ArgumentCaptor<GroupKey> groupKeyCaptor;
174   @Captor ArgumentCaptor<List<GroupKey>> groupKeysCaptor;
175 
176   // Note: Executor must not be a single thread executor.
177   ListeningExecutorService controlExecutor =
178       MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
179 
180   @Rule public final MockitoRule mocks = MockitoJUnit.rule();
181 
182   @Before
setUp()183   public void setUp() throws IOException {
184     fileStorage =
185         new SynchronousFileStorage(
186             ImmutableList.of(AndroidFileBackend.builder(context).build()) /*backends*/);
187     createFile(onDeviceUri1, "test");
188     if (!fileStorage.exists(onDeviceDirUri)) {
189       fileStorage.createDirectory(onDeviceDirUri);
190     }
191     createFile(onDeviceDirFileUri1, onDeviceDirFile1Content);
192     createFile(onDeviceDirFileUri2, onDeviceDirFile2Content);
193     createFile(onDeviceDirFileUri3, onDeviceDirFile3Content);
194     timeSource = new FakeTimeSource();
195     fakeEventLogger = new FakeEventLogger();
196   }
197 
198   @After
tearDown()199   public void tearDown() throws Exception {
200     if (fileStorage.exists(onDeviceUri1)) {
201       fileStorage.deleteFile(onDeviceUri1);
202     }
203     if (fileStorage.exists(onDeviceDirFileUri1)) {
204       fileStorage.deleteFile(onDeviceDirFileUri1);
205     }
206     if (fileStorage.exists(onDeviceDirFileUri2)) {
207       fileStorage.deleteFile(onDeviceDirFileUri2);
208     }
209     if (fileStorage.exists(onDeviceDirFileUri3)) {
210       fileStorage.deleteFile(onDeviceDirFileUri3);
211     }
212   }
213 
createFile(Uri uri, String content)214   private void createFile(Uri uri, String content) throws IOException {
215     try (OutputStream out = fileStorage.open(uri, WriteStreamOpener.create())) {
216       out.write(content.getBytes(UTF_8));
217     }
218   }
219 
expectErrorLogMessage(String message)220   private void expectErrorLogMessage(String message) {}
221 
222   @Test
buildGetFileGroupsByFilterRequest()223   public void buildGetFileGroupsByFilterRequest() throws Exception {
224     Account account = AccountUtil.create("account-name", "account-type");
225     GetFileGroupsByFilterRequest request1 =
226         GetFileGroupsByFilterRequest.newBuilder()
227             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
228             .setAccountOptional(Optional.of(account))
229             .build();
230     assertThat(request1.groupNameOptional()).hasValue(FILE_GROUP_NAME_1);
231     assertThat(request1.accountOptional()).hasValue(account);
232 
233     GetFileGroupsByFilterRequest request2 =
234         GetFileGroupsByFilterRequest.newBuilder()
235             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
236             .build();
237     assertThat(request2.groupNameOptional()).hasValue(FILE_GROUP_NAME_1);
238     assertThat(request2.accountOptional()).isAbsent();
239 
240     GetFileGroupsByFilterRequest.Builder builder = GetFileGroupsByFilterRequest.newBuilder();
241 
242     assertThrows(IllegalArgumentException.class, builder::build);
243   }
244 
245   @Test
buildGetFileGroupsByFilterRequest_groupWithNoAccountOnly()246   public void buildGetFileGroupsByFilterRequest_groupWithNoAccountOnly() {
247     Account account = AccountUtil.create("account-name", "account-type");
248     GetFileGroupsByFilterRequest.Builder builder =
249         GetFileGroupsByFilterRequest.newBuilder()
250             .setGroupWithNoAccountOnly(true)
251             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
252             .setAccountOptional(Optional.of(account));
253 
254     // Make sure that when request account independent groups, accountOptional should be absent.
255     assertThrows(IllegalArgumentException.class, builder::build);
256   }
257 
258   @Test
addFileGroup()259   public void addFileGroup() throws Exception {
260     when(mockMobileDataDownloadManager.addGroupForDownloadInternal(
261             any(GroupKey.class), any(DataFileGroupInternal.class), any()))
262         .thenReturn(Futures.immediateFuture(true));
263 
264     MobileDataDownload mobileDataDownload =
265         new MobileDataDownloadImpl(
266             context,
267             mockEventLogger,
268             mockMobileDataDownloadManager,
269             controlExecutor,
270             ImmutableList.of() /* fileGroupPopulatorList */,
271             Optional.of(mockTaskScheduler),
272             fileStorage,
273             Optional.absent() /* downloadMonitorOptional */,
274             Optional.of(this.getClass()), // don't need to use the real foreground download service.
275             flags,
276             singleFileDownloader,
277             Optional.absent() /* customFileGroupValidator */,
278             timeSource);
279 
280     assertThat(
281             mobileDataDownload
282                 .addFileGroup(
283                     AddFileGroupRequest.newBuilder().setDataFileGroup(FILE_GROUP_1).build())
284                 .get())
285         .isTrue();
286   }
287 
288   @Test
addFileGroup_onFailure()289   public void addFileGroup_onFailure() throws Exception {
290     when(mockMobileDataDownloadManager.addGroupForDownloadInternal(
291             any(GroupKey.class), any(DataFileGroupInternal.class), any()))
292         .thenReturn(Futures.immediateFuture(false));
293 
294     MobileDataDownload mobileDataDownload =
295         new MobileDataDownloadImpl(
296             context,
297             mockEventLogger,
298             mockMobileDataDownloadManager,
299             controlExecutor,
300             ImmutableList.of() /* fileGroupPopulatorList */,
301             Optional.of(mockTaskScheduler),
302             fileStorage,
303             Optional.absent() /* downloadMonitorOptional */,
304             Optional.of(this.getClass()), // don't need to use the real foreground download service.
305             flags,
306             singleFileDownloader,
307             Optional.absent() /* customFileGroupValidator */,
308             timeSource);
309 
310     assertThat(
311             mobileDataDownload
312                 .addFileGroup(
313                     AddFileGroupRequest.newBuilder().setDataFileGroup(FILE_GROUP_1).build())
314                 .get())
315         .isFalse();
316   }
317 
318   @Test
addFileGroup_invalidOwnerPackageName()319   public void addFileGroup_invalidOwnerPackageName() throws Exception {
320     when(mockMobileDataDownloadManager.addGroupForDownloadInternal(
321             any(GroupKey.class), any(DataFileGroupInternal.class), any()))
322         .thenReturn(Futures.immediateFuture(true));
323 
324     // Owner Package should be same as the app package.
325     DataFileGroup dataFileGroup =
326         createDataFileGroup(
327             FILE_GROUP_NAME_1,
328             "PACKAGE_NAME",
329             1 /* versionNumber */,
330             new String[] {FILE_ID_1},
331             new int[] {FILE_SIZE_1},
332             new String[] {FILE_CHECKSUM_1},
333             new String[] {FILE_URL_1},
334             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
335 
336     MobileDataDownload mobileDataDownload =
337         new MobileDataDownloadImpl(
338             context,
339             mockEventLogger,
340             mockMobileDataDownloadManager,
341             controlExecutor,
342             null /* fileGroupPopulatorList */,
343             Optional.of(mockTaskScheduler),
344             fileStorage,
345             Optional.absent() /* downloadMonitorOptional */,
346             Optional.of(this.getClass()), // don't need to use the real foreground download service.
347             flags,
348             singleFileDownloader,
349             Optional.absent() /* customFileGroupValidator */,
350             timeSource);
351 
352     expectErrorLogMessage(
353         "MobileDataDownload: Added group = 'test-group-1' with wrong owner package:"
354             + " 'com.google.android.libraries.mobiledatadownload' v.s. 'PACKAGE_NAME' ");
355     assertThat(
356             mobileDataDownload
357                 .addFileGroup(
358                     AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build())
359                 .get())
360         .isFalse();
361   }
362 
363   @Test
addFileGroupWithFileGroupKey()364   public void addFileGroupWithFileGroupKey() throws Exception {
365     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
366     when(mockMobileDataDownloadManager.addGroupForDownloadInternal(
367             groupKeyCaptor.capture(), any(DataFileGroupInternal.class), any()))
368         .thenReturn(Futures.immediateFuture(true));
369 
370     MobileDataDownload mobileDataDownload =
371         new MobileDataDownloadImpl(
372             context,
373             mockEventLogger,
374             mockMobileDataDownloadManager,
375             controlExecutor,
376             ImmutableList.of() /* fileGroupPopulatorList */,
377             Optional.of(mockTaskScheduler),
378             fileStorage,
379             Optional.absent() /* downloadMonitorOptional */,
380             Optional.of(this.getClass()), // don't need to use the real foreground download service.
381             flags,
382             singleFileDownloader,
383             Optional.absent() /* customFileGroupValidator */,
384             timeSource);
385 
386     Account account = AccountUtil.create("account-name", "account-type");
387     AddFileGroupRequest addFileGroupRequest =
388         AddFileGroupRequest.newBuilder()
389             .setDataFileGroup(FILE_GROUP_1)
390             .setAccountOptional(Optional.of(account))
391             .build();
392 
393     assertThat(mobileDataDownload.addFileGroup(addFileGroupRequest).get()).isTrue();
394 
395     GroupKey groupKey =
396         GroupKey.newBuilder()
397             .setGroupName(FILE_GROUP_NAME_1)
398             .setOwnerPackage(context.getPackageName())
399             .setAccount(AccountUtil.serialize(account))
400             .build();
401     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
402     verify(mockMobileDataDownloadManager)
403         .addGroupForDownloadInternal(
404             eq(groupKey), eq(ProtoConversionUtil.convert(FILE_GROUP_1)), any());
405   }
406 
407   @Test
addFileGroupWithFileGroupKey_onFailure()408   public void addFileGroupWithFileGroupKey_onFailure() throws Exception {
409     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
410     when(mockMobileDataDownloadManager.addGroupForDownloadInternal(
411             groupKeyCaptor.capture(), any(DataFileGroupInternal.class), any()))
412         .thenReturn(Futures.immediateFuture(false));
413 
414     MobileDataDownload mobileDataDownload =
415         new MobileDataDownloadImpl(
416             context,
417             mockEventLogger,
418             mockMobileDataDownloadManager,
419             controlExecutor,
420             ImmutableList.of() /* fileGroupPopulatorList */,
421             Optional.of(mockTaskScheduler),
422             fileStorage,
423             Optional.absent() /* downloadMonitorOptional */,
424             Optional.of(this.getClass()), // don't need to use the real foreground download service.
425             flags,
426             singleFileDownloader,
427             Optional.absent() /* customFileGroupValidator */,
428             timeSource);
429 
430     Account account = AccountUtil.create("account-name", "account-type");
431     AddFileGroupRequest addFileGroupRequest =
432         AddFileGroupRequest.newBuilder()
433             .setDataFileGroup(FILE_GROUP_1)
434             .setAccountOptional(Optional.of(account))
435             .build();
436 
437     assertThat(mobileDataDownload.addFileGroup(addFileGroupRequest).get()).isFalse();
438 
439     GroupKey groupKey =
440         GroupKey.newBuilder()
441             .setGroupName(FILE_GROUP_NAME_1)
442             .setOwnerPackage(context.getPackageName())
443             .setAccount(AccountUtil.serialize(account))
444             .build();
445     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
446     verify(mockMobileDataDownloadManager)
447         .addGroupForDownloadInternal(
448             eq(groupKey), eq(ProtoConversionUtil.convert(FILE_GROUP_1)), any());
449   }
450 
451   @Test
addFileGroupWithFileGroupKey_nullAccount()452   public void addFileGroupWithFileGroupKey_nullAccount() throws Exception {
453     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
454     when(mockMobileDataDownloadManager.addGroupForDownloadInternal(
455             groupKeyCaptor.capture(), any(DataFileGroupInternal.class), any()))
456         .thenReturn(Futures.immediateFuture(true));
457 
458     DataFileGroup dataFileGroup =
459         createDataFileGroup(
460             FILE_GROUP_NAME_1,
461             context.getPackageName(),
462             1 /* versionNumber */,
463             new String[] {FILE_ID_1},
464             new int[] {FILE_SIZE_1},
465             new String[] {FILE_CHECKSUM_1},
466             new String[] {FILE_URL_1},
467             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
468 
469     MobileDataDownload mobileDataDownload =
470         new MobileDataDownloadImpl(
471             context,
472             mockEventLogger,
473             mockMobileDataDownloadManager,
474             controlExecutor,
475             ImmutableList.of() /* fileGroupPopulatorList */,
476             Optional.of(mockTaskScheduler),
477             fileStorage,
478             Optional.absent() /* downloadMonitorOptional */,
479             Optional.of(this.getClass()), // don't need to use the real foreground download service.
480             flags,
481             singleFileDownloader,
482             Optional.absent() /* customFileGroupValidator */,
483             timeSource);
484 
485     AddFileGroupRequest addFileGroupRequest =
486         AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build();
487 
488     assertThat(mobileDataDownload.addFileGroup(addFileGroupRequest).get()).isTrue();
489 
490     GroupKey groupKey =
491         GroupKey.newBuilder()
492             .setGroupName(FILE_GROUP_NAME_1)
493             .setOwnerPackage(context.getPackageName())
494             .build();
495     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
496     verify(mockMobileDataDownloadManager)
497         .addGroupForDownloadInternal(
498             eq(groupKey), eq(ProtoConversionUtil.convert(dataFileGroup)), any());
499   }
500 
501   @Test
removeFileGroup_onSuccess_returnsTrue()502   public void removeFileGroup_onSuccess_returnsTrue() throws Exception {
503     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
504     when(mockMobileDataDownloadManager.removeFileGroup(groupKeyCaptor.capture(), eq(false)))
505         .thenReturn(Futures.immediateFuture(null /* Void */));
506 
507     MobileDataDownload mobileDataDownload =
508         new MobileDataDownloadImpl(
509             context,
510             mockEventLogger,
511             mockMobileDataDownloadManager,
512             controlExecutor,
513             ImmutableList.of() /* fileGroupPopulatorList */,
514             Optional.of(mockTaskScheduler),
515             fileStorage,
516             Optional.absent() /* downloadMonitorOptional */,
517             Optional.of(this.getClass()), // don't need to use the real foreground download service.
518             flags,
519             singleFileDownloader,
520             Optional.absent() /* customFileGroupValidator */,
521             timeSource);
522 
523     RemoveFileGroupRequest removeFileGroupRequest =
524         RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build();
525 
526     assertThat(mobileDataDownload.removeFileGroup(removeFileGroupRequest).get()).isTrue();
527 
528     GroupKey groupKey =
529         GroupKey.newBuilder()
530             .setGroupName(FILE_GROUP_NAME_1)
531             .setOwnerPackage(context.getPackageName())
532             .build();
533     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
534   }
535 
536   @Test
removeFileGroup_onFailure_returnsFalse()537   public void removeFileGroup_onFailure_returnsFalse() throws Exception {
538     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
539     doThrow(new IOException())
540         .when(mockMobileDataDownloadManager)
541         .removeFileGroup(groupKeyCaptor.capture(), /* pendingOnly= */ eq(false));
542 
543     MobileDataDownload mobileDataDownload =
544         new MobileDataDownloadImpl(
545             context,
546             mockEventLogger,
547             mockMobileDataDownloadManager,
548             controlExecutor,
549             ImmutableList.of() /* fileGroupPopulatorList */,
550             Optional.of(mockTaskScheduler),
551             fileStorage,
552             Optional.absent() /* downloadMonitorOptional */,
553             Optional.of(this.getClass()), // don't need to use the real foreground download service.
554             flags,
555             singleFileDownloader,
556             Optional.absent() /* customFileGroupValidator */,
557             timeSource);
558 
559     RemoveFileGroupRequest removeFileGroupRequest =
560         RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build();
561 
562     ExecutionException exception =
563         assertThrows(
564             ExecutionException.class,
565             () -> mobileDataDownload.removeFileGroup(removeFileGroupRequest).get());
566     assertThat(exception).hasCauseThat().isInstanceOf(IOException.class);
567 
568     GroupKey groupKey =
569         GroupKey.newBuilder()
570             .setGroupName(FILE_GROUP_NAME_1)
571             .setOwnerPackage(context.getPackageName())
572             .build();
573     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
574   }
575 
576   @Test
removeFileGroup_withAccount_returnsTrue()577   public void removeFileGroup_withAccount_returnsTrue() throws Exception {
578     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
579     when(mockMobileDataDownloadManager.removeFileGroup(
580             groupKeyCaptor.capture(), /* pendingOnly= */ eq(false)))
581         .thenReturn(Futures.immediateFuture(null /* Void */));
582 
583     MobileDataDownload mobileDataDownload =
584         new MobileDataDownloadImpl(
585             context,
586             mockEventLogger,
587             mockMobileDataDownloadManager,
588             controlExecutor,
589             ImmutableList.of() /* fileGroupPopulatorList */,
590             Optional.of(mockTaskScheduler),
591             fileStorage,
592             Optional.absent() /* downloadMonitorOptional */,
593             Optional.of(this.getClass()), // don't need to use the real foreground download service.
594             flags,
595             singleFileDownloader,
596             Optional.absent() /* customFileGroupValidator */,
597             timeSource);
598 
599     Account account = AccountUtil.create("account-name", "account-type");
600     RemoveFileGroupRequest removeFileGroupRequest =
601         RemoveFileGroupRequest.newBuilder()
602             .setGroupName(FILE_GROUP_NAME_1)
603             .setAccountOptional(Optional.of(account))
604             .build();
605 
606     assertThat(mobileDataDownload.removeFileGroup(removeFileGroupRequest).get()).isTrue();
607 
608     GroupKey groupKey =
609         GroupKey.newBuilder()
610             .setGroupName(FILE_GROUP_NAME_1)
611             .setOwnerPackage(context.getPackageName())
612             .setAccount(AccountUtil.serialize(account))
613             .build();
614     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
615   }
616 
617   @Test
removeFileGroup_withVariantId_returnsTrue()618   public void removeFileGroup_withVariantId_returnsTrue() throws Exception {
619     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
620     when(mockMobileDataDownloadManager.removeFileGroup(
621             groupKeyCaptor.capture(), /* pendingOnly= */ eq(false)))
622         .thenReturn(Futures.immediateFuture(null /* Void */));
623 
624     MobileDataDownload mobileDataDownload =
625         new MobileDataDownloadImpl(
626             context,
627             mockEventLogger,
628             mockMobileDataDownloadManager,
629             controlExecutor,
630             ImmutableList.of() /* fileGroupPopulatorList */,
631             Optional.of(mockTaskScheduler),
632             fileStorage,
633             Optional.absent() /* downloadMonitorOptional */,
634             Optional.of(this.getClass()), // don't need to use the real foreground download service.
635             flags,
636             singleFileDownloader,
637             Optional.absent() /* customFileGroupValidator */,
638             timeSource);
639 
640     RemoveFileGroupRequest removeFileGroupRequest =
641         RemoveFileGroupRequest.newBuilder()
642             .setGroupName(FILE_GROUP_NAME_1)
643             .setVariantIdOptional(Optional.of("en"))
644             .build();
645 
646     assertThat(mobileDataDownload.removeFileGroup(removeFileGroupRequest).get()).isTrue();
647 
648     GroupKey groupKey =
649         GroupKey.newBuilder()
650             .setGroupName(FILE_GROUP_NAME_1)
651             .setOwnerPackage(context.getPackageName())
652             .setVariantId("en")
653             .build();
654     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
655   }
656 
657   @Test
getFileGroup()658   public void getFileGroup() throws Exception {
659     DataFileGroupInternal dataFileGroup =
660         FILE_GROUP_INTERNAL_1.toBuilder().setBuildId(10).setVariantId("test-variant").build();
661     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
662         .thenReturn(Futures.immediateFuture(dataFileGroup));
663     when(mockMobileDataDownloadManager.getDataFileUris(
664             dataFileGroup, /* verifyIsolatedStructure= */ true))
665         .thenReturn(
666             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
667 
668     MobileDataDownload mobileDataDownload =
669         new MobileDataDownloadImpl(
670             context,
671             mockEventLogger,
672             mockMobileDataDownloadManager,
673             controlExecutor,
674             ImmutableList.of() /* fileGroupPopulatorList */,
675             Optional.of(mockTaskScheduler),
676             fileStorage,
677             Optional.absent() /* downloadMonitorOptional */,
678             Optional.of(this.getClass()), // don't need to use the real foreground download service.
679             flags,
680             singleFileDownloader,
681             Optional.absent() /* customFileGroupValidator */,
682             timeSource);
683 
684     ClientFileGroup clientFileGroup =
685         mobileDataDownload
686             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
687             .get();
688     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
689     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
690     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
691     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
692     assertThat(clientFileGroup.hasAccount()).isFalse();
693 
694     ClientFile clientFile = clientFileGroup.getFileList().get(0);
695     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
696     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
697   }
698 
699   @Test
getFileGroup_withDirectory()700   public void getFileGroup_withDirectory() throws Exception {
701     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
702         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
703     when(mockMobileDataDownloadManager.getDataFileUris(
704             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
705         .thenReturn(
706             Futures.immediateFuture(
707                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceDirUri)));
708 
709     MobileDataDownload mobileDataDownload =
710         new MobileDataDownloadImpl(
711             context,
712             mockEventLogger,
713             mockMobileDataDownloadManager,
714             controlExecutor,
715             ImmutableList.of() /* fileGroupPopulatorList */,
716             Optional.of(mockTaskScheduler),
717             fileStorage,
718             Optional.absent() /* downloadMonitorOptional */,
719             Optional.of(this.getClass()), // don't need to use the real foreground download service.
720             flags,
721             singleFileDownloader,
722             Optional.absent() /* customFileGroupValidator */,
723             timeSource);
724 
725     ClientFileGroup clientFileGroup =
726         mobileDataDownload
727             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
728             .get();
729     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
730     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
731     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
732     assertThat(clientFileGroup.getFileCount()).isEqualTo(3);
733     assertThat(clientFileGroup.hasAccount()).isFalse();
734 
735     List<ClientFile> clientFileList = clientFileGroup.getFileList();
736     assertThat(clientFileList)
737         .contains(
738             ClientFile.newBuilder()
739                 .setFileId("/file_1")
740                 .setFileUri(onDeviceDirFileUri1.toString())
741                 .setFullSizeInBytes(onDeviceDirFile1Content.getBytes(UTF_8).length)
742                 .build());
743     assertThat(clientFileList)
744         .contains(
745             ClientFile.newBuilder()
746                 .setFileId("/file_2")
747                 .setFileUri(onDeviceDirFileUri2.toString())
748                 .setFullSizeInBytes(onDeviceDirFile2Content.getBytes(UTF_8).length)
749                 .build());
750     assertThat(clientFileList)
751         .contains(
752             ClientFile.newBuilder()
753                 .setFileId("/sub/file")
754                 .setFileUri(onDeviceDirFileUri3.toString())
755                 .setFullSizeInBytes(onDeviceDirFile3Content.getBytes(UTF_8).length)
756                 .build());
757   }
758 
759   @Test
getFileGroup_withDirectory_withTraverseDisabled()760   public void getFileGroup_withDirectory_withTraverseDisabled() throws Exception {
761     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
762         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
763     when(mockMobileDataDownloadManager.getDataFileUris(
764             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
765         .thenReturn(
766             Futures.immediateFuture(
767                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceDirUri)));
768 
769     MobileDataDownload mobileDataDownload =
770         new MobileDataDownloadImpl(
771             context,
772             mockEventLogger,
773             mockMobileDataDownloadManager,
774             controlExecutor,
775             ImmutableList.of() /* fileGroupPopulatorList */,
776             Optional.of(mockTaskScheduler),
777             fileStorage,
778             Optional.absent() /* downloadMonitorOptional */,
779             Optional.of(this.getClass()), // don't need to use the real foreground download service.
780             flags,
781             singleFileDownloader,
782             Optional.absent() /* customFileGroupValidator */,
783             timeSource);
784 
785     ClientFileGroup clientFileGroup =
786         mobileDataDownload
787             .getFileGroup(
788                 GetFileGroupRequest.newBuilder()
789                     .setPreserveZipDirectories(true)
790                     .setGroupName(FILE_GROUP_NAME_1)
791                     .build())
792             .get();
793     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
794     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
795     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
796     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
797     assertThat(clientFileGroup.hasAccount()).isFalse();
798 
799     List<ClientFile> clientFileList = clientFileGroup.getFileList();
800     assertThat(clientFileList)
801         .contains(
802             ClientFile.newBuilder()
803                 .setFileId("test-file-1")
804                 .setFileUri(onDeviceDirUri.toString())
805                 .setFullSizeInBytes(FILE_SIZE_1)
806                 .build());
807     assertThat(fileStorage.isDirectory(Uri.parse(clientFileList.get(0).getFileUri()))).isTrue();
808   }
809 
810   @Test
removeFileGroupsByFilter_withAccountSpecified_removesMatchingAccountGroups()811   public void removeFileGroupsByFilter_withAccountSpecified_removesMatchingAccountGroups()
812       throws Exception {
813     List<GroupKeyAndGroup> keyToGroupList = new ArrayList<>();
814     Account account1 = AccountUtil.create("account-name", "account-type");
815     Account account2 = AccountUtil.create("account-name2", "account-type");
816 
817     DataFileGroupInternal downloadedFileGroup = FILE_GROUP_INTERNAL_1.toBuilder().build();
818     DataFileGroupInternal pendingFileGroup =
819         FILE_GROUP_INTERNAL_1.toBuilder().setFileGroupVersionNumber(6).build();
820 
821     GroupKey account1GroupKey =
822         GroupKey.newBuilder()
823             .setGroupName(FILE_GROUP_NAME_1)
824             .setOwnerPackage(context.getPackageName())
825             .setAccount(AccountUtil.serialize(account1))
826             .build();
827     GroupKey downloadedAccount1GroupKey = account1GroupKey.toBuilder().setDownloaded(true).build();
828     GroupKey pendingAccount1GroupKey = account1GroupKey.toBuilder().setDownloaded(false).build();
829 
830     GroupKey account2GroupKey =
831         GroupKey.newBuilder()
832             .setGroupName(FILE_GROUP_NAME_1)
833             .setOwnerPackage(context.getPackageName())
834             .setAccount(AccountUtil.serialize(account2))
835             .build();
836     GroupKey downloadedAccount2GroupKey = account2GroupKey.toBuilder().setDownloaded(true).build();
837     GroupKey pendingAccount2GroupKey = account2GroupKey.toBuilder().setDownloaded(false).build();
838 
839     GroupKey noAccountGroupKey =
840         GroupKey.newBuilder()
841             .setGroupName(FILE_GROUP_NAME_1)
842             .setOwnerPackage(context.getPackageName())
843             .build();
844     GroupKey downloadedGroupKey = noAccountGroupKey.toBuilder().setDownloaded(true).build();
845     GroupKey pendingGroupKey = noAccountGroupKey.toBuilder().setDownloaded(false).build();
846 
847     keyToGroupList.add(GroupKeyAndGroup.create(downloadedGroupKey, downloadedFileGroup));
848     keyToGroupList.add(GroupKeyAndGroup.create(downloadedAccount1GroupKey, downloadedFileGroup));
849     keyToGroupList.add(GroupKeyAndGroup.create(downloadedAccount2GroupKey, downloadedFileGroup));
850     keyToGroupList.add(GroupKeyAndGroup.create(pendingGroupKey, pendingFileGroup));
851     keyToGroupList.add(GroupKeyAndGroup.create(pendingAccount1GroupKey, pendingFileGroup));
852     keyToGroupList.add(GroupKeyAndGroup.create(pendingAccount2GroupKey, pendingFileGroup));
853 
854     when(mockMobileDataDownloadManager.getAllFreshGroups())
855         .thenReturn(Futures.immediateFuture(keyToGroupList));
856     when(mockMobileDataDownloadManager.removeFileGroups(groupKeysCaptor.capture()))
857         .thenReturn(Futures.immediateVoidFuture());
858 
859     MobileDataDownload mobileDataDownload =
860         new MobileDataDownloadImpl(
861             context,
862             mockEventLogger,
863             mockMobileDataDownloadManager,
864             controlExecutor,
865             /* fileGroupPopulatorList= */ ImmutableList.of(),
866             Optional.of(mockTaskScheduler),
867             fileStorage,
868             /* downloadMonitorOptional= */ Optional.absent(),
869             /* foregroundDownloadServiceClassOptional= */ Optional.absent(),
870             flags,
871             singleFileDownloader,
872             Optional.absent() /* customFileGroupValidator */,
873             timeSource);
874 
875     // Setup request that matches all fresh groups, but also include account to make sure only
876     // account associated file groups are removed
877     RemoveFileGroupsByFilterRequest removeFileGroupsByFilterRequest =
878         RemoveFileGroupsByFilterRequest.newBuilder()
879             .setAccountOptional(Optional.of(account1))
880             .build();
881 
882     RemoveFileGroupsByFilterResponse response =
883         mobileDataDownload.removeFileGroupsByFilter(removeFileGroupsByFilterRequest).get();
884 
885     assertThat(response.removedFileGroupsCount()).isEqualTo(1);
886     verify(mockMobileDataDownloadManager, times(1)).removeFileGroups(anyList());
887     List<GroupKey> removedGroupKeys = groupKeysCaptor.getValue();
888     assertThat(removedGroupKeys).containsExactly(account1GroupKey);
889   }
890 
891   @Test
getFileGroup_nullFileUri()892   public void getFileGroup_nullFileUri() throws Exception {
893     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
894         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
895     when(mockMobileDataDownloadManager.getDataFileUris(
896             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
897         .thenReturn(
898             Futures.immediateFailedFuture(
899                 DownloadException.builder()
900                     .setDownloadResultCode(DownloadResultCode.UNKNOWN_ERROR)
901                     .setMessage("Fail to download file group")
902                     .build()));
903 
904     MobileDataDownload mobileDataDownload =
905         new MobileDataDownloadImpl(
906             context,
907             mockEventLogger,
908             mockMobileDataDownloadManager,
909             controlExecutor,
910             ImmutableList.of() /* fileGroupPopulatorList */,
911             Optional.of(mockTaskScheduler),
912             fileStorage,
913             Optional.absent() /* downloadMonitorOptional */,
914             Optional.of(this.getClass()), // don't need to use the real foreground download service.
915             flags,
916             singleFileDownloader,
917             Optional.absent() /* customFileGroupValidator */,
918             timeSource);
919 
920     assertNull(
921         mobileDataDownload
922             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
923             .get());
924   }
925 
926   @Test
getFileGroup_null()927   public void getFileGroup_null() throws Exception {
928     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
929         .thenReturn(Futures.immediateFuture(null));
930 
931     MobileDataDownload mobileDataDownload =
932         new MobileDataDownloadImpl(
933             context,
934             mockEventLogger,
935             mockMobileDataDownloadManager,
936             controlExecutor,
937             ImmutableList.of() /* fileGroupPopulatorList */,
938             Optional.of(mockTaskScheduler),
939             fileStorage,
940             Optional.absent() /* downloadMonitorOptional */,
941             Optional.of(this.getClass()), // don't need to use the real foreground download service.
942             flags,
943             singleFileDownloader,
944             Optional.absent() /* customFileGroupValidator */,
945             timeSource);
946 
947     assertNull(
948         mobileDataDownload
949             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
950             .get());
951   }
952 
953   @Test
getFileGroup_withAccount()954   public void getFileGroup_withAccount() throws Exception {
955     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
956     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), eq(true)))
957         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
958     when(mockMobileDataDownloadManager.getDataFileUris(
959             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
960         .thenReturn(
961             Futures.immediateFuture(
962                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceUri1)));
963 
964     MobileDataDownload mobileDataDownload =
965         new MobileDataDownloadImpl(
966             context,
967             mockEventLogger,
968             mockMobileDataDownloadManager,
969             controlExecutor,
970             ImmutableList.of() /* fileGroupPopulatorList */,
971             Optional.of(mockTaskScheduler),
972             fileStorage,
973             Optional.absent() /* downloadMonitorOptional */,
974             Optional.of(this.getClass()), // don't need to use the real foreground download service.
975             flags,
976             singleFileDownloader,
977             Optional.absent() /* customFileGroupValidator */,
978             timeSource);
979 
980     Account account = AccountUtil.create("account-name", "account-type");
981     ClientFileGroup clientFileGroup =
982         mobileDataDownload
983             .getFileGroup(
984                 GetFileGroupRequest.newBuilder()
985                     .setGroupName(FILE_GROUP_NAME_1)
986                     .setAccountOptional(Optional.of(account))
987                     .build())
988             .get();
989     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
990     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
991     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
992     assertThat(clientFileGroup.getAccount()).isEqualTo(AccountUtil.serialize(account));
993     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
994 
995     ClientFile clientFile = clientFileGroup.getFileList().get(0);
996     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
997     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
998 
999     GroupKey groupKey =
1000         GroupKey.newBuilder()
1001             .setGroupName(FILE_GROUP_NAME_1)
1002             .setOwnerPackage(context.getPackageName())
1003             .setAccount(AccountUtil.serialize(account))
1004             .build();
1005     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
1006   }
1007 
1008   @Test
getFileGroup_withVariantId()1009   public void getFileGroup_withVariantId() throws Exception {
1010     DataFileGroupInternal dataFileGroup =
1011         FILE_GROUP_INTERNAL_1.toBuilder().setVariantId("en").build();
1012 
1013     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
1014     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), eq(true)))
1015         .thenReturn(Futures.immediateFuture(dataFileGroup));
1016     when(mockMobileDataDownloadManager.getDataFileUris(
1017             dataFileGroup, /* verifyIsolatedStructure= */ true))
1018         .thenReturn(
1019             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
1020 
1021     MobileDataDownload mobileDataDownload =
1022         new MobileDataDownloadImpl(
1023             context,
1024             mockEventLogger,
1025             mockMobileDataDownloadManager,
1026             controlExecutor,
1027             ImmutableList.of() /* fileGroupPopulatorList */,
1028             Optional.of(mockTaskScheduler),
1029             fileStorage,
1030             Optional.absent() /* downloadMonitorOptional */,
1031             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1032             flags,
1033             singleFileDownloader,
1034             Optional.absent() /* customFileGroupValidator */,
1035             timeSource);
1036 
1037     ClientFileGroup clientFileGroup =
1038         mobileDataDownload
1039             .getFileGroup(
1040                 GetFileGroupRequest.newBuilder()
1041                     .setGroupName(FILE_GROUP_NAME_1)
1042                     .setVariantIdOptional(Optional.of("en"))
1043                     .build())
1044             .get();
1045 
1046     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1047     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
1048     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
1049     assertThat(clientFileGroup.getAccount()).isEmpty();
1050     assertThat(clientFileGroup.getVariantId()).isEqualTo("en");
1051     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
1052 
1053     GroupKey groupKey =
1054         GroupKey.newBuilder()
1055             .setGroupName(FILE_GROUP_NAME_1)
1056             .setOwnerPackage(context.getPackageName())
1057             .setVariantId("en")
1058             .build();
1059     assertThat(groupKeyCaptor.getValue()).isEqualTo(groupKey);
1060   }
1061 
1062   @Test
getFileGroup_includesIdentifyingProperties()1063   public void getFileGroup_includesIdentifyingProperties() throws Exception {
1064     Any customProperty =
1065         Any.newBuilder()
1066             .setTypeUrl("type.googleapis.com/google.protobuf.stringvalue")
1067             .setValue(StringValue.of("TEST_PROPERTY").toByteString())
1068             .build();
1069     DataFileGroupInternal dataFileGroup =
1070         FILE_GROUP_INTERNAL_1.toBuilder()
1071             .setBuildId(1L)
1072             .setVariantId("testvariant")
1073             .setCustomProperty(customProperty)
1074             .build();
1075 
1076     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1077         .thenReturn(Futures.immediateFuture(dataFileGroup));
1078     when(mockMobileDataDownloadManager.getDataFileUris(
1079             dataFileGroup, /* verifyIsolatedStructure= */ true))
1080         .thenReturn(
1081             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
1082 
1083     MobileDataDownload mobileDataDownload =
1084         new MobileDataDownloadImpl(
1085             context,
1086             mockEventLogger,
1087             mockMobileDataDownloadManager,
1088             controlExecutor,
1089             ImmutableList.of() /* fileGroupPopulatorList */,
1090             Optional.of(mockTaskScheduler),
1091             fileStorage,
1092             Optional.absent() /* downloadMonitorOptional */,
1093             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1094             flags,
1095             singleFileDownloader,
1096             Optional.absent() /* customFileGroupValidator */,
1097             timeSource);
1098 
1099     ClientFileGroup clientFileGroup =
1100         mobileDataDownload
1101             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1102             .get();
1103 
1104     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1105     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
1106     assertThat(clientFileGroup.getBuildId()).isEqualTo(1L);
1107     assertThat(clientFileGroup.getVariantId()).isEqualTo("testvariant");
1108     assertThat(clientFileGroup.getCustomProperty()).isEqualTo(customProperty);
1109   }
1110 
1111   @Test
getFileGroup_includesLocale()1112   public void getFileGroup_includesLocale() throws Exception {
1113     DataFileGroupInternal dataFileGroup =
1114         FILE_GROUP_INTERNAL_1.toBuilder().addLocale("en-US").addLocale("en-CA").build();
1115 
1116     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1117         .thenReturn(Futures.immediateFuture(dataFileGroup));
1118     when(mockMobileDataDownloadManager.getDataFileUris(
1119             dataFileGroup, /* verifyIsolatedStructure= */ true))
1120         .thenReturn(
1121             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
1122 
1123     MobileDataDownload mobileDataDownload =
1124         new MobileDataDownloadImpl(
1125             context,
1126             mockEventLogger,
1127             mockMobileDataDownloadManager,
1128             controlExecutor,
1129             ImmutableList.of() /* fileGroupPopulatorList */,
1130             Optional.of(mockTaskScheduler),
1131             fileStorage,
1132             Optional.absent() /* downloadMonitorOptional */,
1133             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1134             flags,
1135             singleFileDownloader,
1136             Optional.absent() /* customFileGroupValidator */,
1137             timeSource);
1138 
1139     ClientFileGroup clientFileGroup =
1140         mobileDataDownload
1141             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1142             .get();
1143 
1144     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1145     assertThat(clientFileGroup.getLocaleList()).containsExactly("en-US", "en-CA");
1146   }
1147 
1148   @Test
getFileGroup_includesGroupLevelMetadataWhenProvided()1149   public void getFileGroup_includesGroupLevelMetadataWhenProvided() throws Exception {
1150     Any customMetadata =
1151         Any.newBuilder()
1152             .setTypeUrl("type.googleapis.com/google.protobuf.stringvalue")
1153             .setValue(StringValue.of("TEST_METADATA").toByteString())
1154             .build();
1155     DataFileGroupInternal dataFileGroup =
1156         FILE_GROUP_INTERNAL_1.toBuilder().setCustomMetadata(customMetadata).build();
1157 
1158     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1159         .thenReturn(Futures.immediateFuture(dataFileGroup));
1160     when(mockMobileDataDownloadManager.getDataFileUris(
1161             dataFileGroup, /* verifyIsolatedStructure= */ true))
1162         .thenReturn(
1163             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
1164 
1165     MobileDataDownload mobileDataDownload =
1166         new MobileDataDownloadImpl(
1167             context,
1168             mockEventLogger,
1169             mockMobileDataDownloadManager,
1170             controlExecutor,
1171             ImmutableList.of() /* fileGroupPopulatorList */,
1172             Optional.of(mockTaskScheduler),
1173             fileStorage,
1174             Optional.absent() /* downloadMonitorOptional */,
1175             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1176             flags,
1177             singleFileDownloader,
1178             Optional.absent() /* customFileGroupValidator */,
1179             timeSource);
1180 
1181     ClientFileGroup clientFileGroup =
1182         mobileDataDownload
1183             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1184             .get();
1185 
1186     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1187     assertThat(clientFileGroup.getCustomMetadata()).isEqualTo(customMetadata);
1188   }
1189 
1190   @Test
getFileGroup_includesFileLevelMetadataWhenProvided()1191   public void getFileGroup_includesFileLevelMetadataWhenProvided() throws Exception {
1192     Any customMetadata =
1193         Any.newBuilder()
1194             .setTypeUrl("type.googleapis.com/google.protobuf.stringvalue")
1195             .setValue(StringValue.of("TEST_METADATA").toByteString())
1196             .build();
1197     DataFileGroupInternal dataFileGroup =
1198         DataFileGroupInternal.newBuilder()
1199             .setGroupName(FILE_GROUP_NAME_1)
1200             .setOwnerPackage(context.getPackageName())
1201             .addFile(
1202                 MetadataProto.DataFile.newBuilder()
1203                     .setFileId(FILE_ID_1)
1204                     .setUrlToDownload(FILE_URL_1)
1205                     .setCustomMetadata(customMetadata)
1206                     .build())
1207             .addFile(
1208                 MetadataProto.DataFile.newBuilder()
1209                     .setFileId(FILE_ID_2)
1210                     .setUrlToDownload(FILE_URL_2)
1211                     .build())
1212             .build();
1213 
1214     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1215         .thenReturn(Futures.immediateFuture(dataFileGroup));
1216     when(mockMobileDataDownloadManager.getDataFileUris(
1217             dataFileGroup, /* verifyIsolatedStructure= */ true))
1218         .thenReturn(
1219             Futures.immediateFuture(
1220                 ImmutableMap.of(
1221                     dataFileGroup.getFile(0),
1222                     onDeviceUri1,
1223                     dataFileGroup.getFile(1),
1224                     onDeviceUri1)));
1225 
1226     MobileDataDownload mobileDataDownload =
1227         new MobileDataDownloadImpl(
1228             context,
1229             mockEventLogger,
1230             mockMobileDataDownloadManager,
1231             controlExecutor,
1232             ImmutableList.of() /* fileGroupPopulatorList */,
1233             Optional.of(mockTaskScheduler),
1234             fileStorage,
1235             Optional.absent() /* downloadMonitorOptional */,
1236             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1237             flags,
1238             singleFileDownloader,
1239             Optional.absent() /* customFileGroupValidator */,
1240             timeSource);
1241 
1242     ClientFileGroup clientFileGroup =
1243         mobileDataDownload
1244             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1245             .get();
1246 
1247     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1248     assertThat(clientFileGroup.hasCustomMetadata()).isFalse();
1249 
1250     ClientFile clientFile1 =
1251         clientFileGroup.getFile(0).getFileId().equals(FILE_ID_1)
1252             ? clientFileGroup.getFile(0)
1253             : clientFileGroup.getFile(1);
1254     ClientFile clientFile2 =
1255         clientFileGroup.getFile(0).getFileId().equals(FILE_ID_1)
1256             ? clientFileGroup.getFile(1)
1257             : clientFileGroup.getFile(0);
1258 
1259     assertThat(clientFile1.hasCustomMetadata()).isTrue();
1260     assertThat(clientFile1.getCustomMetadata()).isEqualTo(customMetadata);
1261     assertThat(clientFile2.hasCustomMetadata()).isFalse();
1262   }
1263 
1264   @Test
getFileGroup_whenVerifyIsolatedStructureIsFalse_skipsStructureVerification()1265   public void getFileGroup_whenVerifyIsolatedStructureIsFalse_skipsStructureVerification()
1266       throws Exception {
1267     MetadataProto.DataFile isolatedStructureFile =
1268         MetadataProto.DataFile.newBuilder()
1269             .setFileId(FILE_ID_1)
1270             .setChecksumType(MetadataProto.DataFile.ChecksumType.NONE)
1271             .setUrlToDownload(FILE_URL_1)
1272             .setRelativeFilePath("mycustom/file.txt")
1273             .build();
1274     DataFileGroupInternal isolatedStructureGroup =
1275         DataFileGroupInternal.newBuilder()
1276             .setGroupName(FILE_GROUP_NAME_1)
1277             .setOwnerPackage(context.getPackageName())
1278             .setPreserveFilenamesAndIsolateFiles(true)
1279             .addFile(isolatedStructureFile)
1280             .build();
1281 
1282     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1283         .thenReturn(Futures.immediateFuture(isolatedStructureGroup));
1284     when(mockMobileDataDownloadManager.getDataFileUris(
1285             isolatedStructureGroup, /* verifyIsolatedStructure= */ false))
1286         .thenReturn(
1287             Futures.immediateFuture(
1288                 ImmutableMap.of(isolatedStructureGroup.getFile(0), onDeviceUri1)));
1289 
1290     MobileDataDownload mobileDataDownload =
1291         new MobileDataDownloadImpl(
1292             context,
1293             mockEventLogger,
1294             mockMobileDataDownloadManager,
1295             controlExecutor,
1296             ImmutableList.of() /* fileGroupPopulatorList */,
1297             Optional.of(mockTaskScheduler),
1298             fileStorage,
1299             Optional.absent() /* downloadMonitorOptional */,
1300             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1301             flags,
1302             singleFileDownloader,
1303             Optional.absent() /* customFileGroupValidator */,
1304             timeSource);
1305 
1306     ClientFileGroup clientFileGroup =
1307         mobileDataDownload
1308             .getFileGroup(
1309                 GetFileGroupRequest.newBuilder()
1310                     .setGroupName(FILE_GROUP_NAME_1)
1311                     .setVerifyIsolatedStructure(false)
1312                     .build())
1313             .get();
1314 
1315     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1316     assertThat(clientFileGroup.getFile(0).getFileUri()).isEqualTo(onDeviceUri1.toString());
1317 
1318     // Verify getting the file uri bypassed the verify check.
1319     verify(mockMobileDataDownloadManager, never()).getDataFileUris(any(), eq(true));
1320   }
1321 
1322   @Test
getFileGroup_whenVerifyIsolatedStructureIsTrue_returnsNullOnInvalidStructure()1323   public void getFileGroup_whenVerifyIsolatedStructureIsTrue_returnsNullOnInvalidStructure()
1324       throws Exception {
1325     MetadataProto.DataFile isolatedStructureFile =
1326         MetadataProto.DataFile.newBuilder()
1327             .setFileId(FILE_ID_1)
1328             .setChecksumType(MetadataProto.DataFile.ChecksumType.NONE)
1329             .setUrlToDownload(FILE_URL_1)
1330             .setRelativeFilePath("mycustom/file.txt")
1331             .build();
1332     DataFileGroupInternal isolatedStructureGroup =
1333         DataFileGroupInternal.newBuilder()
1334             .setGroupName(FILE_GROUP_NAME_1)
1335             .setOwnerPackage(context.getPackageName())
1336             .setPreserveFilenamesAndIsolateFiles(true)
1337             .addFile(isolatedStructureFile)
1338             .build();
1339 
1340     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1341         .thenReturn(Futures.immediateFuture(isolatedStructureGroup));
1342 
1343     // Mock that verification failed
1344     when(mockMobileDataDownloadManager.getDataFileUris(
1345             isolatedStructureGroup, /* verifyIsolatedStructure= */ true))
1346         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
1347 
1348     MobileDataDownload mobileDataDownload =
1349         new MobileDataDownloadImpl(
1350             context,
1351             mockEventLogger,
1352             mockMobileDataDownloadManager,
1353             controlExecutor,
1354             ImmutableList.of() /* fileGroupPopulatorList */,
1355             Optional.of(mockTaskScheduler),
1356             fileStorage,
1357             Optional.absent() /* downloadMonitorOptional */,
1358             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1359             flags,
1360             singleFileDownloader,
1361             Optional.absent() /* customFileGroupValidator */,
1362             timeSource);
1363 
1364     // Assert that a failure to verify the isolated structure returns a null group
1365     assertThat(
1366             mobileDataDownload
1367                 .getFileGroup(
1368                     GetFileGroupRequest.newBuilder()
1369                         .setGroupName(FILE_GROUP_NAME_1)
1370                         .setVerifyIsolatedStructure(true)
1371                         .build())
1372                 .get())
1373         .isNull();
1374 
1375     // Verify getting the file uri did not bypass the verify check.
1376     verify(mockMobileDataDownloadManager, never()).getDataFileUris(any(), eq(false));
1377   }
1378 
1379   @Test
getFileGroup_fileGroupFound_logsQueryStatsForFileGroup()1380   public void getFileGroup_fileGroupFound_logsQueryStatsForFileGroup() throws Exception {
1381     DataFileGroupInternal dataFileGroup =
1382         FILE_GROUP_INTERNAL_1.toBuilder().setBuildId(10).setVariantId("test-variant").build();
1383     when(mockMobileDataDownloadManager.getFileGroup(
1384             any(GroupKey.class), /* downloaded= */ eq(true)))
1385         .thenReturn(Futures.immediateFuture(dataFileGroup));
1386     when(mockMobileDataDownloadManager.getDataFileUris(
1387             dataFileGroup, /* verifyIsolatedStructure= */ true))
1388         .thenReturn(
1389             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
1390 
1391     MobileDataDownload mobileDataDownload =
1392         new MobileDataDownloadImpl(
1393             context,
1394             fakeEventLogger,
1395             mockMobileDataDownloadManager,
1396             controlExecutor,
1397             /* fileGroupPopulatorList= */ ImmutableList.of(),
1398             Optional.of(mockTaskScheduler),
1399             fileStorage,
1400             /* downloadMonitorOptional= */ Optional.absent(),
1401             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1402             flags,
1403             singleFileDownloader,
1404             /* customValidatorOptional= */ Optional.absent(),
1405             timeSource);
1406 
1407     ClientFileGroup unused =
1408         mobileDataDownload
1409             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1410             .get();
1411 
1412     List<DataDownloadFileGroupStats> fileGroupDetailsList =
1413         fakeEventLogger.getLoggedMddQueryStats();
1414 
1415     assertThat(fileGroupDetailsList).hasSize(1);
1416     DataDownloadFileGroupStats fileGroupStats = fileGroupDetailsList.get(0);
1417     assertThat(fileGroupStats.getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1418     assertThat(fileGroupStats.getOwnerPackage()).isEqualTo(context.getPackageName());
1419     assertThat(fileGroupStats.getFileGroupVersionNumber()).isEqualTo(5);
1420     assertThat(fileGroupStats.getBuildId()).isEqualTo(10);
1421     assertThat(fileGroupStats.getVariantId()).isEqualTo("test-variant");
1422     assertThat(fileGroupStats.getFileCount()).isEqualTo(1);
1423   }
1424 
1425   @Test
getFileGroup_fileGroupFound_doesNotOverLog()1426   public void getFileGroup_fileGroupFound_doesNotOverLog() throws Exception {
1427     DataFileGroupInternal dataFileGroup = FILE_GROUP_INTERNAL_1;
1428     when(mockMobileDataDownloadManager.getFileGroup(
1429             any(GroupKey.class), /* downloaded= */ eq(true)))
1430         .thenReturn(Futures.immediateFuture(dataFileGroup));
1431     when(mockMobileDataDownloadManager.getDataFileUris(
1432             dataFileGroup, /* verifyIsolatedStructure= */ true))
1433         .thenReturn(
1434             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
1435 
1436     MobileDataDownload mobileDataDownload =
1437         new MobileDataDownloadImpl(
1438             context,
1439             mockEventLogger,
1440             mockMobileDataDownloadManager,
1441             controlExecutor,
1442             /* fileGroupPopulatorList= */ ImmutableList.of(),
1443             Optional.of(mockTaskScheduler),
1444             fileStorage,
1445             /* downloadMonitorOptional= */ Optional.absent(),
1446             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1447             flags,
1448             singleFileDownloader,
1449             /* customValidatorOptional= */ Optional.absent(),
1450             timeSource);
1451 
1452     ClientFileGroup unused =
1453         mobileDataDownload
1454             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1455             .get();
1456 
1457     verify(mockEventLogger).logMddQueryStats(any());
1458     verify(mockEventLogger).logMddLibApiResultLog(any());
1459     verifyNoMoreInteractions(mockEventLogger);
1460   }
1461 
1462   @Test
getFileGroup_fileGroupNotFound_doesNotOverLog()1463   public void getFileGroup_fileGroupNotFound_doesNotOverLog() throws Exception {
1464     when(mockMobileDataDownloadManager.getFileGroup(
1465             any(GroupKey.class), /* downloaded= */ eq(true)))
1466         .thenReturn(Futures.immediateFuture(null));
1467 
1468     MobileDataDownload mobileDataDownload =
1469         new MobileDataDownloadImpl(
1470             context,
1471             mockEventLogger,
1472             mockMobileDataDownloadManager,
1473             controlExecutor,
1474             /* fileGroupPopulatorList= */ ImmutableList.of(),
1475             Optional.of(mockTaskScheduler),
1476             fileStorage,
1477             /* downloadMonitorOptional= */ Optional.absent(),
1478             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1479             flags,
1480             singleFileDownloader,
1481             /* customValidatorOptional= */ Optional.absent(),
1482             timeSource);
1483 
1484     ClientFileGroup unused =
1485         mobileDataDownload
1486             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1487             .get();
1488 
1489     verify(mockEventLogger).logMddLibApiResultLog(any());
1490     verifyNoMoreInteractions(mockEventLogger);
1491   }
1492 
1493   @Test
getFileGroup_throwsException_doesNotOverLog()1494   public void getFileGroup_throwsException_doesNotOverLog() throws Exception {
1495     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1496         .thenReturn(Futures.immediateFailedFuture(new Exception()));
1497 
1498     MobileDataDownload mobileDataDownload =
1499         new MobileDataDownloadImpl(
1500             context,
1501             mockEventLogger,
1502             mockMobileDataDownloadManager,
1503             controlExecutor,
1504             /* fileGroupPopulatorList= */ ImmutableList.of(),
1505             Optional.of(mockTaskScheduler),
1506             fileStorage,
1507             /* downloadMonitorOptional= */ Optional.absent(),
1508             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1509             flags,
1510             singleFileDownloader,
1511             /* customValidatorOptional= */ Optional.absent(),
1512             timeSource);
1513 
1514     assertThrows(
1515         ExecutionException.class,
1516         () ->
1517             mobileDataDownload
1518                 .getFileGroup(
1519                     GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1520                 .get());
1521 
1522     verify(mockEventLogger).logMddLibApiResultLog(any());
1523     verifyNoMoreInteractions(mockEventLogger);
1524   }
1525 
1526   /**
1527    * Helper function to test that expected errors are being logged.
1528    *
1529    * <p>causeThrowable is used to check for cause only if expectedThrowable is instance of
1530    * ExecutionException.
1531    */
getFileGroupErrorLoggingTestHelper( ListenableFuture<?> getFileGroupResultFuture, Class<T> expectedThrowable, Class<?> causeThrowable, int code)1532   private <T extends Throwable> void getFileGroupErrorLoggingTestHelper(
1533       ListenableFuture<?> getFileGroupResultFuture,
1534       Class<T> expectedThrowable,
1535       Class<?> causeThrowable,
1536       int code)
1537       throws Exception {
1538     long latencyNs = 1000;
1539     when(mockMobileDataDownloadManager.getFileGroup(
1540             any(GroupKey.class), /* downloaded= */ eq(true)))
1541         .thenAnswer(
1542             invocation -> {
1543               // Advancing time source to test latency.
1544               timeSource.advance(latencyNs, TimeUnit.NANOSECONDS);
1545               return getFileGroupResultFuture;
1546             });
1547 
1548     MobileDataDownload mobileDataDownload =
1549         new MobileDataDownloadImpl(
1550             context,
1551             fakeEventLogger,
1552             mockMobileDataDownloadManager,
1553             controlExecutor,
1554             /* fileGroupPopulatorList= */ ImmutableList.of(),
1555             Optional.of(mockTaskScheduler),
1556             fileStorage,
1557             /* downloadMonitorOptional= */ Optional.absent(),
1558             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1559             flags,
1560             singleFileDownloader,
1561             /* customValidatorOptional= */ Optional.absent(),
1562             timeSource);
1563 
1564     Throwable thrown =
1565         assertThrows(
1566             expectedThrowable,
1567             () ->
1568                 mobileDataDownload
1569                     .getFileGroup(
1570                         GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1571                     .get());
1572 
1573     if (thrown instanceof ExecutionException) {
1574       assertThat(thrown).hasCauseThat().isInstanceOf(causeThrowable);
1575     }
1576   }
1577 
1578   @Test
getFileGroup_throwsCancelledException_logsCancelled()1579   public void getFileGroup_throwsCancelledException_logsCancelled() throws Exception {
1580     getFileGroupErrorLoggingTestHelper(
1581         Futures.immediateCancelledFuture(), CancellationException.class, null, 0);
1582   }
1583 
1584   @Test
getFileGroup_throwsUnknownException_logsFailureWithoutCause()1585   public void getFileGroup_throwsUnknownException_logsFailureWithoutCause() throws Exception {
1586     getFileGroupErrorLoggingTestHelper(
1587         Futures.immediateFailedFuture(new Exception()),
1588         ExecutionException.class,
1589         Exception.class,
1590         0);
1591   }
1592 
1593   @Test
getFileGroup_throwsInterruptedException_logsInterrupted()1594   public void getFileGroup_throwsInterruptedException_logsInterrupted() throws Exception {
1595     getFileGroupErrorLoggingTestHelper(
1596         Futures.immediateFailedFuture(new InterruptedException()),
1597         ExecutionException.class,
1598         InterruptedException.class,
1599         0);
1600   }
1601 
1602   @Test
getFileGroup_throwsIOException_logsIOError()1603   public void getFileGroup_throwsIOException_logsIOError() throws Exception {
1604     getFileGroupErrorLoggingTestHelper(
1605         Futures.immediateFailedFuture(new IOException()),
1606         ExecutionException.class,
1607         IOException.class,
1608         0);
1609   }
1610 
1611   @Test
getFileGroup_throwsIllegalStateException_logsIllegalState()1612   public void getFileGroup_throwsIllegalStateException_logsIllegalState() throws Exception {
1613     getFileGroupErrorLoggingTestHelper(
1614         Futures.immediateFailedFuture(new IllegalStateException()),
1615         ExecutionException.class,
1616         IllegalStateException.class,
1617         0);
1618   }
1619 
1620   @Test
getFileGroup_throwsIllegalArgumentException_logsIllegalArgument()1621   public void getFileGroup_throwsIllegalArgumentException_logsIllegalArgument() throws Exception {
1622     getFileGroupErrorLoggingTestHelper(
1623         Futures.immediateFailedFuture(new IllegalArgumentException()),
1624         ExecutionException.class,
1625         IllegalArgumentException.class,
1626         0);
1627   }
1628 
1629   @Test
getFileGroup_throwsUnsupportedOperationException_logsUnsupportedOperation()1630   public void getFileGroup_throwsUnsupportedOperationException_logsUnsupportedOperation()
1631       throws Exception {
1632     getFileGroupErrorLoggingTestHelper(
1633         Futures.immediateFailedFuture(new UnsupportedOperationException()),
1634         ExecutionException.class,
1635         UnsupportedOperationException.class,
1636         0);
1637   }
1638 
1639   @Test
getFileGroup_throwsDownloadException_logsDownloadError()1640   public void getFileGroup_throwsDownloadException_logsDownloadError() throws Exception {
1641     getFileGroupErrorLoggingTestHelper(
1642         Futures.immediateFailedFuture(
1643             DownloadException.builder()
1644                 .setDownloadResultCode(DownloadResultCode.UNSPECIFIED)
1645                 .build()),
1646         ExecutionException.class,
1647         DownloadException.class,
1648         0);
1649   }
1650 
1651   @Test
readDataFileGroup_returnsFileGroup()1652   public void readDataFileGroup_returnsFileGroup() throws Exception {
1653     DataFileGroupInternal dataFileGroupInternal =
1654         DataFileGroupInternal.newBuilder()
1655             .setGroupName(FILE_GROUP_NAME_1)
1656             .setOwnerPackage(context.getPackageName())
1657             .addFile(
1658                 MetadataProto.DataFile.newBuilder()
1659                     .setFileId(FILE_ID_1)
1660                     .setUrlToDownload(FILE_URL_1)
1661                     .build())
1662             .addFile(
1663                 MetadataProto.DataFile.newBuilder()
1664                     .setFileId(FILE_ID_2)
1665                     .setUrlToDownload(FILE_URL_2)
1666                     .build())
1667             .build();
1668 
1669     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
1670         .thenReturn(Futures.immediateFuture(dataFileGroupInternal));
1671     when(mockMobileDataDownloadManager.getDataFileUris(
1672             dataFileGroupInternal, /* verifyIsolatedStructure= */ true))
1673         .thenReturn(
1674             Futures.immediateFuture(
1675                 ImmutableMap.of(
1676                     dataFileGroupInternal.getFile(0),
1677                     onDeviceUri1,
1678                     dataFileGroupInternal.getFile(1),
1679                     onDeviceUri1)));
1680 
1681     MobileDataDownload mobileDataDownload =
1682         new MobileDataDownloadImpl(
1683             context,
1684             mockEventLogger,
1685             mockMobileDataDownloadManager,
1686             controlExecutor,
1687             ImmutableList.of() /* fileGroupPopulatorList */,
1688             Optional.of(mockTaskScheduler),
1689             fileStorage,
1690             Optional.absent() /* downloadMonitorOptional */,
1691             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1692             flags,
1693             singleFileDownloader,
1694             Optional.absent() /* customFileGroupValidator */,
1695             timeSource);
1696 
1697     DataFileGroup dataFileGroup =
1698         mobileDataDownload
1699             .readDataFileGroup(
1700                 ReadDataFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
1701             .get();
1702 
1703     assertThat(dataFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1704     assertThat(dataFileGroup.getFileList())
1705         .containsExactly(
1706             DataFile.newBuilder().setFileId(FILE_ID_1).setUrlToDownload(FILE_URL_1).build(),
1707             DataFile.newBuilder().setFileId(FILE_ID_2).setUrlToDownload(FILE_URL_2).build());
1708   }
1709 
1710   @Test
getFileGroupsByFilter_singleGroup()1711   public void getFileGroupsByFilter_singleGroup() throws Exception {
1712     List<GroupKeyAndGroup> keyDataFileGroupList = new ArrayList<>();
1713 
1714     DataFileGroupInternal downloadedFileGroup = FILE_GROUP_INTERNAL_1;
1715 
1716     GroupKey groupKey =
1717         GroupKey.newBuilder()
1718             .setGroupName(FILE_GROUP_NAME_1)
1719             .setOwnerPackage(context.getPackageName())
1720             .build();
1721     when(mockMobileDataDownloadManager.getFileGroup(eq(groupKey), eq(true)))
1722         .thenReturn(Futures.immediateFuture(downloadedFileGroup));
1723     when(mockMobileDataDownloadManager.getDataFileUris(
1724             downloadedFileGroup, /* verifyIsolatedStructure= */ true))
1725         .thenReturn(
1726             Futures.immediateFuture(ImmutableMap.of(downloadedFileGroup.getFile(0), onDeviceUri1)));
1727     keyDataFileGroupList.add(
1728         GroupKeyAndGroup.create(
1729             groupKey.toBuilder().setDownloaded(true).build(), downloadedFileGroup));
1730 
1731     DataFileGroupInternal pendingFileGroup =
1732         createDataFileGroupInternal(
1733             FILE_GROUP_NAME_1,
1734             context.getPackageName(),
1735             7 /* versionNumber */,
1736             new String[] {FILE_ID_1},
1737             new int[] {FILE_SIZE_2},
1738             new String[] {FILE_CHECKSUM_2},
1739             new String[] {FILE_URL_2},
1740             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
1741     pendingFileGroup =
1742         pendingFileGroup.toBuilder()
1743             .setFile(0, pendingFileGroup.getFile(0).toBuilder().setDownloadedFileByteSize(222222))
1744             .build();
1745     when(mockMobileDataDownloadManager.getFileGroup(groupKey, false))
1746         .thenReturn(Futures.immediateFuture(pendingFileGroup));
1747     when(mockMobileDataDownloadManager.getDataFileUris(
1748             pendingFileGroup, /* verifyIsolatedStructure= */ true))
1749         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
1750     keyDataFileGroupList.add(
1751         GroupKeyAndGroup.create(
1752             groupKey.toBuilder().setDownloaded(false).build(), pendingFileGroup));
1753 
1754     DataFileGroupInternal pendingFileGroup2 =
1755         createDataFileGroupInternal(
1756             FILE_GROUP_NAME_2,
1757             context.getPackageName(),
1758             4 /* versionNumber */,
1759             new String[] {FILE_ID_1, FILE_ID_2},
1760             new int[] {FILE_SIZE_1, FILE_SIZE_2},
1761             new String[] {FILE_CHECKSUM_1, FILE_CHECKSUM_2},
1762             new String[] {FILE_URL_1, FILE_URL_2},
1763             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
1764     GroupKey groupKey2 =
1765         GroupKey.newBuilder()
1766             .setGroupName(FILE_GROUP_NAME_2)
1767             .setOwnerPackage(context.getPackageName())
1768             .build();
1769     when(mockMobileDataDownloadManager.getFileGroup(groupKey2, false))
1770         .thenReturn(Futures.immediateFuture(pendingFileGroup2));
1771     when(mockMobileDataDownloadManager.getDataFileUris(
1772             pendingFileGroup2, /* verifyIsolatedStructure= */ true))
1773         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
1774     keyDataFileGroupList.add(
1775         GroupKeyAndGroup.create(
1776             groupKey2.toBuilder().setDownloaded(false).build(), pendingFileGroup2));
1777 
1778     when(mockMobileDataDownloadManager.getAllFreshGroups())
1779         .thenReturn(Futures.immediateFuture(keyDataFileGroupList));
1780 
1781     MobileDataDownload mobileDataDownload =
1782         new MobileDataDownloadImpl(
1783             context,
1784             mockEventLogger,
1785             mockMobileDataDownloadManager,
1786             controlExecutor,
1787             ImmutableList.of() /* fileGroupPopulatorList */,
1788             Optional.of(mockTaskScheduler),
1789             fileStorage,
1790             Optional.absent() /* downloadMonitorOptional */,
1791             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1792             flags,
1793             singleFileDownloader,
1794             Optional.absent() /* customFileGroupValidator */,
1795             timeSource);
1796 
1797     // We should get back 2 groups for FILE_GROUP_NAME_1.
1798     GetFileGroupsByFilterRequest getFileGroupsByFilterRequest =
1799         GetFileGroupsByFilterRequest.newBuilder()
1800             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
1801             .build();
1802     ImmutableList<ClientFileGroup> clientFileGroups =
1803         mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
1804     assertThat(clientFileGroups).hasSize(2);
1805 
1806     ClientFileGroup downloadedClientFileGroup = clientFileGroups.get(0);
1807     assertThat(downloadedClientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1808     assertThat(downloadedClientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
1809     assertThat(downloadedClientFileGroup.getVersionNumber()).isEqualTo(5);
1810     assertThat(downloadedClientFileGroup.getStatus()).isEqualTo(Status.DOWNLOADED);
1811     assertThat(downloadedClientFileGroup.getFileCount()).isEqualTo(1);
1812     assertThat(downloadedClientFileGroup.hasAccount()).isFalse();
1813 
1814     ClientFile clientFile = downloadedClientFileGroup.getFileList().get(0);
1815     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
1816     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
1817     assertThat(clientFile.getFullSizeInBytes()).isEqualTo(FILE_SIZE_1);
1818 
1819     ClientFileGroup pendingClientFileGroup = clientFileGroups.get(1);
1820     assertThat(pendingClientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1821     assertThat(pendingClientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
1822     assertThat(pendingClientFileGroup.getVersionNumber()).isEqualTo(7);
1823     assertThat(pendingClientFileGroup.getStatus()).isEqualTo(Status.PENDING);
1824     assertThat(pendingClientFileGroup.getFileCount()).isEqualTo(1);
1825     assertThat(pendingClientFileGroup.hasAccount()).isFalse();
1826 
1827     clientFile = pendingClientFileGroup.getFileList().get(0);
1828     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
1829     assertThat(clientFile.getDownloadSizeInBytes()).isEqualTo(222222);
1830     assertThat(clientFile.getFileUri()).isEmpty();
1831 
1832     // We should get back 1 group for FILE_GROUP_NAME_2.
1833     getFileGroupsByFilterRequest =
1834         GetFileGroupsByFilterRequest.newBuilder()
1835             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_2))
1836             .build();
1837     clientFileGroups = mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
1838     ClientFileGroup pendingClientFileGroup2 = clientFileGroups.get(0);
1839     assertThat(pendingClientFileGroup2.getGroupName()).isEqualTo(FILE_GROUP_NAME_2);
1840     assertThat(pendingClientFileGroup2.getOwnerPackage()).isEqualTo(context.getPackageName());
1841     assertThat(pendingClientFileGroup2.getVersionNumber()).isEqualTo(4);
1842     assertThat(pendingClientFileGroup2.getStatus()).isEqualTo(Status.PENDING);
1843     assertThat(pendingClientFileGroup2.getFileCount()).isEqualTo(2);
1844     assertThat(pendingClientFileGroup2.hasAccount()).isFalse();
1845 
1846     ArgumentCaptor<DataDownloadFileGroupStats> fileGroupDetailsCaptor =
1847         ArgumentCaptor.forClass(DataDownloadFileGroupStats.class);
1848     verify(mockEventLogger, times(3)).logMddQueryStats(fileGroupDetailsCaptor.capture());
1849 
1850     List<DataDownloadFileGroupStats> fileGroupStats = fileGroupDetailsCaptor.getAllValues();
1851     assertThat(fileGroupStats).hasSize(3);
1852     assertThat(fileGroupStats.get(0).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1853     assertThat(fileGroupStats.get(0).getOwnerPackage()).isEqualTo(context.getPackageName());
1854     assertThat(fileGroupStats.get(0).getFileGroupVersionNumber()).isEqualTo(5);
1855     assertThat(fileGroupStats.get(0).getFileCount()).isEqualTo(1);
1856     assertThat(fileGroupStats.get(1).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1857     assertThat(fileGroupStats.get(1).getOwnerPackage()).isEqualTo(context.getPackageName());
1858     assertThat(fileGroupStats.get(1).getFileGroupVersionNumber()).isEqualTo(7);
1859     assertThat(fileGroupStats.get(1).getFileCount()).isEqualTo(1);
1860     assertThat(fileGroupStats.get(2).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_2);
1861     assertThat(fileGroupStats.get(2).getOwnerPackage()).isEqualTo(context.getPackageName());
1862     assertThat(fileGroupStats.get(2).getFileGroupVersionNumber()).isEqualTo(4);
1863     assertThat(fileGroupStats.get(2).getFileCount()).isEqualTo(2);
1864 
1865     verifyNoMoreInteractions(mockEventLogger);
1866   }
1867 
1868   @Test
getFileGroupsByFilter_includeAllGroups()1869   public void getFileGroupsByFilter_includeAllGroups() throws Exception {
1870     List<GroupKeyAndGroup> keyDataFileGroupList = new ArrayList<>();
1871 
1872     Account account = AccountUtil.create("account-name", "account-type");
1873 
1874     DataFileGroupInternal downloadedFileGroup = FILE_GROUP_INTERNAL_1;
1875     GroupKey groupKey =
1876         GroupKey.newBuilder()
1877             .setGroupName(FILE_GROUP_NAME_1)
1878             .setOwnerPackage(context.getPackageName())
1879             .setAccount(AccountUtil.serialize(account))
1880             .build();
1881     when(mockMobileDataDownloadManager.getDataFileUris(
1882             downloadedFileGroup, /* verifyIsolatedStructure= */ true))
1883         .thenReturn(
1884             Futures.immediateFuture(ImmutableMap.of(downloadedFileGroup.getFile(0), onDeviceUri1)));
1885     keyDataFileGroupList.add(
1886         GroupKeyAndGroup.create(
1887             groupKey.toBuilder().setDownloaded(true).build(), downloadedFileGroup));
1888 
1889     DataFileGroupInternal pendingFileGroup =
1890         createDataFileGroupInternal(
1891             FILE_GROUP_NAME_1,
1892             context.getPackageName(),
1893             7,
1894             new String[] {FILE_ID_1},
1895             new int[] {FILE_SIZE_2},
1896             new String[] {FILE_CHECKSUM_2},
1897             new String[] {FILE_URL_2},
1898             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
1899     when(mockMobileDataDownloadManager.getDataFileUris(
1900             pendingFileGroup, /* verifyIsolatedStructure= */ true))
1901         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
1902     keyDataFileGroupList.add(
1903         GroupKeyAndGroup.create(
1904             groupKey.toBuilder().setDownloaded(false).build(), pendingFileGroup));
1905 
1906     DataFileGroupInternal pendingFileGroup2 =
1907         createDataFileGroupInternal(
1908             FILE_GROUP_NAME_2,
1909             context.getPackageName(),
1910             4 /* versionNumber */,
1911             new String[] {FILE_ID_1, FILE_ID_2},
1912             new int[] {FILE_SIZE_1, FILE_SIZE_2},
1913             new String[] {FILE_CHECKSUM_1, FILE_CHECKSUM_2},
1914             new String[] {FILE_URL_1, FILE_URL_2},
1915             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
1916     GroupKey groupKey2 =
1917         GroupKey.newBuilder()
1918             .setGroupName(FILE_GROUP_NAME_2)
1919             .setOwnerPackage(context.getPackageName())
1920             .build();
1921     when(mockMobileDataDownloadManager.getFileGroup(groupKey2, false))
1922         .thenReturn(Futures.immediateFuture(pendingFileGroup2));
1923     when(mockMobileDataDownloadManager.getDataFileUris(
1924             pendingFileGroup2, /* verifyIsolatedStructure= */ true))
1925         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
1926     keyDataFileGroupList.add(
1927         GroupKeyAndGroup.create(
1928             groupKey2.toBuilder().setDownloaded(false).build(), pendingFileGroup2));
1929 
1930     when(mockMobileDataDownloadManager.getAllFreshGroups())
1931         .thenReturn(Futures.immediateFuture(keyDataFileGroupList));
1932 
1933     MobileDataDownload mobileDataDownload =
1934         new MobileDataDownloadImpl(
1935             context,
1936             mockEventLogger,
1937             mockMobileDataDownloadManager,
1938             controlExecutor,
1939             ImmutableList.of() /* fileGroupPopulatorList */,
1940             Optional.of(mockTaskScheduler),
1941             fileStorage,
1942             Optional.of(mockDownloadMonitor),
1943             Optional.of(this.getClass()), // don't need to use the real foreground download service.
1944             flags,
1945             singleFileDownloader,
1946             Optional.absent() /* customFileGroupValidator */,
1947             timeSource);
1948 
1949     // We should get back all 3 groups for this key.
1950     GetFileGroupsByFilterRequest getFileGroupsByFilterRequest =
1951         GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build();
1952     List<ClientFileGroup> clientFileGroups =
1953         mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
1954     assertThat(clientFileGroups).hasSize(3);
1955 
1956     ClientFileGroup downloadedClientFileGroup = clientFileGroups.get(0);
1957     assertThat(downloadedClientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1958     assertThat(downloadedClientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
1959     assertThat(downloadedClientFileGroup.getVersionNumber()).isEqualTo(5);
1960     assertThat(downloadedClientFileGroup.getStatus()).isEqualTo(Status.DOWNLOADED);
1961     assertThat(downloadedClientFileGroup.getFileCount()).isEqualTo(1);
1962     assertThat(downloadedClientFileGroup.getAccount()).isEqualTo(AccountUtil.serialize(account));
1963 
1964     ClientFile clientFile = downloadedClientFileGroup.getFileList().get(0);
1965     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
1966     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
1967     assertThat(clientFile.getFullSizeInBytes()).isEqualTo(FILE_SIZE_1);
1968 
1969     ClientFileGroup pendingClientFileGroup = clientFileGroups.get(1);
1970     assertThat(pendingClientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1971     assertThat(pendingClientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
1972     assertThat(pendingClientFileGroup.getVersionNumber()).isEqualTo(7);
1973     assertThat(pendingClientFileGroup.getStatus()).isEqualTo(Status.PENDING);
1974     assertThat(pendingClientFileGroup.getFileCount()).isEqualTo(1);
1975     assertThat(pendingClientFileGroup.getAccount()).isEqualTo(AccountUtil.serialize(account));
1976 
1977     clientFile = pendingClientFileGroup.getFileList().get(0);
1978     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
1979     assertThat(clientFile.getFileUri()).isEmpty();
1980 
1981     ClientFileGroup pendingClientFileGroup2 = clientFileGroups.get(2);
1982     assertThat(pendingClientFileGroup2.getGroupName()).isEqualTo(FILE_GROUP_NAME_2);
1983     assertThat(pendingClientFileGroup2.getOwnerPackage()).isEqualTo(context.getPackageName());
1984     assertThat(pendingClientFileGroup2.getVersionNumber()).isEqualTo(4);
1985     assertThat(pendingClientFileGroup2.getStatus()).isEqualTo(Status.PENDING);
1986     assertThat(pendingClientFileGroup2.getFileCount()).isEqualTo(2);
1987     assertThat(pendingClientFileGroup2.hasAccount()).isFalse();
1988 
1989     ArgumentCaptor<DataDownloadFileGroupStats> fileGroupDetailsCaptor =
1990         ArgumentCaptor.forClass(DataDownloadFileGroupStats.class);
1991     verify(mockEventLogger, times(3)).logMddQueryStats(fileGroupDetailsCaptor.capture());
1992 
1993     List<DataDownloadFileGroupStats> fileGroupStats = fileGroupDetailsCaptor.getAllValues();
1994     assertThat(fileGroupStats).hasSize(3);
1995     assertThat(fileGroupStats.get(0).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
1996     assertThat(fileGroupStats.get(0).getOwnerPackage()).isEqualTo(context.getPackageName());
1997     assertThat(fileGroupStats.get(0).getFileGroupVersionNumber()).isEqualTo(5);
1998     assertThat(fileGroupStats.get(0).getFileCount()).isEqualTo(1);
1999     assertThat(fileGroupStats.get(1).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2000     assertThat(fileGroupStats.get(1).getOwnerPackage()).isEqualTo(context.getPackageName());
2001     assertThat(fileGroupStats.get(1).getFileGroupVersionNumber()).isEqualTo(7);
2002     assertThat(fileGroupStats.get(1).getFileCount()).isEqualTo(1);
2003     assertThat(fileGroupStats.get(2).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_2);
2004     assertThat(fileGroupStats.get(2).getOwnerPackage()).isEqualTo(context.getPackageName());
2005     assertThat(fileGroupStats.get(2).getFileGroupVersionNumber()).isEqualTo(4);
2006     assertThat(fileGroupStats.get(2).getFileCount()).isEqualTo(2);
2007 
2008     verifyNoMoreInteractions(mockEventLogger);
2009   }
2010 
2011   @Test
getFileGroupsByFilter_noData()2012   public void getFileGroupsByFilter_noData() throws Exception {
2013     MobileDataDownload mobileDataDownload =
2014         new MobileDataDownloadImpl(
2015             context,
2016             mockEventLogger,
2017             mockMobileDataDownloadManager,
2018             controlExecutor,
2019             ImmutableList.of() /* fileGroupPopulatorList */,
2020             Optional.of(mockTaskScheduler),
2021             fileStorage,
2022             Optional.absent() /* downloadMonitorOptional */,
2023             Optional.of(this.getClass()), // don't need to use the real foreground download service.
2024             flags,
2025             singleFileDownloader,
2026             Optional.absent() /* customFileGroupValidator */,
2027             timeSource);
2028     when(mockMobileDataDownloadManager.getAllFreshGroups())
2029         .thenReturn(Futures.immediateFuture(ImmutableList.of()));
2030 
2031     GetFileGroupsByFilterRequest getFileGroupsByFilterRequest =
2032         GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build();
2033     List<ClientFileGroup> clientFileGroups =
2034         mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
2035     assertThat(clientFileGroups).isEmpty();
2036 
2037     getFileGroupsByFilterRequest =
2038         GetFileGroupsByFilterRequest.newBuilder()
2039             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
2040             .build();
2041     clientFileGroups = mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
2042     assertThat(clientFileGroups).isEmpty();
2043 
2044     verifyNoInteractions(mockEventLogger);
2045   }
2046 
2047   @Test
getFileGroupsByFilter_withAccount()2048   public void getFileGroupsByFilter_withAccount() throws Exception {
2049     List<GroupKeyAndGroup> keyDataFileGroupList = new ArrayList<>();
2050 
2051     Account account1 = AccountUtil.create("account-name-1", "account-type");
2052     Account account2 = AccountUtil.create("account-name-2", "account-type");
2053 
2054     DataFileGroupInternal downloadedFileGroup = FILE_GROUP_INTERNAL_1;
2055     GroupKey groupKey =
2056         GroupKey.newBuilder()
2057             .setGroupName(FILE_GROUP_NAME_1)
2058             .setOwnerPackage(context.getPackageName())
2059             .setAccount(AccountUtil.serialize(account1))
2060             .build();
2061     when(mockMobileDataDownloadManager.getFileGroup(groupKey, true))
2062         .thenReturn(Futures.immediateFuture(downloadedFileGroup));
2063     when(mockMobileDataDownloadManager.getDataFileUris(
2064             downloadedFileGroup, /* verifyIsolatedStructure= */ true))
2065         .thenReturn(
2066             Futures.immediateFuture(ImmutableMap.of(downloadedFileGroup.getFile(0), onDeviceUri1)));
2067     keyDataFileGroupList.add(
2068         GroupKeyAndGroup.create(
2069             groupKey.toBuilder().setDownloaded(true).build(), downloadedFileGroup));
2070 
2071     DataFileGroupInternal pendingFileGroup =
2072         createDataFileGroupInternal(
2073             FILE_GROUP_NAME_1,
2074             context.getPackageName(),
2075             7 /* versionNumber */,
2076             new String[] {FILE_ID_1},
2077             new int[] {FILE_SIZE_2},
2078             new String[] {FILE_CHECKSUM_2},
2079             new String[] {FILE_URL_2},
2080             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
2081     when(mockMobileDataDownloadManager.getFileGroup(groupKey, false))
2082         .thenReturn(Futures.immediateFuture(pendingFileGroup));
2083     when(mockMobileDataDownloadManager.getDataFileUris(
2084             pendingFileGroup, /* verifyIsolatedStructure= */ true))
2085         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
2086     keyDataFileGroupList.add(
2087         GroupKeyAndGroup.create(
2088             groupKey.toBuilder().setDownloaded(false).build(), pendingFileGroup));
2089 
2090     DataFileGroupInternal pendingFileGroup2 =
2091         createDataFileGroupInternal(
2092             FILE_GROUP_NAME_1,
2093             context.getPackageName(),
2094             4 /* versionNumber */,
2095             new String[] {FILE_ID_1, FILE_ID_2},
2096             new int[] {FILE_SIZE_1, FILE_SIZE_2},
2097             new String[] {FILE_CHECKSUM_1, FILE_CHECKSUM_2},
2098             new String[] {FILE_URL_1, FILE_URL_2},
2099             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
2100     GroupKey groupKey2 =
2101         GroupKey.newBuilder()
2102             .setGroupName(FILE_GROUP_NAME_1)
2103             .setOwnerPackage(context.getPackageName())
2104             .setAccount(AccountUtil.serialize(account2))
2105             .build();
2106     when(mockMobileDataDownloadManager.getFileGroup(groupKey2, false))
2107         .thenReturn(Futures.immediateFuture(pendingFileGroup2));
2108     when(mockMobileDataDownloadManager.getDataFileUris(
2109             pendingFileGroup2, /* verifyIsolatedStructure= */ true))
2110         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
2111     keyDataFileGroupList.add(
2112         GroupKeyAndGroup.create(
2113             groupKey2.toBuilder().setDownloaded(false).build(), pendingFileGroup2));
2114 
2115     when(mockMobileDataDownloadManager.getAllFreshGroups())
2116         .thenReturn(Futures.immediateFuture(keyDataFileGroupList));
2117 
2118     MobileDataDownload mobileDataDownload =
2119         new MobileDataDownloadImpl(
2120             context,
2121             mockEventLogger,
2122             mockMobileDataDownloadManager,
2123             controlExecutor,
2124             ImmutableList.of() /* fileGroupPopulatorList */,
2125             Optional.of(mockTaskScheduler),
2126             fileStorage,
2127             Optional.absent() /* downloadMonitorOptional */,
2128             Optional.of(this.getClass()), // don't need to use the real foreground download service.
2129             flags,
2130             singleFileDownloader,
2131             Optional.absent() /* customFileGroupValidator */,
2132             timeSource);
2133 
2134     // We should get back 2 groups for FILE_GROUP_NAME_1 with account1.
2135     GetFileGroupsByFilterRequest getFileGroupsByFilterRequest =
2136         GetFileGroupsByFilterRequest.newBuilder()
2137             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
2138             .setAccountOptional(Optional.of(account1))
2139             .build();
2140     ImmutableList<ClientFileGroup> clientFileGroups =
2141         mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
2142     assertThat(clientFileGroups).hasSize(2);
2143 
2144     ClientFileGroup downloadedClientFileGroup = clientFileGroups.get(0);
2145     assertThat(downloadedClientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2146     assertThat(downloadedClientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
2147     assertThat(downloadedClientFileGroup.getVersionNumber()).isEqualTo(5);
2148     assertThat(downloadedClientFileGroup.getAccount()).isEqualTo(AccountUtil.serialize(account1));
2149     assertThat(downloadedClientFileGroup.getStatus()).isEqualTo(Status.DOWNLOADED);
2150     assertThat(downloadedClientFileGroup.getFileCount()).isEqualTo(1);
2151 
2152     ClientFile clientFile = downloadedClientFileGroup.getFileList().get(0);
2153     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
2154     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
2155     assertThat(clientFile.getFullSizeInBytes()).isEqualTo(FILE_SIZE_1);
2156 
2157     ClientFileGroup pendingClientFileGroup = clientFileGroups.get(1);
2158     assertThat(pendingClientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2159     assertThat(pendingClientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
2160     assertThat(pendingClientFileGroup.getVersionNumber()).isEqualTo(7);
2161     assertThat(pendingClientFileGroup.getAccount()).isEqualTo(AccountUtil.serialize(account1));
2162     assertThat(pendingClientFileGroup.getStatus()).isEqualTo(Status.PENDING);
2163     assertThat(pendingClientFileGroup.getFileCount()).isEqualTo(1);
2164 
2165     clientFile = pendingClientFileGroup.getFileList().get(0);
2166     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
2167     assertThat(clientFile.getFileUri()).isEmpty();
2168 
2169     // We should get back 1 group for FILE_GROUP_NAME_1 with account2.
2170     getFileGroupsByFilterRequest =
2171         GetFileGroupsByFilterRequest.newBuilder()
2172             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
2173             .setAccountOptional(Optional.of(account2))
2174             .build();
2175     clientFileGroups = mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
2176     ClientFileGroup pendingClientFileGroup2 = clientFileGroups.get(0);
2177     assertThat(pendingClientFileGroup2.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2178     assertThat(pendingClientFileGroup2.getOwnerPackage()).isEqualTo(context.getPackageName());
2179     assertThat(pendingClientFileGroup2.getVersionNumber()).isEqualTo(4);
2180     assertThat(pendingClientFileGroup2.getAccount()).isEqualTo(AccountUtil.serialize(account2));
2181     assertThat(pendingClientFileGroup2.getStatus()).isEqualTo(Status.PENDING);
2182     assertThat(pendingClientFileGroup2.getFileCount()).isEqualTo(2);
2183 
2184     ArgumentCaptor<DataDownloadFileGroupStats> fileGroupDetailsCaptor =
2185         ArgumentCaptor.forClass(DataDownloadFileGroupStats.class);
2186     verify(mockEventLogger, times(3)).logMddQueryStats(fileGroupDetailsCaptor.capture());
2187 
2188     List<DataDownloadFileGroupStats> fileGroupStats = fileGroupDetailsCaptor.getAllValues();
2189     assertThat(fileGroupStats).hasSize(3);
2190     assertThat(fileGroupStats.get(0).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2191     assertThat(fileGroupStats.get(0).getOwnerPackage()).isEqualTo(context.getPackageName());
2192     assertThat(fileGroupStats.get(0).getFileGroupVersionNumber()).isEqualTo(5);
2193     assertThat(fileGroupStats.get(0).getFileCount()).isEqualTo(1);
2194     assertThat(fileGroupStats.get(1).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2195     assertThat(fileGroupStats.get(1).getOwnerPackage()).isEqualTo(context.getPackageName());
2196     assertThat(fileGroupStats.get(1).getFileGroupVersionNumber()).isEqualTo(7);
2197     assertThat(fileGroupStats.get(1).getFileCount()).isEqualTo(1);
2198     assertThat(fileGroupStats.get(2).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2199     assertThat(fileGroupStats.get(2).getOwnerPackage()).isEqualTo(context.getPackageName());
2200     assertThat(fileGroupStats.get(2).getFileGroupVersionNumber()).isEqualTo(4);
2201     assertThat(fileGroupStats.get(2).getFileCount()).isEqualTo(2);
2202 
2203     verifyNoMoreInteractions(mockEventLogger);
2204   }
2205 
2206   @Test
getFileGroupsByFilter_groupWithNoAccountOnly()2207   public void getFileGroupsByFilter_groupWithNoAccountOnly() throws Exception {
2208     List<GroupKeyAndGroup> keyDataFileGroupList = new ArrayList<>();
2209 
2210     Account account1 = AccountUtil.create("account-name-1", "account-type");
2211     Account account2 = AccountUtil.create("account-name-2", "account-type");
2212 
2213     // downloadedFileGroup is associated with account1.
2214     DataFileGroupInternal downloadedFileGroup = FILE_GROUP_INTERNAL_1;
2215     GroupKey groupKey =
2216         GroupKey.newBuilder()
2217             .setGroupName(FILE_GROUP_NAME_1)
2218             .setOwnerPackage(context.getPackageName())
2219             .setAccount(AccountUtil.serialize(account1))
2220             .build();
2221     when(mockMobileDataDownloadManager.getFileGroup(groupKey, true))
2222         .thenReturn(Futures.immediateFuture(downloadedFileGroup));
2223     when(mockMobileDataDownloadManager.getDataFileUris(
2224             downloadedFileGroup, /* verifyIsolatedStructure= */ true))
2225         .thenReturn(
2226             Futures.immediateFuture(ImmutableMap.of(downloadedFileGroup.getFile(0), onDeviceUri1)));
2227     keyDataFileGroupList.add(
2228         GroupKeyAndGroup.create(
2229             groupKey.toBuilder().setDownloaded(true).build(), downloadedFileGroup));
2230 
2231     // pendingFileGroup is associated with account2.
2232     DataFileGroupInternal pendingFileGroup =
2233         createDataFileGroupInternal(
2234             FILE_GROUP_NAME_1,
2235             context.getPackageName(),
2236             /* versionNumber= */ 7,
2237             new String[] {FILE_ID_1},
2238             new int[] {FILE_SIZE_2},
2239             new String[] {FILE_CHECKSUM_2},
2240             new String[] {FILE_URL_2},
2241             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
2242     GroupKey groupKey2 =
2243         GroupKey.newBuilder()
2244             .setGroupName(FILE_GROUP_NAME_1)
2245             .setOwnerPackage(context.getPackageName())
2246             .setAccount(AccountUtil.serialize(account2))
2247             .build();
2248     when(mockMobileDataDownloadManager.getFileGroup(groupKey2, false))
2249         .thenReturn(Futures.immediateFuture(pendingFileGroup));
2250     when(mockMobileDataDownloadManager.getDataFileUris(
2251             pendingFileGroup, /* verifyIsolatedStructure= */ true))
2252         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
2253     keyDataFileGroupList.add(
2254         GroupKeyAndGroup.create(
2255             groupKey2.toBuilder().setDownloaded(false).build(), pendingFileGroup));
2256 
2257     // pendingFileGroup2 is an account independent group.
2258     DataFileGroupInternal pendingFileGroup2 =
2259         createDataFileGroupInternal(
2260             FILE_GROUP_NAME_1,
2261             context.getPackageName(),
2262             /* versionNumber= */ 4,
2263             new String[] {FILE_ID_1, FILE_ID_2},
2264             new int[] {FILE_SIZE_1, FILE_SIZE_2},
2265             new String[] {FILE_CHECKSUM_1, FILE_CHECKSUM_2},
2266             new String[] {FILE_URL_1, FILE_URL_2},
2267             DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI);
2268     GroupKey groupKey3 =
2269         GroupKey.newBuilder()
2270             .setGroupName(FILE_GROUP_NAME_1)
2271             .setOwnerPackage(context.getPackageName())
2272             .build();
2273     when(mockMobileDataDownloadManager.getFileGroup(groupKey3, false))
2274         .thenReturn(Futures.immediateFuture(pendingFileGroup2));
2275     when(mockMobileDataDownloadManager.getDataFileUris(
2276             pendingFileGroup2, /* verifyIsolatedStructure= */ true))
2277         .thenReturn(Futures.immediateFuture(ImmutableMap.of()));
2278     keyDataFileGroupList.add(
2279         GroupKeyAndGroup.create(
2280             groupKey3.toBuilder().setDownloaded(false).build(), pendingFileGroup2));
2281 
2282     when(mockMobileDataDownloadManager.getAllFreshGroups())
2283         .thenReturn(Futures.immediateFuture(keyDataFileGroupList));
2284 
2285     MobileDataDownload mobileDataDownload =
2286         new MobileDataDownloadImpl(
2287             context,
2288             mockEventLogger,
2289             mockMobileDataDownloadManager,
2290             controlExecutor,
2291             /* fileGroupPopulatorList= */ ImmutableList.of(),
2292             Optional.of(mockTaskScheduler),
2293             fileStorage,
2294             /* downloadMonitorOptional= */ Optional.absent(),
2295             /* foregroundDownloadServiceClassOptional= */ Optional.absent(),
2296             flags,
2297             singleFileDownloader,
2298             Optional.absent() /* customFileGroupValidator */,
2299             timeSource);
2300 
2301     // We should get back only 1 group for FILE_GROUP_NAME_1 with groupWithNoAccountOnly being set
2302     // to true.
2303     GetFileGroupsByFilterRequest getFileGroupsByFilterRequest =
2304         GetFileGroupsByFilterRequest.newBuilder()
2305             .setGroupWithNoAccountOnly(true)
2306             .setGroupNameOptional(Optional.of(FILE_GROUP_NAME_1))
2307             .build();
2308     ImmutableList<ClientFileGroup> clientFileGroups =
2309         mobileDataDownload.getFileGroupsByFilter(getFileGroupsByFilterRequest).get();
2310     assertThat(clientFileGroups).hasSize(1);
2311 
2312     ClientFileGroup pendingClientFileGroup = clientFileGroups.get(0);
2313     assertThat(pendingClientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2314     assertThat(pendingClientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
2315     assertThat(pendingClientFileGroup.getVersionNumber()).isEqualTo(4);
2316     assertThat(pendingClientFileGroup.getStatus()).isEqualTo(Status.PENDING);
2317     assertThat(pendingClientFileGroup.getFileCount()).isEqualTo(2);
2318     assertThat(pendingClientFileGroup.hasAccount()).isFalse();
2319 
2320     ArgumentCaptor<DataDownloadFileGroupStats> fileGroupDetailsCaptor =
2321         ArgumentCaptor.forClass(DataDownloadFileGroupStats.class);
2322     verify(mockEventLogger, times(1)).logMddQueryStats(fileGroupDetailsCaptor.capture());
2323 
2324     List<DataDownloadFileGroupStats> fileGroupStats = fileGroupDetailsCaptor.getAllValues();
2325     assertThat(fileGroupStats).hasSize(1);
2326     assertThat(fileGroupStats.get(0).getFileGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2327     assertThat(fileGroupStats.get(0).getOwnerPackage()).isEqualTo(context.getPackageName());
2328     assertThat(fileGroupStats.get(0).getFileGroupVersionNumber()).isEqualTo(4);
2329     assertThat(fileGroupStats.get(0).getFileCount()).isEqualTo(2);
2330 
2331     verifyNoMoreInteractions(mockEventLogger);
2332   }
2333 
2334   @Test
importFiles_whenSuccessful_returns()2335   public void importFiles_whenSuccessful_returns() throws Exception {
2336     String inlineFileUrl = String.format("inlinefile:sha1:%s", FILE_CHECKSUM_1);
2337     DataFile inlineFile =
2338         DataFile.newBuilder()
2339             .setFileId(FILE_ID_1)
2340             .setByteSize(FILE_SIZE_1)
2341             .setChecksum(FILE_CHECKSUM_1)
2342             .setUrlToDownload(inlineFileUrl)
2343             .build();
2344     ImmutableList<DataFile> updatedDataFileList = ImmutableList.of(inlineFile);
2345     ImmutableMap<String, FileSource> inlineFileMap =
2346         ImmutableMap.of(
2347             FILE_ID_1, FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2348 
2349     // TODO: rely on actual implementation once feature is fully implemented.
2350     when(mockMobileDataDownloadManager.importFiles(
2351             any(), anyLong(), any(), any(), any(), any(), any()))
2352         .thenReturn(Futures.immediateVoidFuture());
2353 
2354     MobileDataDownload mobileDataDownload =
2355         new MobileDataDownloadImpl(
2356             context,
2357             mockEventLogger,
2358             mockMobileDataDownloadManager,
2359             controlExecutor,
2360             /* fileGroupPopulatorList= */ ImmutableList.of(),
2361             Optional.of(mockTaskScheduler),
2362             fileStorage,
2363             Optional.of(mockDownloadMonitor),
2364             Optional.of(this.getClass()),
2365             flags,
2366             singleFileDownloader,
2367             Optional.absent() /* customFileGroupValidator */,
2368             timeSource);
2369 
2370     // Since we use mocks, just call the method directly, no need to call addFileGroup first
2371     mobileDataDownload
2372         .importFiles(
2373             ImportFilesRequest.newBuilder()
2374                 .setGroupName(FILE_GROUP_NAME_1)
2375                 .setBuildId(1)
2376                 .setVariantId("testvariant")
2377                 .setUpdatedDataFileList(updatedDataFileList)
2378                 .setInlineFileMap(inlineFileMap)
2379                 .build())
2380         .get();
2381 
2382     // Verify mocks were called
2383     GroupKey expectedGroupKey =
2384         GroupKey.newBuilder()
2385             .setGroupName(FILE_GROUP_NAME_1)
2386             .setOwnerPackage(context.getPackageName())
2387             .build();
2388     ImmutableList<MetadataProto.DataFile> expectedDataFileList =
2389         ImmutableList.of(ProtoConversionUtil.convertDataFile(inlineFile));
2390     verify(mockMobileDataDownloadManager)
2391         .importFiles(
2392             eq(expectedGroupKey),
2393             eq(1L),
2394             eq("testvariant"),
2395             eq(expectedDataFileList),
2396             eq(inlineFileMap),
2397             eq(Optional.absent()),
2398             any());
2399   }
2400 
2401   @Test
importFiles_whenAccountIsSpecified_usesAccount()2402   public void importFiles_whenAccountIsSpecified_usesAccount() throws Exception {
2403     Account account = AccountUtil.create("account-name", "account-type");
2404     String inlineFileUrl = String.format("inlinefile:sha1:%s", FILE_CHECKSUM_1);
2405     DataFile inlineFile =
2406         DataFile.newBuilder()
2407             .setFileId(FILE_ID_1)
2408             .setByteSize(FILE_SIZE_1)
2409             .setChecksum(FILE_CHECKSUM_1)
2410             .setUrlToDownload(inlineFileUrl)
2411             .build();
2412     ImmutableList<DataFile> updatedDataFileList = ImmutableList.of(inlineFile);
2413     ImmutableMap<String, FileSource> inlineFileMap =
2414         ImmutableMap.of(
2415             FILE_ID_1, FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2416 
2417     // TODO: rely on actual implementation once feature is fully implemented.
2418     when(mockMobileDataDownloadManager.importFiles(
2419             any(), anyLong(), any(), any(), any(), any(), any()))
2420         .thenReturn(Futures.immediateVoidFuture());
2421 
2422     MobileDataDownload mobileDataDownload =
2423         new MobileDataDownloadImpl(
2424             context,
2425             mockEventLogger,
2426             mockMobileDataDownloadManager,
2427             controlExecutor,
2428             /* fileGroupPopulatorList= */ ImmutableList.of(),
2429             Optional.of(mockTaskScheduler),
2430             fileStorage,
2431             Optional.of(mockDownloadMonitor),
2432             Optional.of(this.getClass()),
2433             flags,
2434             singleFileDownloader,
2435             Optional.absent() /* customFileGroupValidator */,
2436             timeSource);
2437 
2438     // Since we use mocks, just call the method directly, no need to call addFileGroup first
2439     mobileDataDownload
2440         .importFiles(
2441             ImportFilesRequest.newBuilder()
2442                 .setGroupName(FILE_GROUP_NAME_1)
2443                 .setAccountOptional(Optional.of(account))
2444                 .setBuildId(1)
2445                 .setVariantId("testvariant")
2446                 .setUpdatedDataFileList(updatedDataFileList)
2447                 .setInlineFileMap(inlineFileMap)
2448                 .build())
2449         .get();
2450 
2451     // Verify mocks were called
2452     GroupKey expectedGroupKey =
2453         GroupKey.newBuilder()
2454             .setGroupName(FILE_GROUP_NAME_1)
2455             .setAccount(AccountUtil.serialize(account))
2456             .setOwnerPackage(context.getPackageName())
2457             .build();
2458     ImmutableList<MetadataProto.DataFile> expectedDataFileList =
2459         ImmutableList.of(ProtoConversionUtil.convertDataFile(inlineFile));
2460     verify(mockMobileDataDownloadManager)
2461         .importFiles(
2462             eq(expectedGroupKey),
2463             eq(1L),
2464             eq("testvariant"),
2465             eq(expectedDataFileList),
2466             eq(inlineFileMap),
2467             eq(Optional.absent()),
2468             any());
2469   }
2470 
2471   @Test
importFiles_whenFails_returnsFailure()2472   public void importFiles_whenFails_returnsFailure() throws Exception {
2473     String inlineFileUrl = String.format("inlinefile:%s", FILE_CHECKSUM_1);
2474     DataFile inlineFile =
2475         DataFile.newBuilder()
2476             .setFileId(FILE_ID_1)
2477             .setByteSize(FILE_SIZE_1)
2478             .setChecksum(FILE_CHECKSUM_1)
2479             .setUrlToDownload(inlineFileUrl)
2480             .build();
2481     ImmutableList<DataFile> updatedDataFileList = ImmutableList.of(inlineFile);
2482     ImmutableMap<String, FileSource> inlineFileMap =
2483         ImmutableMap.of(
2484             FILE_ID_1, FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2485 
2486     // TODO: rely on actual implementation once feature is fully implemented.
2487     when(mockMobileDataDownloadManager.importFiles(
2488             any(), anyLong(), any(), any(), any(), any(), any()))
2489         .thenReturn(Futures.immediateFailedFuture(new Exception("Test failure")));
2490 
2491     MobileDataDownload mobileDataDownload =
2492         new MobileDataDownloadImpl(
2493             context,
2494             mockEventLogger,
2495             mockMobileDataDownloadManager,
2496             controlExecutor,
2497             /* fileGroupPopulatorList= */ ImmutableList.of(),
2498             Optional.of(mockTaskScheduler),
2499             fileStorage,
2500             Optional.of(mockDownloadMonitor),
2501             Optional.of(this.getClass()),
2502             flags,
2503             singleFileDownloader,
2504             Optional.absent() /* customFileGroupValidator */,
2505             timeSource);
2506 
2507     // Since we use mocks, just call the method directly, no need to call addFileGroup first
2508     ExecutionException ex =
2509         assertThrows(
2510             ExecutionException.class,
2511             () ->
2512                 mobileDataDownload
2513                     .importFiles(
2514                         ImportFilesRequest.newBuilder()
2515                             .setGroupName(FILE_GROUP_NAME_1)
2516                             .setBuildId(1)
2517                             .setVariantId("testvariant")
2518                             .setUpdatedDataFileList(updatedDataFileList)
2519                             .setInlineFileMap(inlineFileMap)
2520                             .build())
2521                     .get());
2522     assertThat(ex).hasMessageThat().contains("Test failure");
2523 
2524     // Verify mocks were called
2525     GroupKey expectedGroupKey =
2526         GroupKey.newBuilder()
2527             .setGroupName(FILE_GROUP_NAME_1)
2528             .setOwnerPackage(context.getPackageName())
2529             .build();
2530     ImmutableList<MetadataProto.DataFile> expectedDataFileList =
2531         ImmutableList.of(ProtoConversionUtil.convertDataFile(inlineFile));
2532     verify(mockMobileDataDownloadManager)
2533         .importFiles(
2534             eq(expectedGroupKey),
2535             eq(1L),
2536             eq("testvariant"),
2537             eq(expectedDataFileList),
2538             eq(inlineFileMap),
2539             eq(Optional.absent()),
2540             any());
2541   }
2542 
2543   @Test
downloadFileGroup()2544   public void downloadFileGroup() throws Exception {
2545     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
2546     when(mockMobileDataDownloadManager.downloadFileGroup(groupKeyCaptor.capture(), any(), any()))
2547         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2548     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(false)))
2549         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2550     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(true)))
2551         .thenReturn(Futures.immediateFuture(null));
2552     when(mockMobileDataDownloadManager.getDataFileUris(
2553             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
2554         .thenReturn(
2555             Futures.immediateFuture(
2556                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceUri1)));
2557 
2558     MobileDataDownload mobileDataDownload =
2559         new MobileDataDownloadImpl(
2560             context,
2561             mockEventLogger,
2562             mockMobileDataDownloadManager,
2563             controlExecutor,
2564             ImmutableList.of() /* fileGroupPopulatorList */,
2565             Optional.of(mockTaskScheduler),
2566             fileStorage,
2567             Optional.of(mockDownloadMonitor),
2568             Optional.of(this.getClass()), // don't need to use the real foreground download service.
2569             flags,
2570             singleFileDownloader,
2571             Optional.absent() /* customFileGroupValidator */,
2572             timeSource);
2573 
2574     AtomicBoolean onCompleteInvoked = new AtomicBoolean();
2575 
2576     ClientFileGroup clientFileGroup =
2577         mobileDataDownload
2578             .downloadFileGroup(
2579                 DownloadFileGroupRequest.newBuilder()
2580                     .setGroupName(FILE_GROUP_NAME_1)
2581                     .setListenerOptional(
2582                         Optional.of(
2583                             new DownloadListener() {
2584                               @Override
2585                               public void onProgress(long currentSize) {}
2586 
2587                               @Override
2588                               public void onComplete(ClientFileGroup clientFileGroup) {
2589                                 onCompleteInvoked.set(true);
2590                                 assertThat(clientFileGroup.getGroupName())
2591                                     .isEqualTo(FILE_GROUP_NAME_1);
2592                                 assertThat(clientFileGroup.getOwnerPackage())
2593                                     .isEqualTo(context.getPackageName());
2594                                 assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
2595                                 assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
2596                               }
2597                             }))
2598                     .build())
2599             .get();
2600 
2601     assertThat(onCompleteInvoked.get()).isTrue();
2602     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2603     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
2604     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
2605     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
2606     assertThat(clientFileGroup.hasAccount()).isFalse();
2607 
2608     ClientFile clientFile = clientFileGroup.getFileList().get(0);
2609     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
2610     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
2611 
2612     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
2613     verify(mockDownloadMonitor)
2614         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
2615     verify(mockDownloadMonitor).removeDownloadListener(eq(FILE_GROUP_NAME_1));
2616 
2617     assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2618     assertThat(groupKeyCaptor.getValue().hasAccount()).isFalse();
2619   }
2620 
2621   @Test
downloadFileGroup_failed()2622   public void downloadFileGroup_failed() throws Exception {
2623     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
2624     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(false)))
2625         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2626     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(true)))
2627         .thenReturn(Futures.immediateFuture(null));
2628     when(mockMobileDataDownloadManager.downloadFileGroup(groupKeyCaptor.capture(), any(), any()))
2629         .thenReturn(
2630             Futures.immediateFailedFuture(
2631                 DownloadException.builder()
2632                     .setDownloadResultCode(DownloadResultCode.UNKNOWN_ERROR)
2633                     .setMessage("Fail to download file group")
2634                     .build()));
2635 
2636     MobileDataDownload mobileDataDownload =
2637         new MobileDataDownloadImpl(
2638             context,
2639             mockEventLogger,
2640             mockMobileDataDownloadManager,
2641             controlExecutor,
2642             ImmutableList.of() /* fileGroupPopulatorList */,
2643             Optional.of(mockTaskScheduler),
2644             fileStorage,
2645             Optional.of(mockDownloadMonitor),
2646             Optional.of(this.getClass()), // don't need to use the real foreground download service.
2647             flags,
2648             singleFileDownloader,
2649             Optional.absent() /* customFileGroupValidator */,
2650             timeSource);
2651 
2652     AtomicBoolean listenerOnFailureInvoked = new AtomicBoolean();
2653     AtomicBoolean callbackOnFailureInvoked = new AtomicBoolean();
2654 
2655     ListenableFuture<ClientFileGroup> downloadFuture =
2656         mobileDataDownload.downloadFileGroup(
2657             DownloadFileGroupRequest.newBuilder()
2658                 .setGroupName(FILE_GROUP_NAME_1)
2659                 .setListenerOptional(
2660                     Optional.of(
2661                         new DownloadListener() {
2662                           @Override
2663                           public void onProgress(long currentSize) {}
2664 
2665                           @Override
2666                           public void onComplete(ClientFileGroup clientFileGroup) {}
2667 
2668                           @Override
2669                           public void onFailure(Throwable t) {
2670                             listenerOnFailureInvoked.set(true);
2671                           }
2672                         }))
2673                 .build());
2674 
2675     Futures.addCallback(
2676         downloadFuture,
2677         new FutureCallback<ClientFileGroup>() {
2678           @Override
2679           public void onSuccess(ClientFileGroup result) {}
2680 
2681           @Override
2682           public void onFailure(Throwable t) {
2683             callbackOnFailureInvoked.set(true);
2684           }
2685         },
2686         MoreExecutors.directExecutor());
2687 
2688     assertThrows(ExecutionException.class, downloadFuture::get);
2689     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
2690     assertThat(e).hasMessageThat().contains("Fail");
2691 
2692     assertThat(listenerOnFailureInvoked.get()).isTrue();
2693     assertThat(callbackOnFailureInvoked.get()).isTrue();
2694     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
2695     verify(mockDownloadMonitor)
2696         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
2697     verify(mockDownloadMonitor).removeDownloadListener(eq(FILE_GROUP_NAME_1));
2698 
2699     assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2700     assertThat(groupKeyCaptor.getValue().hasAccount()).isFalse();
2701   }
2702 
2703   @Test
downloadFileGroup_withAccount()2704   public void downloadFileGroup_withAccount() throws Exception {
2705     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
2706     when(mockMobileDataDownloadManager.downloadFileGroup(groupKeyCaptor.capture(), any(), any()))
2707         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2708     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(false)))
2709         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2710     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(true)))
2711         .thenReturn(Futures.immediateFuture(null));
2712     when(mockMobileDataDownloadManager.getDataFileUris(
2713             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
2714         .thenReturn(
2715             Futures.immediateFuture(
2716                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceUri1)));
2717 
2718     MobileDataDownload mobileDataDownload =
2719         new MobileDataDownloadImpl(
2720             context,
2721             mockEventLogger,
2722             mockMobileDataDownloadManager,
2723             controlExecutor,
2724             ImmutableList.of() /* fileGroupPopulatorList */,
2725             Optional.of(mockTaskScheduler),
2726             fileStorage,
2727             Optional.of(mockDownloadMonitor),
2728             Optional.of(this.getClass()), // don't need to use the real foreground download service.
2729             flags,
2730             singleFileDownloader,
2731             Optional.absent() /* customFileGroupValidator */,
2732             timeSource);
2733 
2734     AtomicBoolean onCompleteInvoked = new AtomicBoolean();
2735 
2736     Account account = AccountUtil.create("account-name", "account-type");
2737     ClientFileGroup clientFileGroup =
2738         mobileDataDownload
2739             .downloadFileGroup(
2740                 DownloadFileGroupRequest.newBuilder()
2741                     .setGroupName(FILE_GROUP_NAME_1)
2742                     .setAccountOptional(Optional.of(account))
2743                     .setListenerOptional(
2744                         Optional.of(
2745                             new DownloadListener() {
2746                               @Override
2747                               public void onProgress(long currentSize) {}
2748 
2749                               @Override
2750                               public void onComplete(ClientFileGroup clientFileGroup) {
2751                                 onCompleteInvoked.set(true);
2752                                 assertThat(clientFileGroup.getGroupName())
2753                                     .isEqualTo(FILE_GROUP_NAME_1);
2754                                 assertThat(clientFileGroup.getOwnerPackage())
2755                                     .isEqualTo(context.getPackageName());
2756                                 assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
2757                                 assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
2758                               }
2759                             }))
2760                     .build())
2761             .get();
2762 
2763     assertThat(onCompleteInvoked.get()).isTrue();
2764     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2765     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
2766     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
2767     assertThat(clientFileGroup.getAccount()).isEqualTo(AccountUtil.serialize(account));
2768     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
2769 
2770     ClientFile clientFile = clientFileGroup.getFileList().get(0);
2771     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
2772     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
2773 
2774     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
2775     verify(mockDownloadMonitor)
2776         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
2777     verify(mockDownloadMonitor).removeDownloadListener(eq(FILE_GROUP_NAME_1));
2778 
2779     assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2780     assertThat(groupKeyCaptor.getValue().getAccount()).isEqualTo(AccountUtil.serialize(account));
2781   }
2782 
2783   @Test
downloadFileGroup_withVariantId()2784   public void downloadFileGroup_withVariantId() throws Exception {
2785     DataFileGroupInternal dataFileGroup =
2786         FILE_GROUP_INTERNAL_1.toBuilder().setVariantId("en").build();
2787 
2788     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
2789     when(mockMobileDataDownloadManager.downloadFileGroup(groupKeyCaptor.capture(), any(), any()))
2790         .thenReturn(Futures.immediateFuture(dataFileGroup));
2791     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(false)))
2792         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2793     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(true)))
2794         .thenReturn(Futures.immediateFuture(null));
2795     when(mockMobileDataDownloadManager.getDataFileUris(
2796             dataFileGroup, /* verifyIsolatedStructure= */ true))
2797         .thenReturn(
2798             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
2799 
2800     MobileDataDownload mobileDataDownload =
2801         new MobileDataDownloadImpl(
2802             context,
2803             mockEventLogger,
2804             mockMobileDataDownloadManager,
2805             controlExecutor,
2806             ImmutableList.of() /* fileGroupPopulatorList */,
2807             Optional.of(mockTaskScheduler),
2808             fileStorage,
2809             Optional.of(mockDownloadMonitor),
2810             Optional.of(this.getClass()), // don't need to use the real foreground download service.
2811             flags,
2812             singleFileDownloader,
2813             Optional.absent() /* customFileGroupValidator */,
2814             timeSource);
2815 
2816     ClientFileGroup clientFileGroup =
2817         mobileDataDownload
2818             .downloadFileGroup(
2819                 DownloadFileGroupRequest.newBuilder()
2820                     .setGroupName(FILE_GROUP_NAME_1)
2821                     .setVariantIdOptional(Optional.of("en"))
2822                     .build())
2823             .get();
2824 
2825     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2826     assertThat(clientFileGroup.getVariantId()).isEqualTo("en");
2827 
2828     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
2829 
2830     GroupKey expectedGroupKey =
2831         GroupKey.newBuilder()
2832             .setGroupName(FILE_GROUP_NAME_1)
2833             .setOwnerPackage(context.getPackageName())
2834             .setVariantId("en")
2835             .build();
2836     assertThat(groupKeyCaptor.getValue()).isEqualTo(expectedGroupKey);
2837   }
2838 
2839   @Test
downloadFileGroupWithForegroundService()2840   public void downloadFileGroupWithForegroundService() throws Exception {
2841     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
2842     when(mockMobileDataDownloadManager.downloadFileGroup(groupKeyCaptor.capture(), any(), any()))
2843         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2844     when(mockMobileDataDownloadManager.getDataFileUris(
2845             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
2846         .thenReturn(
2847             Futures.immediateFuture(
2848                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceUri1)));
2849     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), anyBoolean()))
2850         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2851 
2852     MobileDataDownload mobileDataDownload =
2853         new MobileDataDownloadImpl(
2854             context,
2855             mockEventLogger,
2856             mockMobileDataDownloadManager,
2857             controlExecutor,
2858             /* fileGroupPopulatorList= */ ImmutableList.of(),
2859             Optional.of(mockTaskScheduler),
2860             fileStorage,
2861             Optional.of(mockDownloadMonitor),
2862             Optional.of(this.getClass()),
2863             flags,
2864             singleFileDownloader,
2865             Optional.absent() /* customFileGroupValidator */,
2866             timeSource);
2867 
2868     CountDownLatch onCompleteLatch = new CountDownLatch(1);
2869 
2870     ClientFileGroup clientFileGroup =
2871         mobileDataDownload
2872             .downloadFileGroupWithForegroundService(
2873                 DownloadFileGroupRequest.newBuilder()
2874                     .setGroupName(FILE_GROUP_NAME_1)
2875                     .setListenerOptional(
2876                         Optional.of(
2877                             new DownloadListener() {
2878                               @Override
2879                               public void onProgress(long currentSize) {}
2880 
2881                               @Override
2882                               public void onComplete(ClientFileGroup clientFileGroup) {
2883                                 assertThat(clientFileGroup.getGroupName())
2884                                     .isEqualTo(FILE_GROUP_NAME_1);
2885                                 assertThat(clientFileGroup.getOwnerPackage())
2886                                     .isEqualTo(context.getPackageName());
2887                                 assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
2888                                 assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
2889 
2890                                 // This is to verify that onComplete is called.
2891                                 onCompleteLatch.countDown();
2892                               }
2893                             }))
2894                     .build())
2895             .get();
2896 
2897     // Verify that onComplete is called.
2898     if (!onCompleteLatch.await(LATCH_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) {
2899       throw new RuntimeException("onComplete is not called");
2900     }
2901 
2902     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2903     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
2904     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
2905     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
2906     assertThat(clientFileGroup.hasAccount()).isFalse();
2907 
2908     ClientFile clientFile = clientFileGroup.getFileList().get(0);
2909     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
2910     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
2911 
2912     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
2913     verify(mockDownloadMonitor)
2914         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
2915     verify(mockDownloadMonitor).removeDownloadListener(eq(FILE_GROUP_NAME_1));
2916 
2917     assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2918     assertThat(groupKeyCaptor.getValue().hasAccount()).isFalse();
2919   }
2920 
2921   @Test
downloadFileGroupWithForegroundService_failed()2922   public void downloadFileGroupWithForegroundService_failed() throws Exception {
2923     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
2924     when(mockMobileDataDownloadManager.downloadFileGroup(groupKeyCaptor.capture(), any(), any()))
2925         .thenReturn(
2926             Futures.immediateFailedFuture(
2927                 DownloadException.builder()
2928                     .setDownloadResultCode(DownloadResultCode.UNKNOWN_ERROR)
2929                     .setMessage("Fail to download file group")
2930                     .build()));
2931     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), eq(false)))
2932         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
2933     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), eq(true)))
2934         .thenReturn(Futures.immediateFuture(null));
2935 
2936     MobileDataDownload mobileDataDownload =
2937         new MobileDataDownloadImpl(
2938             context,
2939             mockEventLogger,
2940             mockMobileDataDownloadManager,
2941             controlExecutor,
2942             /* fileGroupPopulatorList= */ ImmutableList.of(),
2943             Optional.of(mockTaskScheduler),
2944             fileStorage,
2945             Optional.of(mockDownloadMonitor),
2946             Optional.of(this.getClass()), // don't need to use the real foreground download service.
2947             flags,
2948             singleFileDownloader,
2949             Optional.absent() /* customFileGroupValidator */,
2950             timeSource);
2951 
2952     expectErrorLogMessage(
2953         "DownloadListener: onFailure:"
2954             + " com.google.android.libraries.mobiledatadownload.DownloadException: Fail to download"
2955             + " file group");
2956 
2957     ListenableFuture<ClientFileGroup> downloadFuture =
2958         mobileDataDownload.downloadFileGroupWithForegroundService(
2959             DownloadFileGroupRequest.newBuilder()
2960                 .setGroupName(FILE_GROUP_NAME_1)
2961                 .setListenerOptional(
2962                     Optional.of(
2963                         new DownloadListener() {
2964                           @Override
2965                           public void onProgress(long currentSize) {}
2966 
2967                           @Override
2968                           public void onComplete(ClientFileGroup clientFileGroup) {}
2969                         }))
2970                 .build());
2971 
2972     AtomicBoolean onFailureInvoked = new AtomicBoolean();
2973     Futures.addCallback(
2974         downloadFuture,
2975         new FutureCallback<ClientFileGroup>() {
2976           @Override
2977           public void onSuccess(ClientFileGroup result) {}
2978 
2979           @Override
2980           public void onFailure(Throwable t) {
2981             onFailureInvoked.set(true);
2982           }
2983         },
2984         MoreExecutors.directExecutor());
2985 
2986     assertThrows(ExecutionException.class, downloadFuture::get);
2987     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
2988     assertThat(e).hasMessageThat().contains("Fail");
2989 
2990     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
2991     verify(mockDownloadMonitor)
2992         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
2993 
2994     assertThat(onFailureInvoked.get()).isTrue();
2995     verify(mockDownloadMonitor).removeDownloadListener(eq(FILE_GROUP_NAME_1));
2996 
2997     assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
2998     assertThat(groupKeyCaptor.getValue().hasAccount()).isFalse();
2999   }
3000 
3001   @Test
downloadFileGroupWithForegroundService_withAccount()3002   public void downloadFileGroupWithForegroundService_withAccount() throws Exception {
3003     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
3004     when(mockMobileDataDownloadManager.downloadFileGroup(groupKeyCaptor.capture(), any(), any()))
3005         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
3006     when(mockMobileDataDownloadManager.getDataFileUris(
3007             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
3008         .thenReturn(
3009             Futures.immediateFuture(
3010                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceUri1)));
3011     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), eq(false)))
3012         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
3013     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), eq(true)))
3014         .thenReturn(Futures.immediateFuture(null));
3015 
3016     MobileDataDownload mobileDataDownload =
3017         new MobileDataDownloadImpl(
3018             context,
3019             mockEventLogger,
3020             mockMobileDataDownloadManager,
3021             controlExecutor,
3022             /* fileGroupPopulatorList= */ ImmutableList.of(),
3023             Optional.of(mockTaskScheduler),
3024             fileStorage,
3025             Optional.of(mockDownloadMonitor),
3026             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3027             flags,
3028             singleFileDownloader,
3029             Optional.absent() /* customFileGroupValidator */,
3030             timeSource);
3031 
3032     AtomicBoolean onCompleteInvoked = new AtomicBoolean();
3033     Account account = AccountUtil.create("account-name", "account-type");
3034     ClientFileGroup clientFileGroup =
3035         mobileDataDownload
3036             .downloadFileGroupWithForegroundService(
3037                 DownloadFileGroupRequest.newBuilder()
3038                     .setGroupName(FILE_GROUP_NAME_1)
3039                     .setAccountOptional(Optional.of(account))
3040                     .setListenerOptional(
3041                         Optional.of(
3042                             new DownloadListener() {
3043                               @Override
3044                               public void onProgress(long currentSize) {}
3045 
3046                               @Override
3047                               public void onComplete(ClientFileGroup clientFileGroup) {
3048                                 onCompleteInvoked.set(true);
3049                                 assertThat(clientFileGroup.getGroupName())
3050                                     .isEqualTo(FILE_GROUP_NAME_1);
3051                                 assertThat(clientFileGroup.getOwnerPackage())
3052                                     .isEqualTo(context.getPackageName());
3053                                 assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
3054                                 assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
3055                               }
3056                             }))
3057                     .build())
3058             .get();
3059 
3060     assertThat(onCompleteInvoked.get()).isTrue();
3061     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
3062     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
3063     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
3064     assertThat(clientFileGroup.getAccount()).isEqualTo(AccountUtil.serialize(account));
3065     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
3066 
3067     ClientFile clientFile = clientFileGroup.getFileList().get(0);
3068     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
3069     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
3070 
3071     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
3072     verify(mockDownloadMonitor)
3073         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
3074     verify(mockDownloadMonitor).removeDownloadListener(eq(FILE_GROUP_NAME_1));
3075 
3076     assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
3077     assertThat(groupKeyCaptor.getValue().getAccount()).isEqualTo(AccountUtil.serialize(account));
3078   }
3079 
3080   @Test
downloadFileGroupWithForegroundService_withVariantId()3081   public void downloadFileGroupWithForegroundService_withVariantId() throws Exception {
3082     DataFileGroupInternal dataFileGroup =
3083         FILE_GROUP_INTERNAL_1.toBuilder().setVariantId("en").build();
3084 
3085     ArgumentCaptor<GroupKey> pendingGroupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
3086     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
3087     ArgumentCaptor<GroupKey> downloadGroupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
3088     when(mockMobileDataDownloadManager.downloadFileGroup(
3089             downloadGroupKeyCaptor.capture(), any(), any()))
3090         .thenReturn(Futures.immediateFuture(dataFileGroup));
3091     when(mockMobileDataDownloadManager.getDataFileUris(
3092             dataFileGroup, /* verifyIsolatedStructure= */ true))
3093         .thenReturn(
3094             Futures.immediateFuture(ImmutableMap.of(dataFileGroup.getFile(0), onDeviceUri1)));
3095     // The order here is important: first mock true and then false.
3096     // eq(true) returns false, and so if you mock false first, pendingGroupKeyCapture will capture
3097     // the value of groupKeyCaptor.capture(), which is null.
3098     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), eq(true)))
3099         .thenReturn(Futures.immediateFuture(null));
3100     when(mockMobileDataDownloadManager.getFileGroup(pendingGroupKeyCaptor.capture(), eq(false)))
3101         .thenReturn(Futures.immediateFuture(dataFileGroup));
3102 
3103     MobileDataDownload mobileDataDownload =
3104         new MobileDataDownloadImpl(
3105             context,
3106             mockEventLogger,
3107             mockMobileDataDownloadManager,
3108             controlExecutor,
3109             /* fileGroupPopulatorList= */ ImmutableList.of(),
3110             Optional.of(mockTaskScheduler),
3111             fileStorage,
3112             Optional.of(mockDownloadMonitor),
3113             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3114             flags,
3115             singleFileDownloader,
3116             Optional.absent() /* customFileGroupValidator */,
3117             timeSource);
3118 
3119     ClientFileGroup clientFileGroup =
3120         mobileDataDownload
3121             .downloadFileGroupWithForegroundService(
3122                 DownloadFileGroupRequest.newBuilder()
3123                     .setGroupName(FILE_GROUP_NAME_1)
3124                     .setVariantIdOptional(Optional.of("en"))
3125                     .build())
3126             .get();
3127 
3128     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
3129     assertThat(clientFileGroup.getVariantId()).isEqualTo("en");
3130 
3131     verify(mockMobileDataDownloadManager).downloadFileGroup(any(GroupKey.class), any(), any());
3132 
3133     GroupKey expectedGroupKey =
3134         GroupKey.newBuilder()
3135             .setGroupName(FILE_GROUP_NAME_1)
3136             .setOwnerPackage(context.getPackageName())
3137             .setVariantId("en")
3138             .build();
3139     assertThat(groupKeyCaptor.getAllValues()).hasSize(1);
3140     assertThat(groupKeyCaptor.getValue()).isEqualTo(expectedGroupKey);
3141     assertThat(pendingGroupKeyCaptor.getAllValues()).hasSize(1);
3142     assertThat(pendingGroupKeyCaptor.getValue()).isEqualTo(expectedGroupKey);
3143     assertThat(downloadGroupKeyCaptor.getAllValues()).hasSize(1);
3144     assertThat(downloadGroupKeyCaptor.getValue()).isEqualTo(expectedGroupKey);
3145   }
3146 
3147   @Test
downloadFileGroupWithForegroundService_whenAlreadyDownloaded()3148   public void downloadFileGroupWithForegroundService_whenAlreadyDownloaded() throws Exception {
3149     when(mockMobileDataDownloadManager.getDataFileUris(
3150             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
3151         .thenReturn(
3152             Futures.immediateFuture(
3153                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceUri1)));
3154 
3155     // Mock situation: no pending group but there is a downloaded group
3156     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(false)))
3157         .thenReturn(Futures.immediateFuture(null));
3158     when(mockMobileDataDownloadManager.getFileGroup(any(), eq(true)))
3159         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
3160 
3161     MobileDataDownload mobileDataDownload =
3162         new MobileDataDownloadImpl(
3163             context,
3164             mockEventLogger,
3165             mockMobileDataDownloadManager,
3166             controlExecutor,
3167             /* fileGroupPopulatorList= */ ImmutableList.of(),
3168             Optional.of(mockTaskScheduler),
3169             fileStorage,
3170             Optional.of(mockDownloadMonitor),
3171             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3172             flags,
3173             singleFileDownloader,
3174             Optional.absent() /* customFileGroupValidator */,
3175             timeSource);
3176 
3177     AtomicBoolean onCompleteInvoked = new AtomicBoolean(false);
3178     ClientFileGroup clientFileGroup =
3179         mobileDataDownload
3180             .downloadFileGroupWithForegroundService(
3181                 DownloadFileGroupRequest.newBuilder()
3182                     .setGroupName(FILE_GROUP_NAME_1)
3183                     .setListenerOptional(
3184                         Optional.of(
3185                             new DownloadListener() {
3186                               @Override
3187                               public void onProgress(long currentSize) {}
3188 
3189                               @Override
3190                               public void onComplete(ClientFileGroup clientFileGroup) {
3191                                 onCompleteInvoked.set(true);
3192                                 assertThat(clientFileGroup.getGroupName())
3193                                     .isEqualTo(FILE_GROUP_NAME_1);
3194                                 assertThat(clientFileGroup.getOwnerPackage())
3195                                     .isEqualTo(context.getPackageName());
3196                                 assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
3197                                 assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
3198                               }
3199                             }))
3200                     .build())
3201             .get();
3202 
3203     assertThat(onCompleteInvoked.get()).isTrue();
3204     assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME_1);
3205     assertThat(clientFileGroup.getOwnerPackage()).isEqualTo(context.getPackageName());
3206     assertThat(clientFileGroup.getVersionNumber()).isEqualTo(5);
3207     assertThat(clientFileGroup.getFileCount()).isEqualTo(1);
3208     assertThat(clientFileGroup.hasAccount()).isFalse();
3209 
3210     ClientFile clientFile = clientFileGroup.getFileList().get(0);
3211     assertThat(clientFile.getFileId()).isEqualTo(FILE_ID_1);
3212     assertThat(clientFile.getFileUri()).isEqualTo(onDeviceUri1.toString());
3213 
3214     verify(mockMobileDataDownloadManager, times(1)).getFileGroup(any(GroupKey.class), eq(true));
3215     verify(mockMobileDataDownloadManager, times(1)).getFileGroup(any(GroupKey.class), eq(false));
3216     verify(mockMobileDataDownloadManager, times(0))
3217         .downloadFileGroup(any(GroupKey.class), any(), any());
3218     verify(mockDownloadMonitor)
3219         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
3220     verify(mockDownloadMonitor).removeDownloadListener(eq(FILE_GROUP_NAME_1));
3221   }
3222 
3223   @Test
downloadFileGroupWithForegroundService_whenNoVersionFound_fails()3224   public void downloadFileGroupWithForegroundService_whenNoVersionFound_fails() throws Exception {
3225     when(mockMobileDataDownloadManager.getFileGroup(groupKeyCaptor.capture(), anyBoolean()))
3226         .thenReturn(Futures.immediateFuture(null));
3227 
3228     MobileDataDownload mobileDataDownload =
3229         new MobileDataDownloadImpl(
3230             context,
3231             mockEventLogger,
3232             mockMobileDataDownloadManager,
3233             controlExecutor,
3234             /* fileGroupPopulatorList= */ ImmutableList.of(),
3235             Optional.of(mockTaskScheduler),
3236             fileStorage,
3237             Optional.of(mockDownloadMonitor),
3238             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3239             flags,
3240             singleFileDownloader,
3241             Optional.absent() /* customFileGroupValidator */,
3242             timeSource);
3243 
3244     AtomicBoolean onFailureInvoked = new AtomicBoolean(false);
3245     ListenableFuture<ClientFileGroup> downloadFuture =
3246         mobileDataDownload.downloadFileGroupWithForegroundService(
3247             DownloadFileGroupRequest.newBuilder()
3248                 .setGroupName(FILE_GROUP_NAME_1)
3249                 .setListenerOptional(
3250                     Optional.of(
3251                         new DownloadListener() {
3252                           @Override
3253                           public void onProgress(long currentSize) {}
3254 
3255                           @Override
3256                           public void onComplete(ClientFileGroup clientFileGroup) {
3257                             fail("onComplete should not be called");
3258                           }
3259 
3260                           @Override
3261                           public void onFailure(Throwable t) {
3262                             onFailureInvoked.set(true);
3263                             assertThat(t).isInstanceOf(DownloadException.class);
3264                             assertThat(((DownloadException) t).getDownloadResultCode())
3265                                 .isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
3266                           }
3267                         }))
3268                 .build());
3269 
3270     assertThrows(ExecutionException.class, downloadFuture::get);
3271 
3272     assertThat(onFailureInvoked.get()).isTrue();
3273     DownloadException e = LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
3274     assertThat(e.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
3275 
3276     // Verify did not attempt a download
3277     verify(mockMobileDataDownloadManager, times(0))
3278         .downloadFileGroup(any(GroupKey.class), any(), any());
3279     verify(mockDownloadMonitor, times(0))
3280         .addDownloadListener(eq(FILE_GROUP_NAME_1), any(DownloadListener.class));
3281   }
3282 
3283   @Test
maintenance_success()3284   public void maintenance_success() throws Exception {
3285     MobileDataDownload mobileDataDownload =
3286         new MobileDataDownloadImpl(
3287             context,
3288             mockEventLogger,
3289             mockMobileDataDownloadManager,
3290             controlExecutor,
3291             /* fileGroupPopulatorList= */ ImmutableList.of(),
3292             Optional.of(mockTaskScheduler),
3293             fileStorage,
3294             /* downloadMonitorOptional= */ Optional.absent(),
3295             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3296             flags,
3297             singleFileDownloader,
3298             Optional.absent() /* customFileGroupValidator */,
3299             timeSource);
3300 
3301     when(mockMobileDataDownloadManager.maintenance()).thenReturn(Futures.immediateFuture(null));
3302 
3303     mobileDataDownload.maintenance().get();
3304 
3305     verify(mockMobileDataDownloadManager).maintenance();
3306     verifyNoMoreInteractions(mockMobileDataDownloadManager);
3307   }
3308 
3309   @Test
maintenance_failure()3310   public void maintenance_failure() throws Exception {
3311     MobileDataDownload mobileDataDownload =
3312         new MobileDataDownloadImpl(
3313             context,
3314             mockEventLogger,
3315             mockMobileDataDownloadManager,
3316             controlExecutor,
3317             /* fileGroupPopulatorList= */ ImmutableList.of(),
3318             Optional.of(mockTaskScheduler),
3319             fileStorage,
3320             /* downloadMonitorOptional= */ Optional.absent(),
3321             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3322             flags,
3323             singleFileDownloader,
3324             Optional.absent() /* customFileGroupValidator */,
3325             timeSource);
3326 
3327     when(mockMobileDataDownloadManager.maintenance())
3328         .thenReturn(Futures.immediateFailedFuture(new IOException("test-failure")));
3329 
3330     ExecutionException e =
3331         assertThrows(ExecutionException.class, () -> mobileDataDownload.maintenance().get());
3332     assertThat(e).hasCauseThat().isInstanceOf(IOException.class);
3333     assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("test-failure");
3334 
3335     verify(mockMobileDataDownloadManager).maintenance();
3336     verifyNoMoreInteractions(mockMobileDataDownloadManager);
3337   }
3338 
3339   @Test
collectGarbage_interactionTest()3340   public void collectGarbage_interactionTest() throws Exception {
3341     MobileDataDownload mobileDataDownload =
3342         new MobileDataDownloadImpl(
3343             context,
3344             mockEventLogger,
3345             mockMobileDataDownloadManager,
3346             controlExecutor,
3347             /* fileGroupPopulatorList= */ ImmutableList.of(),
3348             Optional.of(mockTaskScheduler),
3349             fileStorage,
3350             /* downloadMonitorOptional= */ Optional.absent(),
3351             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3352             flags,
3353             singleFileDownloader,
3354             Optional.absent() /* customFileGroupValidator */,
3355             timeSource);
3356 
3357     when(mockMobileDataDownloadManager.removeExpiredGroupsAndFiles())
3358         .thenReturn(Futures.immediateFuture(null));
3359 
3360     mobileDataDownload.collectGarbage().get();
3361 
3362     verify(mockMobileDataDownloadManager).removeExpiredGroupsAndFiles();
3363     verifyNoMoreInteractions(mockMobileDataDownloadManager);
3364   }
3365 
3366   @Test
schedulePeriodicTasks()3367   public void schedulePeriodicTasks() throws Exception {
3368     MobileDataDownload mobileDataDownload =
3369         new MobileDataDownloadImpl(
3370             context,
3371             mockEventLogger,
3372             mockMobileDataDownloadManager,
3373             controlExecutor,
3374             /* fileGroupPopulatorList= */ ImmutableList.of(),
3375             Optional.of(mockTaskScheduler),
3376             fileStorage,
3377             /* downloadMonitorOptional= */ Optional.absent(),
3378             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3379             flags,
3380             singleFileDownloader,
3381             Optional.absent() /* customFileGroupValidator */,
3382             timeSource);
3383 
3384     mobileDataDownload.schedulePeriodicTasks();
3385 
3386     verify(mockTaskScheduler)
3387         .schedulePeriodicTask(
3388             TaskScheduler.CHARGING_PERIODIC_TASK,
3389             flags.chargingGcmTaskPeriod(),
3390             NetworkState.NETWORK_STATE_ANY,
3391             /* constraintOverrides= */ Optional.absent());
3392 
3393     verify(mockTaskScheduler)
3394         .schedulePeriodicTask(
3395             TaskScheduler.MAINTENANCE_PERIODIC_TASK,
3396             flags.maintenanceGcmTaskPeriod(),
3397             NetworkState.NETWORK_STATE_ANY,
3398             /* constraintOverrides= */ Optional.absent());
3399 
3400     verify(mockTaskScheduler)
3401         .schedulePeriodicTask(
3402             TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK,
3403             flags.cellularChargingGcmTaskPeriod(),
3404             NetworkState.NETWORK_STATE_CONNECTED,
3405             /* constraintOverrides= */ Optional.absent());
3406 
3407     verify(mockTaskScheduler)
3408         .schedulePeriodicTask(
3409             TaskScheduler.WIFI_CHARGING_PERIODIC_TASK,
3410             flags.wifiChargingGcmTaskPeriod(),
3411             NetworkState.NETWORK_STATE_UNMETERED,
3412             /* constraintOverrides= */ Optional.absent());
3413 
3414     verifyNoMoreInteractions(mockTaskScheduler);
3415   }
3416 
3417   @Test
schedulePeriodicTasks_nullTaskScheduler()3418   public void schedulePeriodicTasks_nullTaskScheduler() throws Exception {
3419     MobileDataDownload mobileDataDownload =
3420         new MobileDataDownloadImpl(
3421             context,
3422             mockEventLogger,
3423             mockMobileDataDownloadManager,
3424             controlExecutor,
3425             /* fileGroupPopulatorList= */ ImmutableList.of(),
3426             /* taskSchedulerOptional= */ Optional.absent(),
3427             fileStorage,
3428             /* downloadMonitorOptional= */ Optional.absent(),
3429             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3430             flags,
3431             singleFileDownloader,
3432             Optional.absent() /* customFileGroupValidator */,
3433             timeSource);
3434 
3435     expectErrorLogMessage(
3436         "MobileDataDownload: Called schedulePeriodicTasksInternal when taskScheduler is not"
3437             + " provided.");
3438     mobileDataDownload.schedulePeriodicTasks();
3439 
3440     verifyNoInteractions(mockTaskScheduler);
3441   }
3442 
3443   @Test
schedulePeriodicBackgroundTasks()3444   public void schedulePeriodicBackgroundTasks() throws Exception {
3445     MobileDataDownload mobileDataDownload =
3446         new MobileDataDownloadImpl(
3447             context,
3448             mockEventLogger,
3449             mockMobileDataDownloadManager,
3450             controlExecutor,
3451             /* fileGroupPopulatorList= */ ImmutableList.of(),
3452             Optional.of(mockTaskScheduler),
3453             fileStorage,
3454             /* downloadMonitorOptional= */ Optional.absent(),
3455             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3456             flags,
3457             singleFileDownloader,
3458             Optional.absent() /* customFileGroupValidator */,
3459             timeSource);
3460 
3461     mobileDataDownload.schedulePeriodicBackgroundTasks().get();
3462 
3463     verify(mockTaskScheduler)
3464         .schedulePeriodicTask(
3465             TaskScheduler.CHARGING_PERIODIC_TASK,
3466             flags.chargingGcmTaskPeriod(),
3467             NetworkState.NETWORK_STATE_ANY,
3468             /* constraintOverrides= */ Optional.absent());
3469 
3470     verify(mockTaskScheduler)
3471         .schedulePeriodicTask(
3472             TaskScheduler.MAINTENANCE_PERIODIC_TASK,
3473             flags.maintenanceGcmTaskPeriod(),
3474             NetworkState.NETWORK_STATE_ANY,
3475             /* constraintOverrides= */ Optional.absent());
3476 
3477     verify(mockTaskScheduler)
3478         .schedulePeriodicTask(
3479             TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK,
3480             flags.cellularChargingGcmTaskPeriod(),
3481             NetworkState.NETWORK_STATE_CONNECTED,
3482             /* constraintOverrides= */ Optional.absent());
3483 
3484     verify(mockTaskScheduler)
3485         .schedulePeriodicTask(
3486             TaskScheduler.WIFI_CHARGING_PERIODIC_TASK,
3487             flags.wifiChargingGcmTaskPeriod(),
3488             NetworkState.NETWORK_STATE_UNMETERED,
3489             /* constraintOverrides= */ Optional.absent());
3490 
3491     verifyNoMoreInteractions(mockTaskScheduler);
3492   }
3493 
3494   @Test
schedulePeriodicBackgroundTasks_nullTaskScheduler()3495   public void schedulePeriodicBackgroundTasks_nullTaskScheduler() throws Exception {
3496     MobileDataDownload mobileDataDownload =
3497         new MobileDataDownloadImpl(
3498             context,
3499             mockEventLogger,
3500             mockMobileDataDownloadManager,
3501             controlExecutor,
3502             /* fileGroupPopulatorList= */ ImmutableList.of(),
3503             /* taskSchedulerOptional= */ Optional.absent(),
3504             fileStorage,
3505             /* downloadMonitorOptional= */ Optional.absent(),
3506             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3507             flags,
3508             singleFileDownloader,
3509             Optional.absent() /* customFileGroupValidator */,
3510             timeSource);
3511 
3512     expectErrorLogMessage(
3513         "MobileDataDownload: Called schedulePeriodicTasksInternal when taskScheduler is not"
3514             + " provided.");
3515     mobileDataDownload.schedulePeriodicBackgroundTasks().get();
3516 
3517     verifyNoInteractions(mockTaskScheduler);
3518   }
3519 
3520   @Test
schedulePeriodicBackgroundTasks_withConstraintOverrides()3521   public void schedulePeriodicBackgroundTasks_withConstraintOverrides() throws Exception {
3522     MobileDataDownload mobileDataDownload =
3523         new MobileDataDownloadImpl(
3524             context,
3525             mockEventLogger,
3526             mockMobileDataDownloadManager,
3527             controlExecutor,
3528             /* fileGroupPopulatorList= */ ImmutableList.of(),
3529             Optional.of(mockTaskScheduler),
3530             fileStorage,
3531             /* downloadMonitorOptional= */ Optional.absent(),
3532             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3533             flags,
3534             singleFileDownloader,
3535             Optional.absent() /* customFileGroupValidator */,
3536             timeSource);
3537 
3538     ConstraintOverrides wifiOverrides =
3539         ConstraintOverrides.newBuilder()
3540             .setRequiresCharging(false)
3541             .setRequiresDeviceIdle(true)
3542             .build();
3543     ConstraintOverrides cellularOverrides =
3544         ConstraintOverrides.newBuilder()
3545             .setRequiresCharging(true)
3546             .setRequiresDeviceIdle(false)
3547             .build();
3548 
3549     Map<String, ConstraintOverrides> constraintOverridesMap = new HashMap<>();
3550     constraintOverridesMap.put(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK, wifiOverrides);
3551     constraintOverridesMap.put(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK, cellularOverrides);
3552 
3553     mobileDataDownload.schedulePeriodicBackgroundTasks(Optional.of(constraintOverridesMap)).get();
3554 
3555     verify(mockTaskScheduler)
3556         .schedulePeriodicTask(
3557             TaskScheduler.CHARGING_PERIODIC_TASK,
3558             flags.chargingGcmTaskPeriod(),
3559             NetworkState.NETWORK_STATE_ANY,
3560             Optional.absent());
3561 
3562     verify(mockTaskScheduler)
3563         .schedulePeriodicTask(
3564             TaskScheduler.MAINTENANCE_PERIODIC_TASK,
3565             flags.maintenanceGcmTaskPeriod(),
3566             NetworkState.NETWORK_STATE_ANY,
3567             Optional.absent());
3568 
3569     verify(mockTaskScheduler)
3570         .schedulePeriodicTask(
3571             TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK,
3572             flags.cellularChargingGcmTaskPeriod(),
3573             NetworkState.NETWORK_STATE_CONNECTED,
3574             Optional.of(cellularOverrides));
3575 
3576     verify(mockTaskScheduler)
3577         .schedulePeriodicTask(
3578             TaskScheduler.WIFI_CHARGING_PERIODIC_TASK,
3579             flags.wifiChargingGcmTaskPeriod(),
3580             NetworkState.NETWORK_STATE_UNMETERED,
3581             Optional.of(wifiOverrides));
3582 
3583     verifyNoMoreInteractions(mockTaskScheduler);
3584   }
3585 
3586   @Test
schedulePeriodicBackgroundTasks_nullTaskScheduler_andOverrides()3587   public void schedulePeriodicBackgroundTasks_nullTaskScheduler_andOverrides() throws Exception {
3588     MobileDataDownload mobileDataDownload =
3589         new MobileDataDownloadImpl(
3590             context,
3591             mockEventLogger,
3592             mockMobileDataDownloadManager,
3593             controlExecutor,
3594             /* fileGroupPopulatorList= */ ImmutableList.of(),
3595             /* taskSchedulerOptional= */ Optional.absent(),
3596             fileStorage,
3597             /* downloadMonitorOptional= */ Optional.absent(),
3598             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3599             flags,
3600             singleFileDownloader,
3601             Optional.absent() /* customFileGroupValidator */,
3602             timeSource);
3603 
3604     expectErrorLogMessage(
3605         "MobileDataDownload: Called schedulePeriodicTasksInternal when taskScheduler is not"
3606             + " provided.");
3607 
3608     mobileDataDownload.schedulePeriodicBackgroundTasks(Optional.absent()).get();
3609 
3610     verifyNoInteractions(mockTaskScheduler);
3611   }
3612 
3613   @Test
cancelPeriodicBackgroundTasks_nullTaskScheduler()3614   public void cancelPeriodicBackgroundTasks_nullTaskScheduler() throws Exception {
3615     MobileDataDownload mobileDataDownload =
3616         new MobileDataDownloadImpl(
3617             context,
3618             mockEventLogger,
3619             mockMobileDataDownloadManager,
3620             controlExecutor,
3621             /* fileGroupPopulatorList= */ ImmutableList.of(),
3622             /* taskSchedulerOptional= */ Optional.absent(),
3623             fileStorage,
3624             /* downloadMonitorOptional= */ Optional.absent(),
3625             Optional.absent() /* foregroundDownloadServiceClassOptional */,
3626             flags,
3627             singleFileDownloader,
3628             Optional.absent() /* customFileGroupValidator */,
3629             timeSource);
3630 
3631     mobileDataDownload.cancelPeriodicBackgroundTasks().get();
3632 
3633     verifyNoInteractions(mockTaskScheduler);
3634   }
3635 
3636   @Test
cancelPeriodicBackgroundTasks()3637   public void cancelPeriodicBackgroundTasks() throws Exception {
3638     MobileDataDownload mobileDataDownload =
3639         new MobileDataDownloadImpl(
3640             context,
3641             mockEventLogger,
3642             mockMobileDataDownloadManager,
3643             controlExecutor,
3644             /* fileGroupPopulatorList= */ ImmutableList.of(),
3645             Optional.of(mockTaskScheduler),
3646             fileStorage,
3647             /* downloadMonitorOptional= */ Optional.absent(),
3648             Optional.absent() /* foregroundDownloadServiceClassOptional */,
3649             flags,
3650             singleFileDownloader,
3651             Optional.absent() /* customFileGroupValidator */,
3652             timeSource);
3653 
3654     mobileDataDownload.cancelPeriodicBackgroundTasks().get();
3655 
3656     verify(mockTaskScheduler).cancelPeriodicTask(TaskScheduler.CHARGING_PERIODIC_TASK);
3657 
3658     verify(mockTaskScheduler).cancelPeriodicTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK);
3659 
3660     verify(mockTaskScheduler).cancelPeriodicTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK);
3661 
3662     verify(mockTaskScheduler).cancelPeriodicTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK);
3663 
3664     verifyNoMoreInteractions(mockTaskScheduler);
3665   }
3666 
3667   // A helper function to create a DataFilegroup.
createDataFileGroup( String groupName, String ownerPackage, int versionNumber, String[] fileId, int[] byteSize, String[] checksum, String[] url, DeviceNetworkPolicy deviceNetworkPolicy)3668   private static DataFileGroup createDataFileGroup(
3669       String groupName,
3670       String ownerPackage,
3671       int versionNumber,
3672       String[] fileId,
3673       int[] byteSize,
3674       String[] checksum,
3675       String[] url,
3676       DeviceNetworkPolicy deviceNetworkPolicy) {
3677     if (fileId.length != byteSize.length
3678         || fileId.length != checksum.length
3679         || fileId.length != url.length) {
3680       throw new IllegalArgumentException();
3681     }
3682 
3683     DataFileGroup.Builder dataFileGroupBuilder =
3684         DataFileGroup.newBuilder()
3685             .setGroupName(groupName)
3686             .setOwnerPackage(ownerPackage)
3687             .setFileGroupVersionNumber(versionNumber)
3688             .setDownloadConditions(
3689                 DownloadConditions.newBuilder().setDeviceNetworkPolicy(deviceNetworkPolicy));
3690 
3691     for (int i = 0; i < fileId.length; ++i) {
3692       DataFile file =
3693           DataFile.newBuilder()
3694               .setFileId(fileId[i])
3695               .setByteSize(byteSize[i])
3696               .setChecksum(checksum[i])
3697               .setUrlToDownload(url[i])
3698               .build();
3699       dataFileGroupBuilder.addFile(file);
3700     }
3701 
3702     return dataFileGroupBuilder.build();
3703   }
3704 
createDataFileGroupInternal( String groupName, String ownerPackage, int versionNumber, String[] fileId, int[] byteSize, String[] checksum, String[] url, DeviceNetworkPolicy deviceNetworkPolicy)3705   private static DataFileGroupInternal createDataFileGroupInternal(
3706       String groupName,
3707       String ownerPackage,
3708       int versionNumber,
3709       String[] fileId,
3710       int[] byteSize,
3711       String[] checksum,
3712       String[] url,
3713       DeviceNetworkPolicy deviceNetworkPolicy) {
3714     try {
3715       return ProtoConversionUtil.convert(
3716           createDataFileGroup(
3717               groupName,
3718               ownerPackage,
3719               versionNumber,
3720               fileId,
3721               byteSize,
3722               checksum,
3723               url,
3724               deviceNetworkPolicy));
3725     } catch (Exception e) {
3726       // wrap with runtime exception to avoid this method having to declare throws
3727       throw new RuntimeException(e);
3728     }
3729   }
3730 
3731   @Test
handleTask_maintenance()3732   public void handleTask_maintenance() throws Exception {
3733     MobileDataDownload mobileDataDownload =
3734         new MobileDataDownloadImpl(
3735             context,
3736             mockEventLogger,
3737             mockMobileDataDownloadManager,
3738             controlExecutor,
3739             /* fileGroupPopulatorList= */ ImmutableList.of(),
3740             Optional.of(mockTaskScheduler),
3741             fileStorage,
3742             /* downloadMonitorOptional= */ Optional.absent(),
3743             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3744             flags,
3745             singleFileDownloader,
3746             Optional.absent() /* customFileGroupValidator */,
3747             timeSource);
3748     when(mockMobileDataDownloadManager.maintenance()).thenReturn(Futures.immediateFuture(null));
3749 
3750     mobileDataDownload.handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK).get();
3751     verify(mockMobileDataDownloadManager).maintenance();
3752     verifyNoMoreInteractions(mockMobileDataDownloadManager);
3753   }
3754 
3755   @Test
handleTask_charging()3756   public void handleTask_charging() throws Exception {
3757     MobileDataDownload mobileDataDownload =
3758         new MobileDataDownloadImpl(
3759             context,
3760             mockEventLogger,
3761             mockMobileDataDownloadManager,
3762             controlExecutor,
3763             ImmutableList.of(mockFileGroupPopulator),
3764             Optional.of(mockTaskScheduler),
3765             fileStorage,
3766             /* downloadMonitorOptional= */ Optional.absent(),
3767             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3768             flags,
3769             singleFileDownloader,
3770             Optional.absent() /* customFileGroupValidator */,
3771             timeSource);
3772 
3773     when(mockMobileDataDownloadManager.verifyAllPendingGroups(any()))
3774         .thenReturn(Futures.immediateFuture(null));
3775     when(mockFileGroupPopulator.refreshFileGroups(mobileDataDownload))
3776         .thenReturn(Futures.immediateFuture(null));
3777 
3778     mobileDataDownload.handleTask(TaskScheduler.CHARGING_PERIODIC_TASK).get();
3779     verify(mockFileGroupPopulator).refreshFileGroups(mobileDataDownload);
3780     verify(mockMobileDataDownloadManager).verifyAllPendingGroups(any());
3781     verifyNoMoreInteractions(mockMobileDataDownloadManager);
3782   }
3783 
3784   @Test
handleTask_wifi_charging()3785   public void handleTask_wifi_charging() throws Exception {
3786     MobileDataDownload mobileDataDownload =
3787         new MobileDataDownloadImpl(
3788             context,
3789             mockEventLogger,
3790             mockMobileDataDownloadManager,
3791             controlExecutor,
3792             ImmutableList.of(mockFileGroupPopulator),
3793             Optional.of(mockTaskScheduler),
3794             fileStorage,
3795             /* downloadMonitorOptional= */ Optional.absent(),
3796             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3797             flags,
3798             singleFileDownloader,
3799             Optional.absent() /* customFileGroupValidator */,
3800             timeSource);
3801 
3802     when(mockFileGroupPopulator.refreshFileGroups(mobileDataDownload))
3803         .thenReturn(Futures.immediateFuture(null));
3804     when(mockMobileDataDownloadManager.downloadAllPendingGroups(eq(true) /*wifi*/, any()))
3805         .thenReturn(Futures.immediateFuture(null));
3806 
3807     mobileDataDownload.handleTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK).get();
3808     verify(mockFileGroupPopulator, times(2)).refreshFileGroups(mobileDataDownload);
3809     verify(mockMobileDataDownloadManager, times(2)).downloadAllPendingGroups(eq(true), any());
3810     verifyNoMoreInteractions(mockMobileDataDownloadManager);
3811   }
3812 
3813   @Test
handleTask_cellular_charging()3814   public void handleTask_cellular_charging() throws Exception {
3815     MobileDataDownload mobileDataDownload =
3816         new MobileDataDownloadImpl(
3817             context,
3818             mockEventLogger,
3819             mockMobileDataDownloadManager,
3820             controlExecutor,
3821             ImmutableList.of(mockFileGroupPopulator),
3822             Optional.of(mockTaskScheduler),
3823             fileStorage,
3824             /* downloadMonitorOptional= */ Optional.absent(),
3825             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3826             flags,
3827             singleFileDownloader,
3828             Optional.absent() /* customFileGroupValidator */,
3829             timeSource);
3830 
3831     when(mockFileGroupPopulator.refreshFileGroups(mobileDataDownload))
3832         .thenReturn(Futures.immediateFuture(null));
3833     when(mockMobileDataDownloadManager.downloadAllPendingGroups(eq(false) /*wifi*/, any()))
3834         .thenReturn(Futures.immediateFuture(null));
3835 
3836     mobileDataDownload.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK).get();
3837     verify(mockFileGroupPopulator, times(2)).refreshFileGroups(mobileDataDownload);
3838     verify(mockMobileDataDownloadManager, times(2)).downloadAllPendingGroups(eq(false), any());
3839     verifyNoMoreInteractions(mockMobileDataDownloadManager);
3840   }
3841 
3842   @Test
handleTask_no_filegroup_populator()3843   public void handleTask_no_filegroup_populator() throws Exception {
3844     MobileDataDownload mobileDataDownload =
3845         new MobileDataDownloadImpl(
3846             context,
3847             mockEventLogger,
3848             mockMobileDataDownloadManager,
3849             controlExecutor,
3850             /* fileGroupPopulatorList= */ ImmutableList.of(),
3851             Optional.of(mockTaskScheduler),
3852             fileStorage,
3853             /* downloadMonitorOptional= */ Optional.absent(),
3854             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3855             flags,
3856             singleFileDownloader,
3857             Optional.absent() /* customFileGroupValidator */,
3858             timeSource);
3859 
3860     when(mockMobileDataDownloadManager.verifyAllPendingGroups(any()))
3861         .thenReturn(Futures.immediateFuture(null));
3862     when(mockMobileDataDownloadManager.downloadAllPendingGroups(anyBoolean() /*wifi*/, any()))
3863         .thenReturn(Futures.immediateFuture(null));
3864 
3865     mobileDataDownload.handleTask(TaskScheduler.CHARGING_PERIODIC_TASK).get();
3866     verify(mockMobileDataDownloadManager).verifyAllPendingGroups(any());
3867 
3868     mobileDataDownload.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK).get();
3869     verify(mockMobileDataDownloadManager, times(2)).downloadAllPendingGroups(eq(false), any());
3870 
3871     mobileDataDownload.handleTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK).get();
3872     verify(mockMobileDataDownloadManager, times(2)).downloadAllPendingGroups(eq(true), any());
3873   }
3874 
3875   @Test
handleTask_invalid_tag()3876   public void handleTask_invalid_tag() throws Exception {
3877     MobileDataDownload mobileDataDownload =
3878         new MobileDataDownloadImpl(
3879             context,
3880             mockEventLogger,
3881             mockMobileDataDownloadManager,
3882             controlExecutor,
3883             ImmutableList.of(mockFileGroupPopulator),
3884             Optional.of(mockTaskScheduler),
3885             fileStorage,
3886             /* downloadMonitorOptional= */ Optional.absent(),
3887             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3888             flags,
3889             singleFileDownloader,
3890             Optional.absent() /* customFileGroupValidator */,
3891             timeSource);
3892 
3893     ExecutionException e =
3894         assertThrows(
3895             ExecutionException.class, () -> mobileDataDownload.handleTask("invalid-tag").get());
3896     assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
3897   }
3898 
3899   @Test
handleTask_onePopulatorFails_continuesToRunOthers()3900   public void handleTask_onePopulatorFails_continuesToRunOthers() throws Exception {
3901     FileGroupPopulator failingPopulator =
3902         (MobileDataDownload unused) ->
3903             Futures.immediateFailedFuture(new IllegalArgumentException("test"));
3904 
3905     AtomicBoolean refreshedOneSucceedingPopulator = new AtomicBoolean(false);
3906     FileGroupPopulator oneSucceedingPopulator =
3907         (MobileDataDownload unused) -> {
3908           refreshedOneSucceedingPopulator.set(true);
3909           return Futures.immediateVoidFuture();
3910         };
3911 
3912     AtomicBoolean refreshedAnotherSucceedingPopulator = new AtomicBoolean(false);
3913     FileGroupPopulator anotherSucceedingPopulator =
3914         (MobileDataDownload unused) -> {
3915           refreshedAnotherSucceedingPopulator.set(true);
3916           return Futures.immediateVoidFuture();
3917         };
3918 
3919     // The populators will be executed in this order.
3920     ImmutableList<FileGroupPopulator> populators =
3921         ImmutableList.of(failingPopulator, oneSucceedingPopulator, anotherSucceedingPopulator);
3922 
3923     MobileDataDownload mobileDataDownload =
3924         new MobileDataDownloadImpl(
3925             context,
3926             mockEventLogger,
3927             mockMobileDataDownloadManager,
3928             controlExecutor,
3929             populators,
3930             Optional.of(mockTaskScheduler),
3931             fileStorage,
3932             /* downloadMonitorOptional= */ Optional.absent(),
3933             Optional.of(this.getClass()), // don't need to use the real foreground download service.
3934             flags,
3935             singleFileDownloader,
3936             Optional.absent() /* customFileGroupValidator */,
3937             timeSource);
3938 
3939     when(mockMobileDataDownloadManager.verifyAllPendingGroups(any() /* validator */))
3940         .thenReturn(Futures.immediateVoidFuture());
3941     when(mockMobileDataDownloadManager.downloadAllPendingGroups(
3942             anyBoolean() /*wifi*/, any() /* validator */))
3943         .thenReturn(Futures.immediateVoidFuture());
3944 
3945     ListenableFuture<Void> handleFuture =
3946         mobileDataDownload.handleTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK);
3947     assertThat(handleFuture).whenDone().isSuccessful();
3948 
3949     handleFuture.get();
3950     assertThat(refreshedOneSucceedingPopulator.get()).isTrue();
3951     assertThat(refreshedAnotherSucceedingPopulator.get()).isTrue();
3952   }
3953 
3954   @Test
reportUsage_basic()3955   public void reportUsage_basic() throws Exception {
3956     when(mockMobileDataDownloadManager.getFileGroup(any(GroupKey.class), eq(true)))
3957         .thenReturn(Futures.immediateFuture(FILE_GROUP_INTERNAL_1));
3958     when(mockMobileDataDownloadManager.getDataFileUris(
3959             FILE_GROUP_INTERNAL_1, /* verifyIsolatedStructure= */ true))
3960         .thenReturn(
3961             Futures.immediateFuture(
3962                 ImmutableMap.of(FILE_GROUP_INTERNAL_1.getFile(0), onDeviceUri1)));
3963 
3964     MobileDataDownload mobileDataDownload = createDefaultMobileDataDownload();
3965 
3966     ClientFileGroup clientFileGroup =
3967         mobileDataDownload
3968             .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME_1).build())
3969             .get();
3970 
3971     UsageEvent usageEvent =
3972         UsageEvent.builder()
3973             .setEventCode(0)
3974             .setAppVersion(123)
3975             .setClientFileGroup(clientFileGroup)
3976             .build();
3977     mobileDataDownload.reportUsage(usageEvent).get();
3978 
3979     verify(mockEventLogger).logMddUsageEvent(createFileGroupStats(clientFileGroup), null);
3980   }
3981 
createFileGroupStats(ClientFileGroup clientFileGroup)3982   private static DataDownloadFileGroupStats createFileGroupStats(ClientFileGroup clientFileGroup) {
3983     return DataDownloadFileGroupStats.newBuilder()
3984         .setFileGroupName(clientFileGroup.getGroupName())
3985         .setOwnerPackage(clientFileGroup.getOwnerPackage())
3986         .setFileGroupVersionNumber(clientFileGroup.getVersionNumber())
3987         .setFileCount(clientFileGroup.getFileCount())
3988         .setVariantId(clientFileGroup.getVariantId())
3989         .setBuildId(clientFileGroup.getBuildId())
3990         .build();
3991   }
3992 
createDefaultMobileDataDownload()3993   private MobileDataDownload createDefaultMobileDataDownload() {
3994     return new MobileDataDownloadImpl(
3995         context,
3996         mockEventLogger,
3997         mockMobileDataDownloadManager,
3998         controlExecutor,
3999         ImmutableList.of() /* fileGroupPopulatorList */,
4000         Optional.of(mockTaskScheduler),
4001         fileStorage,
4002         Optional.absent() /* downloadMonitorOptional */,
4003         Optional.of(this.getClass()), // don't need to use the real foreground download service.
4004         flags,
4005         singleFileDownloader,
4006         Optional.absent() /* customFileGroupValidator */,
4007         timeSource);
4008   }
4009 }
4010