• 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.internal;
17 
18 import static com.google.android.libraries.mobiledatadownload.internal.MddTestUtil.writeSharedFiles;
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 import static org.junit.Assert.assertThrows;
22 import static org.mockito.ArgumentMatchers.any;
23 import static org.mockito.ArgumentMatchers.anyInt;
24 import static org.mockito.ArgumentMatchers.anyList;
25 import static org.mockito.ArgumentMatchers.anyLong;
26 import static org.mockito.ArgumentMatchers.eq;
27 import static org.mockito.ArgumentMatchers.isA;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.reset;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.verifyNoInteractions;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 import static org.mockito.Mockito.when;
35 
36 import android.accounts.Account;
37 import android.content.Context;
38 import android.content.pm.PackageInfo;
39 import android.content.pm.PackageManager;
40 import android.net.Uri;
41 import android.os.Build;
42 import androidx.test.core.app.ApplicationProvider;
43 import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
44 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupBookkeeping;
45 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
46 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders;
47 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions;
48 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.ActivatingCondition;
49 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy;
50 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceStoragePolicy;
51 import com.google.mobiledatadownload.internal.MetadataProto.ExtraHttpHeader;
52 import com.google.mobiledatadownload.internal.MetadataProto.FileStatus;
53 import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
54 import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey;
55 import com.google.mobiledatadownload.internal.MetadataProto.SharedFile;
56 import com.google.android.libraries.mobiledatadownload.AccountSource;
57 import com.google.android.libraries.mobiledatadownload.AggregateException;
58 import com.google.android.libraries.mobiledatadownload.DownloadException;
59 import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
60 import com.google.android.libraries.mobiledatadownload.FileSource;
61 import com.google.android.libraries.mobiledatadownload.SilentFeedback;
62 import com.google.android.libraries.mobiledatadownload.account.AccountUtil;
63 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
64 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
65 import com.google.android.libraries.mobiledatadownload.file.common.LimitExceededException;
66 import com.google.android.libraries.mobiledatadownload.file.spi.Backend;
67 import com.google.android.libraries.mobiledatadownload.internal.FileGroupManager.GroupDownloadStatus;
68 import com.google.android.libraries.mobiledatadownload.internal.collect.GroupKeyAndGroup;
69 import com.google.android.libraries.mobiledatadownload.internal.downloader.DownloaderCallbackImpl;
70 import com.google.android.libraries.mobiledatadownload.internal.downloader.MddFileDownloader;
71 import com.google.android.libraries.mobiledatadownload.internal.experimentation.DownloadStageManager;
72 import com.google.android.libraries.mobiledatadownload.internal.experimentation.NoOpDownloadStageManager;
73 import com.google.android.libraries.mobiledatadownload.internal.logging.DownloadStateLogger;
74 import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger;
75 import com.google.android.libraries.mobiledatadownload.internal.logging.testing.FakeEventLogger;
76 import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil;
77 import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil;
78 import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor;
79 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
80 import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
81 import com.google.common.base.Optional;
82 import com.google.common.collect.ArrayListMultimap;
83 import com.google.common.collect.ImmutableList;
84 import com.google.common.collect.ImmutableMap;
85 import com.google.common.collect.Lists;
86 import com.google.common.labs.concurrent.LabsFutures;
87 import com.google.common.truth.Correspondence;
88 import com.google.common.util.concurrent.AsyncFunction;
89 import com.google.common.util.concurrent.Futures;
90 import com.google.common.util.concurrent.ListenableFuture;
91 import com.google.common.util.concurrent.MoreExecutors;
92 import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent;
93 import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult;
94 import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats;
95 import com.google.protobuf.Any;
96 import com.google.protobuf.ByteString;
97 import com.google.protobuf.ExtensionRegistryLite;
98 import com.google.protobuf.StringValue;
99 import java.io.Closeable;
100 import java.io.File;
101 import java.io.FileOutputStream;
102 import java.io.IOException;
103 import java.util.ArrayList;
104 import java.util.Arrays;
105 import java.util.Calendar;
106 import java.util.List;
107 import java.util.concurrent.ExecutionException;
108 import java.util.concurrent.Executor;
109 import java.util.concurrent.Executors;
110 import java.util.concurrent.TimeUnit;
111 import org.junit.Before;
112 import org.junit.Rule;
113 import org.junit.Test;
114 import org.junit.rules.TemporaryFolder;
115 import org.junit.runner.RunWith;
116 import org.mockito.ArgumentCaptor;
117 import org.mockito.Captor;
118 import org.mockito.Mock;
119 import org.mockito.invocation.InvocationOnMock;
120 import org.mockito.junit.MockitoJUnit;
121 import org.mockito.junit.MockitoRule;
122 import org.mockito.stubbing.Answer;
123 import org.robolectric.RobolectricTestRunner;
124 import org.robolectric.Shadows;
125 import org.robolectric.util.ReflectionHelpers;
126 
127 @RunWith(RobolectricTestRunner.class)
128 public class FileGroupManagerTest {
129 
130   private static final long CURRENT_TIMESTAMP = 1000;
131 
132   private static final int TRAFFIC_TAG = 1000;
133 
134   private static final Executor SEQUENTIAL_CONTROL_EXECUTOR =
135       Executors.newSingleThreadScheduledExecutor();
136 
137   private static final String TEST_GROUP = "test-group";
138   private static final String TEST_GROUP_2 = "test-group-2";
139   private static final String TEST_GROUP_3 = "test-group-3";
140   private static final String TEST_GROUP_4 = "test-group-4";
141   private static final long FILE_GROUP_EXPIRATION_DATE_SECS = 10;
142   private static final String HOST_APP_LOG_SOURCE = "HOST_APP_LOG_SOURCE";
143   private static final String HOST_APP_PRIMES_LOG_SOURCE = "HOST_APP_PRIMES_LOG_SOURCE";
144 
145   private static final Correspondence<GroupKey, String> GROUP_KEY_TO_VARIANT =
146       Correspondence.transforming(GroupKey::getVariantId, "using variant");
147   private static final Correspondence<GroupKeyAndGroup, String> KEY_GROUP_PAIR_TO_VARIANT =
148       Correspondence.transforming(
149           keyGroupPair -> {
150             assertThat(keyGroupPair.groupKey().getVariantId())
151                 .isEqualTo(keyGroupPair.dataFileGroup().getVariantId());
152             return keyGroupPair.dataFileGroup().getVariantId();
153           },
154           "using variant from group key and file group");
155 
156   private static GroupKey testKey;
157   private static GroupKey testKey2;
158   private static GroupKey testKey3;
159   private static GroupKey testKey4;
160 
161   private Context context;
162   private FileGroupManager fileGroupManager;
163   private FileGroupsMetadata fileGroupsMetadata;
164   private SharedFileManager sharedFileManager;
165   private SharedFilesMetadata sharedFilesMetadata;
166   private FakeTimeSource testClock;
167   private SynchronousFileStorage fileStorage;
168   public File publicDirectory;
169   private final TestFlags flags = new TestFlags();
170 
171   @Rule(order = 2)
172   public TemporaryFolder folder = new TemporaryFolder();
173 
174   @Rule(order = 3)
175   public final MockitoRule mocks = MockitoJUnit.rule();
176 
177   @Mock EventLogger mockLogger;
178   @Mock SilentFeedback mockSilentFeedback;
179   @Mock MddFileDownloader mockDownloader;
180   @Mock SharedFileManager mockSharedFileManager;
181   @Mock FileGroupsMetadata mockFileGroupsMetadata;
182   @Mock DownloadProgressMonitor mockDownloadMonitor;
183   @Mock AccountSource mockAccountSource;
184   @Mock Backend mockBackend;
185   @Mock Closeable closeable;
186 
187   @Captor ArgumentCaptor<FileSource> fileSourceCaptor;
188   @Captor ArgumentCaptor<GroupKey> groupKeyCaptor;
189   @Captor ArgumentCaptor<List<GroupKey>> groupKeysCaptor;
190 
191   private DownloadStageManager downloadStageManager;
192 
193   @Before
setUp()194   public void setUp() throws Exception {
195     context = ApplicationProvider.getApplicationContext();
196 
197     when(mockBackend.name()).thenReturn("blobstore");
198     fileStorage =
199         new SynchronousFileStorage(
200             Arrays.asList(AndroidFileBackend.builder(context).build(), mockBackend));
201 
202     testClock = new FakeTimeSource().set(CURRENT_TIMESTAMP);
203 
204     testKey =
205         GroupKey.newBuilder()
206             .setGroupName(TEST_GROUP)
207             .setOwnerPackage(context.getPackageName())
208             .build();
209     testKey2 =
210         GroupKey.newBuilder()
211             .setGroupName(TEST_GROUP_2)
212             .setOwnerPackage(context.getPackageName())
213             .build();
214     testKey3 =
215         GroupKey.newBuilder()
216             .setGroupName(TEST_GROUP_3)
217             .setOwnerPackage(context.getPackageName())
218             .build();
219     testKey4 =
220         GroupKey.newBuilder()
221             .setGroupName(TEST_GROUP_4)
222             .setOwnerPackage(context.getPackageName())
223             .build();
224 
225     fileGroupsMetadata =
226         new SharedPreferencesFileGroupsMetadata(
227             context,
228             testClock,
229             mockSilentFeedback,
230             Optional.absent(),
231             MoreExecutors.directExecutor());
232     sharedFilesMetadata =
233         new SharedPreferencesSharedFilesMetadata(
234             context, mockSilentFeedback, Optional.absent(), flags);
235     sharedFileManager =
236         new SharedFileManager(
237             context,
238             mockSilentFeedback,
239             sharedFilesMetadata,
240             fileStorage,
241             mockDownloader,
242             Optional.absent(),
243             Optional.of(mockDownloadMonitor),
244             mockLogger,
245             flags,
246             fileGroupsMetadata,
247             Optional.absent(),
248             MoreExecutors.directExecutor());
249 
250     downloadStageManager = new NoOpDownloadStageManager();
251 
252     fileGroupManager =
253         new FileGroupManager(
254             context,
255             mockLogger,
256             mockSilentFeedback,
257             fileGroupsMetadata,
258             sharedFileManager,
259             testClock,
260             Optional.of(mockAccountSource),
261             SEQUENTIAL_CONTROL_EXECUTOR,
262             Optional.absent(),
263             fileStorage,
264             downloadStageManager,
265             flags);
266     // TODO(b/117571083): Replace with fileStorage API.
267     File downloadDirectory =
268         new File(context.getFilesDir(), DirectoryUtil.MDD_STORAGE_MODULE + "/" + "shared");
269     publicDirectory = new File(downloadDirectory, DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS);
270     publicDirectory.mkdirs();
271 
272     // file sharing is available for SDK R+
273     ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.R);
274   }
275 
assertLoggedNewConfigs( FakeEventLogger fakeEventLogger, DataDownloadFileGroupStats fileGroupStats, Void newConfigReceivedInfo)276   private void assertLoggedNewConfigs(
277       FakeEventLogger fakeEventLogger,
278       DataDownloadFileGroupStats fileGroupStats,
279       Void newConfigReceivedInfo) {
280     ArrayListMultimap<DataDownloadFileGroupStats, Void> loggedConfigs =
281         fakeEventLogger.getLoggedNewConfigReceived();
282     assertThat(loggedConfigs).hasSize(1);
283     assertThat(loggedConfigs.get(fileGroupStats)).containsExactly(newConfigReceivedInfo);
284   }
285 
286   @Test
testAddGroupForDownload()287   public void testAddGroupForDownload() throws Exception {
288     FakeEventLogger fakeEventLogger = new FakeEventLogger();
289 
290     resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager);
291 
292     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
293     NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup);
294 
295     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
296     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
297 
298     // Check that downloaded file groups doesn't contain this file group.
299     assertThat(readDownloadedFileGroup(testKey)).isNull();
300 
301     assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull();
302     assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull();
303 
304     assertLoggedNewConfigs(
305         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
306   }
307 
308   @Test
testAddGroupForDownload_correctlyPopulatesBuildIdAndVariantId()309   public void testAddGroupForDownload_correctlyPopulatesBuildIdAndVariantId() throws Exception {
310     FakeEventLogger fakeEventLogger = new FakeEventLogger();
311     resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager);
312 
313     DataFileGroupInternal dataFileGroup =
314         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
315             .setBuildId(10)
316             .setVariantId("testVariant")
317             .build();
318     NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup);
319 
320     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
321     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
322 
323     // Check that downloaded file groups doesn't contain this file group.
324     assertThat(readDownloadedFileGroup(testKey)).isNull();
325 
326     assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull();
327     assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull();
328 
329     assertLoggedNewConfigs(
330         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
331   }
332 
333   @Test
testAddGroupForDownload_groupUpdated()334   public void testAddGroupForDownload_groupUpdated() throws Exception {
335     FakeEventLogger fakeEventLogger = new FakeEventLogger();
336     resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager);
337 
338     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
339     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
340     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
341 
342     assertLoggedNewConfigs(
343         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
344     fakeEventLogger.reset();
345 
346     // Update the file id and see that the group gets updated in the pending groups list.
347     dataFileGroup =
348         dataFileGroup.toBuilder()
349             .setFile(0, dataFileGroup.getFile(0).toBuilder().setFileId("file2"))
350             .setDownloadConditions(DownloadConditions.getDefaultInstance())
351             .build();
352     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
353     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
354 
355     assertLoggedNewConfigs(
356         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
357     fakeEventLogger.reset();
358 
359     // Update other parameters and check that we successfully add the group.
360     dataFileGroup = dataFileGroup.toBuilder().setFileGroupVersionNumber(2).build();
361     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
362     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
363 
364     assertLoggedNewConfigs(
365         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
366     fakeEventLogger.reset();
367 
368     dataFileGroup = dataFileGroup.toBuilder().setStaleLifetimeSecs(50).build();
369     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
370     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
371 
372     assertLoggedNewConfigs(
373         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
374     fakeEventLogger.reset();
375 
376     dataFileGroup =
377         dataFileGroup.toBuilder()
378             .setDownloadConditions(
379                 DownloadConditions.newBuilder()
380                     .setDeviceNetworkPolicy(
381                         DeviceNetworkPolicy.DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK))
382             .build();
383     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
384     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
385 
386     assertLoggedNewConfigs(
387         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
388     fakeEventLogger.reset();
389 
390     DownloadConditions downloadConditions =
391         DownloadConditions.newBuilder()
392             .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_LOWER_THRESHOLD)
393             .build();
394     dataFileGroup = dataFileGroup.toBuilder().setDownloadConditions(downloadConditions).build();
395     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
396     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
397 
398     assertLoggedNewConfigs(
399         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
400     fakeEventLogger.reset();
401 
402     dataFileGroup =
403         dataFileGroup.toBuilder()
404             .setAllowedReadersEnum(AllowedReaders.ONLY_GOOGLE_PLAY_SERVICES)
405             .build();
406     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
407     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
408 
409     assertLoggedNewConfigs(
410         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
411   }
412 
413   @Test
testAddGroupForDownload_groupUpdated_whenBuildChanges()414   public void testAddGroupForDownload_groupUpdated_whenBuildChanges() throws Exception {
415     FakeEventLogger fakeEventLogger = new FakeEventLogger();
416     resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager);
417 
418     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
419     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
420     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
421 
422     // Reset to clear events before next add group call
423     fakeEventLogger.reset();
424 
425     // Update the file id and see that the group gets updated in the pending groups list.
426     dataFileGroup = dataFileGroup.toBuilder().setBuildId(123456789L).build();
427     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
428     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
429 
430     assertLoggedNewConfigs(
431         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
432   }
433 
434   @Test
testAddGroupForDownload_groupUpdated_whenVariantChanges()435   public void testAddGroupForDownload_groupUpdated_whenVariantChanges() throws Exception {
436     FakeEventLogger fakeEventLogger = new FakeEventLogger();
437     resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager);
438 
439     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
440     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
441     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
442 
443     // Reset to clear events before next add group call
444     fakeEventLogger.reset();
445 
446     // Update the file id and see that the group gets updated in the pending groups list.
447     dataFileGroup = dataFileGroup.toBuilder().setVariantId("some-different-variant").build();
448     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
449     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
450 
451     assertLoggedNewConfigs(
452         fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
453   }
454 
455   @Test
testAddGroupForDownloadWithSyncId_failedToUpdateMetadataNoScheduleViaSpe()456   public void testAddGroupForDownloadWithSyncId_failedToUpdateMetadataNoScheduleViaSpe()
457       throws Exception {
458     // Mock FileGroupsMetadata and SharedFileManager to test failure scenario.
459     resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager);
460     DataFileGroupInternal dataFileGroup =
461         MddTestUtil.createDataFileGroupInternalWithDownloadId(TEST_GROUP, 2);
462     NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup);
463 
464     when(mockSharedFileManager.reserveFileEntry(any(NewFileKey.class)))
465         .thenReturn(Futures.immediateFuture(true));
466 
467     // Failed to write to Metadata, no task will be scheduled via SPE.
468     when(mockFileGroupsMetadata.write(any(GroupKey.class), any(DataFileGroupInternal.class)))
469         .thenReturn(Futures.immediateFuture(false));
470     when(mockFileGroupsMetadata.read(any(GroupKey.class)))
471         .thenReturn(Futures.immediateFuture(null));
472 
473     ListenableFuture<Boolean> addGroupFuture =
474         fileGroupManager.addGroupForDownload(testKey, dataFileGroup);
475     assertThrows(ExecutionException.class, addGroupFuture::get);
476     IOException e = LabsFutures.getFailureCauseAs(addGroupFuture, IOException.class);
477     assertThat(e).hasMessageThat().contains("Failed to commit new group metadata to disk.");
478 
479     // Check that downloaded file groups doesn't contain this file group.
480     GroupKey downloadedkey = testKey.toBuilder().setDownloaded(true).build();
481     assertWithMessage(String.format("Expected that key %s should not exist.", downloadedkey))
482         .that(mockFileGroupsMetadata.read(downloadedkey).get())
483         .isNull();
484     // Check that the get method doesn't return this file group.
485     assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull();
486 
487     verify(mockSharedFileManager).reserveFileEntry(groupKeys[0]);
488     verify(mockSharedFileManager).reserveFileEntry(groupKeys[1]);
489   }
490 
491   @Test
testAddGroupForDownload_duplicatePendingGroup()492   public void testAddGroupForDownload_duplicatePendingGroup() throws Exception {
493     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
494 
495     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
496     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
497 
498     // Send the exact same group again, and check that it is considered duplicate.
499     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isFalse();
500   }
501 
502   @Test
testAddGroupForDownload_duplicateDownloadedGroup()503   public void testAddGroupForDownload_duplicateDownloadedGroup() throws Exception {
504     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
505     writeDownloadedFileGroup(testKey, dataFileGroup);
506 
507     // Send the exact same group as the downloaded group, and check that it is considered duplicate.
508     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isFalse();
509 
510     verifyNoInteractions(mockLogger);
511   }
512 
513   @Test
testAddGroupForDownload_filePropertiesUpdated()514   public void testAddGroupForDownload_filePropertiesUpdated() throws Exception {
515     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
516     writePendingFileGroup(testKey, dataFileGroup);
517 
518     dataFileGroup =
519         dataFileGroup.toBuilder()
520             .setFile(0, dataFileGroup.getFile(0).toBuilder().setUrlToDownload("https://file2"))
521             .build();
522     // Send the same group with different property, and check that it is NOT duplicate.
523     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
524 
525     dataFileGroup =
526         dataFileGroup.toBuilder()
527             .setFile(0, dataFileGroup.getFile(0).toBuilder().setUrlToDownload("https://file3"))
528             .build();
529     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
530   }
531 
532   @Test
testAddGroupForDownload_differentPendingGroup_duplicateDownloadedGroup()533   public void testAddGroupForDownload_differentPendingGroup_duplicateDownloadedGroup()
534       throws Exception {
535 
536     DataFileGroupInternal firstGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
537     assertThat(fileGroupManager.addGroupForDownload(testKey, firstGroup).get()).isTrue();
538     verifyAddGroupForDownloadWritesMetadata(testKey, firstGroup, CURRENT_TIMESTAMP);
539 
540     verify(mockLogger)
541         .logNewConfigReceived(createFileGroupDetails(firstGroup).clearFileCount().build(), null);
542     reset(mockLogger);
543 
544     // Create a second group that is identical except for one different file id.
545     DataFileGroupInternal.Builder secondGroup =
546         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder();
547     secondGroup.setFile(0, secondGroup.getFile(0).toBuilder().setFileId("file2"));
548     writeDownloadedFileGroup(testKey, secondGroup.build());
549 
550     // Send the updated group, and check that it is not considered duplicate.
551     assertThat(fileGroupManager.addGroupForDownload(testKey, secondGroup.build()).get()).isTrue();
552     verifyAddGroupForDownloadWritesMetadata(testKey, secondGroup.build(), CURRENT_TIMESTAMP);
553     verify(mockLogger)
554         .logNewConfigReceived(createFileGroupDetails(firstGroup).clearFileCount().build(), null);
555   }
556 
557   @Test
testAddGroupForDownload_subscribeFailed()558   public void testAddGroupForDownload_subscribeFailed() throws Exception {
559     // Mock SharedFileManager to test failure scenario.
560     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
561     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3);
562     NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup);
563 
564     ArgumentCaptor<NewFileKey> fileCaptor = ArgumentCaptor.forClass(NewFileKey.class);
565     when(mockSharedFileManager.reserveFileEntry(fileCaptor.capture()))
566         .thenReturn(
567             Futures.immediateFuture(true),
568             Futures.immediateFuture(false),
569             Futures.immediateFuture(true));
570     ExecutionException ex =
571         assertThrows(
572             ExecutionException.class,
573             fileGroupManager.addGroupForDownload(testKey, dataFileGroup)::get);
574     assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);
575 
576     // Verify that we tried to subscribe to only the first 2 files.
577     assertThat(fileCaptor.getAllValues()).containsExactly(groupKeys[0], groupKeys[1]);
578 
579     verify(mockLogger)
580         .logNewConfigReceived(createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
581   }
582 
583   @Test
testAddGroupForDownload_subscribeFailed_firstFile()584   public void testAddGroupForDownload_subscribeFailed_firstFile() throws Exception {
585     // Mock SharedFileManager to test failure scenario.
586     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
587     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3);
588     NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup);
589 
590     ArgumentCaptor<NewFileKey> fileCaptor = ArgumentCaptor.forClass(NewFileKey.class);
591     when(mockSharedFileManager.reserveFileEntry(fileCaptor.capture()))
592         .thenReturn(
593             Futures.immediateFuture(false),
594             Futures.immediateFuture(true),
595             Futures.immediateFuture(true));
596     ExecutionException ex =
597         assertThrows(
598             ExecutionException.class,
599             fileGroupManager.addGroupForDownload(testKey, dataFileGroup)::get);
600     assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);
601 
602     // Verify that we tried to subscribe to only the first file.
603     assertThat(fileCaptor.getAllValues()).containsExactly(groupKeys[0]);
604 
605     verify(mockLogger)
606         .logNewConfigReceived(createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
607   }
608 
609   @Test
testAddGroupForDownload_alreadyDownloadedGroup()610   public void testAddGroupForDownload_alreadyDownloadedGroup() throws Exception {
611     // Write a group to the pending shared prefs.
612     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
613     writePendingFileGroup(testKey, pendingGroup);
614 
615     DataFileGroupInternal oldDownloadedGroup =
616         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
617     writeDownloadedFileGroup(testKey, oldDownloadedGroup);
618 
619     // Add a newer version of that group
620     DataFileGroupInternal receivedGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3);
621 
622     assertThat(fileGroupManager.addGroupForDownload(testKey, receivedGroup).get()).isTrue();
623 
624     // The new added group should be the pending group.
625     verifyAddGroupForDownloadWritesMetadata(testKey, receivedGroup, CURRENT_TIMESTAMP);
626     assertThat(oldDownloadedGroup).isEqualTo(readDownloadedFileGroup(testKey));
627   }
628 
629   @Test
testAddGroupForDownload_addEmptyGroup()630   public void testAddGroupForDownload_addEmptyGroup() throws Exception {
631     // Write a group to the pending shared prefs.
632     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
633     writePendingFileGroup(testKey, pendingGroup);
634 
635     DataFileGroupInternal emptyGroup =
636         DataFileGroupInternal.newBuilder().setGroupName(TEST_GROUP).build();
637     assertThat(fileGroupManager.addGroupForDownload(testKey, emptyGroup).get()).isTrue();
638     verifyAddGroupForDownloadWritesMetadata(testKey, emptyGroup, CURRENT_TIMESTAMP);
639   }
640 
641   @Test
testAddGroupForDownload_addGroupForUninstalledApp()642   public void testAddGroupForDownload_addGroupForUninstalledApp() throws Exception {
643     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
644     GroupKey uninstalledAppKey =
645         GroupKey.newBuilder().setGroupName(TEST_GROUP).setOwnerPackage("not.installed.app").build();
646 
647     // Send a group with an owner package that is not installed. Ensure that this group is rejected.
648     assertThrows(
649         UninstalledAppException.class,
650         () -> fileGroupManager.addGroupForDownload(uninstalledAppKey, dataFileGroup));
651     verify(mockLogger)
652         .logEventSampled(
653             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
654             TEST_GROUP,
655             /* fileGroupVersionNumber= */ 0,
656             /* buildId= */ 0,
657             /* variantId= */ "");
658     verifyNoMoreInteractions(mockLogger);
659   }
660 
661   @Test
testAddGroupForDownload_expiredGroup()662   public void testAddGroupForDownload_expiredGroup() throws Exception {
663     Calendar date = new Calendar.Builder().setDate(1970, Calendar.JANUARY, 2).build();
664     DataFileGroupInternal dataFileGroup =
665         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
666             .setExpirationDateSecs(date.getTimeInMillis() / 1000)
667             .build();
668 
669     testClock.set(System.currentTimeMillis());
670 
671     // Send a group with an expiration date that has already passed.
672     assertThrows(
673         ExpiredFileGroupException.class,
674         () -> fileGroupManager.addGroupForDownload(testKey, dataFileGroup));
675     verify(mockLogger)
676         .logEventSampled(
677             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
678             TEST_GROUP,
679             /* fileGroupVersionNumber= */ 0,
680             /* buildId= */ 0,
681             /* variantId= */ "");
682     verifyNoMoreInteractions(mockLogger);
683   }
684 
685   @Test
testAddGroupForDownload_justExpiredGroup()686   public void testAddGroupForDownload_justExpiredGroup() throws Exception {
687     long oneHourAgo = (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)) / 1000;
688     DataFileGroupInternal dataFileGroup =
689         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
690             .setExpirationDateSecs(oneHourAgo)
691             .build();
692 
693     testClock.set(System.currentTimeMillis());
694 
695     // Send a group with an expiration date that has already passed.
696     assertThrows(
697         ExpiredFileGroupException.class,
698         () -> fileGroupManager.addGroupForDownload(testKey, dataFileGroup));
699     verify(mockLogger)
700         .logEventSampled(
701             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
702             TEST_GROUP,
703             /* fileGroupVersionNumber= */ 0,
704             /* buildId= */ 0,
705             /* variantId= */ "");
706     verifyNoMoreInteractions(mockLogger);
707   }
708 
709   @Test
testAddGroupForDownload_nonexpiredGroup()710   public void testAddGroupForDownload_nonexpiredGroup() throws Exception {
711     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
712     NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup);
713 
714     long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000;
715     dataFileGroup = dataFileGroup.toBuilder().setExpirationDateSecs(tenDaysFromNow).build();
716 
717     testClock.set(System.currentTimeMillis());
718 
719     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
720     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, testClock.currentTimeMillis());
721 
722     // Check that downloaded file groups doesn't contain this file group.
723     assertThat(readDownloadedFileGroup(testKey)).isNull();
724     // Check that the get method doesn't return this file group.
725     assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull();
726 
727     assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull();
728     assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull();
729     verify(mockLogger)
730         .logNewConfigReceived(createFileGroupDetails(dataFileGroup).clearFileCount().build(), null);
731   }
732 
733   @Test
testAddGroupForDownload_nonexpiredGroupNoExpiration()734   public void testAddGroupForDownload_nonexpiredGroupNoExpiration() throws Exception {
735     DataFileGroupInternal.Builder dataFileGroup =
736         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder();
737     NewFileKey[] groupKeys =
738         MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup.build());
739 
740     dataFileGroup.setExpirationDateSecs(0); // 0 means don't expire
741 
742     testClock.set(System.currentTimeMillis());
743 
744     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()).isTrue();
745     verifyAddGroupForDownloadWritesMetadata(
746         testKey, dataFileGroup.build(), testClock.currentTimeMillis());
747 
748     // Check that downloaded file groups doesn't contain this file group.
749     assertThat(readDownloadedFileGroup(testKey)).isNull();
750     // Check that the get method doesn't return this file group.
751     assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull();
752 
753     assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull();
754     assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull();
755     verify(mockLogger)
756         .logNewConfigReceived(
757             createFileGroupDetails(dataFileGroup.build()).clearFileCount().build(), null);
758   }
759 
760   @Test
testAddGroupForDownload_extendExpiration()761   public void testAddGroupForDownload_extendExpiration() throws Exception {
762     DataFileGroupInternal.Builder dataFileGroup =
763         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder();
764     long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000;
765     dataFileGroup.setExpirationDateSecs(tenDaysFromNow);
766 
767     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()).isTrue();
768     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), CURRENT_TIMESTAMP);
769 
770     // Now send the group again with a longer expiration.
771     long twentyDaysFromNow = tenDaysFromNow + TimeUnit.DAYS.toSeconds(10);
772     dataFileGroup = dataFileGroup.setExpirationDateSecs(twentyDaysFromNow);
773 
774     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()).isTrue();
775     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), CURRENT_TIMESTAMP);
776   }
777 
778   @Test
testAddGroupForDownload_reduceExpiration()779   public void testAddGroupForDownload_reduceExpiration() throws Exception {
780     long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000;
781     DataFileGroupInternal dataFileGroup =
782         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
783             .setExpirationDateSecs(tenDaysFromNow)
784             .build();
785 
786     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
787     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
788 
789     // Now send the group again with a longer expiration.
790     long fiveDaysFromNow = tenDaysFromNow - TimeUnit.DAYS.toSeconds(5);
791     dataFileGroup = dataFileGroup.toBuilder().setExpirationDateSecs(fiveDaysFromNow).build();
792 
793     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
794     verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP);
795   }
796 
797   @Test
testAddGroupForDownload_delayedDownload()798   public void testAddGroupForDownload_delayedDownload() throws Exception {
799     flags.enableDelayedDownload = Optional.of(true);
800 
801     // Create 2 groups, one of which requires device side activation.
802     DataFileGroupInternal fileGroup1 =
803         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
804             .setDownloadConditions(
805                 DownloadConditions.newBuilder()
806                     .setActivatingCondition(ActivatingCondition.DEVICE_ACTIVATED))
807             .build();
808 
809     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 3);
810 
811     // Assert that adding the first group throws an exception.
812     ExecutionException ex =
813         assertThrows(
814             ExecutionException.class,
815             fileGroupManager.addGroupForDownload(testKey, fileGroup1)::get);
816     assertThat(ex).hasCauseThat().isInstanceOf(ActivationRequiredForGroupException.class);
817     assertThat(fileGroupManager.addGroupForDownload(testKey2, fileGroup2).get()).isTrue();
818 
819     // Now activate the group and verify that we are able to add the first group.
820     assertThat(fileGroupManager.setGroupActivation(testKey, true).get()).isTrue();
821     assertThat(fileGroupManager.addGroupForDownload(testKey, fileGroup1).get()).isTrue();
822 
823     // Deactivate the group again and verify that we should no longer be able to add it.
824     assertThat(fileGroupManager.setGroupActivation(testKey, false).get()).isTrue();
825     ex =
826         assertThrows(
827             ExecutionException.class,
828             fileGroupManager.addGroupForDownload(testKey, fileGroup1)::get);
829     assertThat(ex).hasCauseThat().isInstanceOf(ActivationRequiredForGroupException.class);
830   }
831 
832   @Test
testAddGroupForDownload_onWifiFirst()833   public void testAddGroupForDownload_onWifiFirst() throws Exception {
834     int elapsedTime = 1000;
835     DataFileGroupInternal.Builder dataFileGroup =
836         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder();
837 
838     {
839       testClock.set(elapsedTime);
840       assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get())
841           .isTrue();
842       // The wifi only download timestamp is set correctly.
843       verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime);
844     }
845 
846     {
847       // Update metadata does not change the wifi only download timestamp.
848       long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000;
849       dataFileGroup.setExpirationDateSecs(tenDaysFromNow);
850       assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get())
851           .isTrue();
852       verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime);
853     }
854 
855     {
856       // Change another metadata field does not change the wifi only download timestamp.
857       dataFileGroup.setFileGroupVersionNumber(2);
858       assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get())
859           .isTrue();
860       // The wifi only download timestamp does not change.
861       verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime);
862     }
863 
864     {
865       // Update the file's urlToDownload will reset the wifi only download timestamp.
866       elapsedTime = 2000;
867       testClock.set(elapsedTime);
868       dataFileGroup.setFile(
869           0, dataFileGroup.getFile(0).toBuilder().setUrlToDownload("https://new_url"));
870       assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get())
871           .isTrue();
872       // The wifi only download timestamp change since we change the urlToDownload
873       verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime);
874     }
875 
876     {
877       // Update the file's byteSize will reset the wifi only download timestamp.
878       elapsedTime = 3000;
879       testClock.set(elapsedTime);
880       dataFileGroup.setFile(1, dataFileGroup.getFile(1).toBuilder().setByteSize(5001));
881       assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get())
882           .isTrue();
883       // The wifi only download timestamp change since we change the urlToDownload
884       verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime);
885     }
886 
887     {
888       // Update the file's checksum will reset the wifi only download timestamp.
889       elapsedTime = 4000;
890       testClock.set(elapsedTime);
891       dataFileGroup.setFile(1, dataFileGroup.getFile(1).toBuilder().setChecksum("new check sum"));
892       assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get())
893           .isTrue();
894       // The wifi only download timestamp change since we change the urlToDownload
895       verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime);
896     }
897   }
898 
899   @Test
testAddGroupForDownload_addsSideloadedGroup()900   public void testAddGroupForDownload_addsSideloadedGroup() throws Exception {
901     // Create sideloaded group
902     DataFileGroupInternal sideloadedGroup =
903         DataFileGroupInternal.newBuilder()
904             .setGroupName(TEST_GROUP)
905             .addFile(
906                 DataFile.newBuilder()
907                     .setFileId("sideloaded_file")
908                     .setUrlToDownload("file:/test")
909                     .setChecksumType(DataFile.ChecksumType.NONE)
910                     .build())
911             .build();
912 
913     assertThat(fileGroupManager.addGroupForDownload(testKey, sideloadedGroup).get()).isTrue();
914 
915     verifyAddGroupForDownloadWritesMetadata(testKey, sideloadedGroup, 1000L);
916   }
917 
918   @Test
testAddGroupForDownload_multipleVariants()919   public void testAddGroupForDownload_multipleVariants() throws Exception {
920     // Create 3 group keys of the same group, but with different variants
921     GroupKey defaultGroupKey =
922         GroupKey.newBuilder()
923             .setGroupName(TEST_GROUP)
924             .setOwnerPackage(context.getPackageName())
925             .build();
926     GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build();
927     GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build();
928 
929     DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
930     DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build();
931     DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build();
932 
933     assertThat(fileGroupManager.addGroupForDownload(defaultGroupKey, defaultFileGroup).get())
934         .isTrue();
935     assertThat(fileGroupManager.addGroupForDownload(enGroupKey, enFileGroup).get()).isTrue();
936     assertThat(fileGroupManager.addGroupForDownload(frGroupKey, frFileGroup).get()).isTrue();
937 
938     assertThat(fileGroupsMetadata.getAllGroupKeys().get())
939         .comparingElementsUsing(GROUP_KEY_TO_VARIANT)
940         .containsExactly("", "en", "fr");
941   }
942 
943   @Test
removeFileGroup_noVersionExists()944   public void removeFileGroup_noVersionExists() throws Exception {
945     // No record for both pending key and downloaded key.
946     GroupKey groupKey =
947         GroupKey.newBuilder()
948             .setGroupName(TEST_GROUP)
949             .setOwnerPackage(context.getPackageName())
950             .build();
951 
952     fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get();
953 
954     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
955     assertThat(fileGroupsMetadata.getAllGroupKeys().get()).isEmpty();
956 
957     // There is no pending file group, so no call to clearSyncReasons.
958     verifyNoInteractions(mockLogger);
959   }
960 
961   @Test
removeFileGroup_pendingVersionExists()962   public void removeFileGroup_pendingVersionExists() throws Exception {
963     DataFile dataFile1 = DataFile.newBuilder().setFileId("file1").setChecksum("checksum1").build();
964     DataFile dataFile2 = DataFile.newBuilder().setFileId("file2").setChecksum("checksum2").build();
965 
966     NewFileKey newFileKey1 =
967         SharedFilesMetadata.createKeyFromDataFile(dataFile1, AllowedReaders.ALL_GOOGLE_APPS);
968     NewFileKey newFileKey2 =
969         SharedFilesMetadata.createKeyFromDataFile(dataFile2, AllowedReaders.ALL_GOOGLE_APPS);
970 
971     DataFileGroupInternal pendingFileGroup =
972         DataFileGroupInternal.newBuilder()
973             .setGroupName(TEST_GROUP)
974             .setFileGroupVersionNumber(1)
975             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
976             .addAllFile(Lists.newArrayList(dataFile1, dataFile2))
977             .build();
978 
979     GroupKey groupKey =
980         GroupKey.newBuilder()
981             .setGroupName(TEST_GROUP)
982             .setOwnerPackage(context.getPackageName())
983             .build();
984     GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build();
985     GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build();
986 
987     writePendingFileGroup(pendingGroupKey, pendingFileGroup);
988     writeSharedFiles(
989         sharedFilesMetadata,
990         pendingFileGroup,
991         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS));
992 
993     fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get();
994 
995     assertThat(readPendingFileGroup(pendingGroupKey)).isNull();
996     assertThat(readPendingFileGroup(downloadedGroupKey)).isNull();
997     assertThat(fileGroupsMetadata.getAllGroupKeys().get()).isEmpty();
998 
999     Uri pendingFileUri1 =
1000         DirectoryUtil.getOnDeviceUri(
1001             context,
1002             newFileKey1.getAllowedReaders(),
1003             dataFile1.getFileId(),
1004             newFileKey1.getChecksum(),
1005             mockSilentFeedback,
1006             /* instanceId= */ Optional.absent(),
1007             /* androidShared= */ false);
1008     Uri pendingFileUri2 =
1009         DirectoryUtil.getOnDeviceUri(
1010             context,
1011             newFileKey2.getAllowedReaders(),
1012             dataFile2.getFileId(),
1013             newFileKey2.getChecksum(),
1014             mockSilentFeedback,
1015             /* instanceId= */ Optional.absent(),
1016             /* androidShared= */ false);
1017 
1018     verify(mockDownloader).stopDownloading(newFileKey1.getChecksum(), pendingFileUri1);
1019     verify(mockDownloader).stopDownloading(newFileKey2.getChecksum(), pendingFileUri2);
1020 
1021     verifyNoInteractions(mockLogger);
1022   }
1023 
1024   @Test
removeFileGroup_downloadedVersionExists()1025   public void removeFileGroup_downloadedVersionExists() throws Exception {
1026     DataFile dataFile1 = DataFile.newBuilder().setFileId("file1").setChecksum("checksum1").build();
1027     DataFile dataFile2 = DataFile.newBuilder().setFileId("file2").setChecksum("checksum2").build();
1028 
1029     DataFileGroupInternal downloadedFileGroup =
1030         DataFileGroupInternal.newBuilder()
1031             .setGroupName(TEST_GROUP)
1032             .setFileGroupVersionNumber(0)
1033             .setBuildId(0)
1034             .setVariantId("")
1035             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
1036             .addAllFile(Lists.newArrayList(dataFile1, dataFile2))
1037             .build();
1038 
1039     GroupKey groupKey =
1040         GroupKey.newBuilder()
1041             .setGroupName(TEST_GROUP)
1042             .setOwnerPackage(context.getPackageName())
1043             .build();
1044     GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build();
1045     GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build();
1046 
1047     writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup);
1048     writeSharedFiles(
1049         sharedFilesMetadata,
1050         downloadedFileGroup,
1051         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
1052 
1053     fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get();
1054 
1055     assertThat(readPendingFileGroup(pendingGroupKey)).isNull();
1056     assertThat(readPendingFileGroup(downloadedGroupKey)).isNull();
1057     assertThat(fileGroupsMetadata.getAllStaleGroups().get())
1058         .containsExactly(
1059             downloadedFileGroup.toBuilder()
1060                 .setBookkeeping(
1061                     downloadedFileGroup.getBookkeeping().toBuilder()
1062                         .setStaleExpirationDate(1)
1063                         .build())
1064                 .build());
1065 
1066     verify(mockDownloader, never()).stopDownloading(any(String.class), any(Uri.class));
1067 
1068     verifyNoInteractions(mockLogger);
1069   }
1070 
1071   @Test
removeFileGroup_bothVersionsExist()1072   public void removeFileGroup_bothVersionsExist() throws Exception {
1073     DataFile registeredFile =
1074         DataFile.newBuilder().setFileId("file").setChecksum("registered").build();
1075     DataFile downloadedFile =
1076         DataFile.newBuilder().setFileId("file").setChecksum("downloaded").build();
1077 
1078     NewFileKey registeredFileKey =
1079         SharedFilesMetadata.createKeyFromDataFile(registeredFile, AllowedReaders.ALL_GOOGLE_APPS);
1080 
1081     DataFileGroupInternal pendingFileGroup =
1082         DataFileGroupInternal.newBuilder()
1083             .setGroupName(TEST_GROUP)
1084             .setFileGroupVersionNumber(1)
1085             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
1086             .addFile(registeredFile)
1087             .build();
1088     DataFileGroupInternal downloadedFileGroup =
1089         DataFileGroupInternal.newBuilder()
1090             .setGroupName(TEST_GROUP)
1091             .setFileGroupVersionNumber(0)
1092             .setBuildId(0)
1093             .setVariantId("")
1094             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
1095             .addFile(downloadedFile)
1096             .build();
1097 
1098     GroupKey groupKey =
1099         GroupKey.newBuilder()
1100             .setGroupName(TEST_GROUP)
1101             .setOwnerPackage(context.getPackageName())
1102             .build();
1103     GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build();
1104     GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build();
1105 
1106     writePendingFileGroup(pendingGroupKey, pendingFileGroup);
1107     writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup);
1108     writeSharedFiles(
1109         sharedFilesMetadata, pendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1110     writeSharedFiles(
1111         sharedFilesMetadata, downloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1112 
1113     fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get();
1114 
1115     assertThat(readPendingFileGroup(pendingGroupKey)).isNull();
1116     assertThat(readPendingFileGroup(downloadedGroupKey)).isNull();
1117     assertThat(fileGroupsMetadata.getAllStaleGroups().get())
1118         .containsExactly(
1119             downloadedFileGroup.toBuilder()
1120                 .setBookkeeping(
1121                     downloadedFileGroup.getBookkeeping().toBuilder()
1122                         .setStaleExpirationDate(1)
1123                         .build())
1124                 .build());
1125 
1126     Uri pendingFileUri =
1127         DirectoryUtil.getOnDeviceUri(
1128             context,
1129             registeredFileKey.getAllowedReaders(),
1130             registeredFile.getFileId(),
1131             registeredFileKey.getChecksum(),
1132             mockSilentFeedback,
1133             /* instanceId= */ Optional.absent(),
1134             /* androidShared= */ false);
1135 
1136     // Only called once to stop download of pending file.
1137     verify(mockDownloader).stopDownloading(registeredFileKey.getChecksum(), pendingFileUri);
1138 
1139     verifyNoInteractions(mockLogger);
1140   }
1141 
1142   @Test
removeFileGroup_bothVersionsExist_onlyRemovePending()1143   public void removeFileGroup_bothVersionsExist_onlyRemovePending() throws Exception {
1144     DataFile registeredFile =
1145         DataFile.newBuilder().setFileId("file").setChecksum("registered").build();
1146     DataFile downloadedFile =
1147         DataFile.newBuilder().setFileId("file").setChecksum("downloaded").build();
1148 
1149     NewFileKey registeredFileKey =
1150         SharedFilesMetadata.createKeyFromDataFile(registeredFile, AllowedReaders.ALL_GOOGLE_APPS);
1151 
1152     DataFileGroupInternal pendingFileGroup =
1153         DataFileGroupInternal.newBuilder()
1154             .setGroupName(TEST_GROUP)
1155             .setFileGroupVersionNumber(1)
1156             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
1157             .addFile(registeredFile)
1158             .build();
1159     DataFileGroupInternal downloadedFileGroup =
1160         DataFileGroupInternal.newBuilder()
1161             .setGroupName(TEST_GROUP)
1162             .setFileGroupVersionNumber(0)
1163             .setBuildId(0)
1164             .setVariantId("")
1165             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
1166             .addFile(downloadedFile)
1167             .build();
1168 
1169     GroupKey groupKey =
1170         GroupKey.newBuilder()
1171             .setGroupName(TEST_GROUP)
1172             .setOwnerPackage(context.getPackageName())
1173             .build();
1174     GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build();
1175     GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build();
1176 
1177     writePendingFileGroup(pendingGroupKey, pendingFileGroup);
1178     writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup);
1179     writeSharedFiles(
1180         sharedFilesMetadata, pendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1181     writeSharedFiles(
1182         sharedFilesMetadata, downloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1183 
1184     fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ true).get();
1185 
1186     assertThat(readPendingFileGroup(pendingGroupKey)).isNull();
1187     assertThat(readPendingFileGroup(downloadedGroupKey)).isNull();
1188 
1189     // Pending group was just removed, and downloaded was not added to stale groups.
1190     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
1191     // Downloaded group is still available.
1192     assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1193         .containsExactly(GroupKeyAndGroup.create(downloadedGroupKey, downloadedFileGroup));
1194 
1195     Uri pendingFileUri =
1196         DirectoryUtil.getOnDeviceUri(
1197             context,
1198             registeredFileKey.getAllowedReaders(),
1199             registeredFile.getFileId(),
1200             registeredFileKey.getChecksum(),
1201             mockSilentFeedback,
1202             /* instanceId= */ Optional.absent(),
1203             /* androidShared= */ false);
1204 
1205     // Only called once to stop download of pending file.
1206     verify(mockDownloader).stopDownloading(registeredFileKey.getChecksum(), pendingFileUri);
1207 
1208     verifyNoInteractions(mockLogger);
1209   }
1210 
1211   @Test
removeFileGroup_fileReferencedByOtherFileGroup_willNotCancelDownload()1212   public void removeFileGroup_fileReferencedByOtherFileGroup_willNotCancelDownload()
1213       throws Exception {
1214     DataFile dataFile1 = DataFile.newBuilder().setFileId("file1").setChecksum("checksum1").build();
1215     DataFile dataFile2 = DataFile.newBuilder().setFileId("file2").setChecksum("checksum2").build();
1216 
1217     DataFileGroupInternal pendingFileGroup =
1218         DataFileGroupInternal.newBuilder()
1219             .setGroupName(TEST_GROUP)
1220             .setFileGroupVersionNumber(1)
1221             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
1222             .addAllFile(Lists.newArrayList(dataFile1, dataFile2))
1223             .build();
1224 
1225     DataFileGroupInternal pendingFileGroup2 =
1226         DataFileGroupInternal.newBuilder()
1227             .setGroupName(TEST_GROUP_2)
1228             .setFileGroupVersionNumber(1)
1229             .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS)
1230             .addAllFile(Lists.newArrayList(dataFile1, dataFile2))
1231             .build();
1232 
1233     GroupKey groupKey =
1234         GroupKey.newBuilder()
1235             .setGroupName(TEST_GROUP)
1236             .setOwnerPackage(context.getPackageName())
1237             .build();
1238     GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build();
1239     GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build();
1240 
1241     GroupKey groupKey2 =
1242         GroupKey.newBuilder()
1243             .setGroupName(TEST_GROUP_2)
1244             .setOwnerPackage(context.getPackageName())
1245             .build();
1246     GroupKey pendingGroupKey2 = groupKey2.toBuilder().setDownloaded(false).build();
1247 
1248     writePendingFileGroup(pendingGroupKey, pendingFileGroup);
1249     writePendingFileGroup(pendingGroupKey2, pendingFileGroup2);
1250     writeSharedFiles(
1251         sharedFilesMetadata,
1252         pendingFileGroup,
1253         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS));
1254     writeSharedFiles(
1255         sharedFilesMetadata,
1256         pendingFileGroup2,
1257         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS));
1258     assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1259         .containsExactly(
1260             GroupKeyAndGroup.create(pendingGroupKey, pendingFileGroup),
1261             GroupKeyAndGroup.create(pendingGroupKey2, pendingFileGroup2));
1262 
1263     fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get();
1264 
1265     assertThat(readPendingFileGroup(pendingGroupKey)).isNull();
1266     assertThat(readPendingFileGroup(pendingGroupKey2)).isNotNull();
1267     assertThat(readPendingFileGroup(downloadedGroupKey)).isNull();
1268     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
1269     assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1270         .containsExactly(GroupKeyAndGroup.create(pendingGroupKey2, pendingFileGroup2));
1271 
1272     verify(mockDownloader, never()).stopDownloading(any(String.class), any(Uri.class));
1273 
1274     verifyNoInteractions(mockLogger);
1275   }
1276 
1277   @Test
removeFileGroup_onFailure()1278   public void removeFileGroup_onFailure() throws Exception {
1279     // Mock FileGroupsMetadata to test failure scenario.
1280     resetFileGroupManager(mockFileGroupsMetadata, sharedFileManager);
1281     DataFileGroupInternal pendingFileGroup =
1282         DataFileGroupInternal.newBuilder()
1283             .setGroupName(TEST_GROUP)
1284             .setFileGroupVersionNumber(1)
1285             .build();
1286     DataFileGroupInternal downloadedFileGroup =
1287         DataFileGroupInternal.newBuilder()
1288             .setGroupName(TEST_GROUP)
1289             .setFileGroupVersionNumber(0)
1290             .setBuildId(0)
1291             .setVariantId("")
1292             .build();
1293 
1294     GroupKey groupKey =
1295         GroupKey.newBuilder()
1296             .setGroupName(TEST_GROUP)
1297             .setOwnerPackage(context.getPackageName())
1298             .build();
1299     GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build();
1300     GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build();
1301 
1302     when(mockFileGroupsMetadata.read(pendingGroupKey))
1303         .thenReturn(Futures.immediateFuture(pendingFileGroup));
1304     when(mockFileGroupsMetadata.read(downloadedGroupKey))
1305         .thenReturn(Futures.immediateFuture(downloadedFileGroup));
1306     when(mockFileGroupsMetadata.remove(pendingGroupKey)).thenReturn(Futures.immediateFuture(true));
1307     when(mockFileGroupsMetadata.remove(downloadedGroupKey))
1308         .thenReturn(Futures.immediateFuture(false));
1309 
1310     // Exception should be thrown when fileGroupManager attempts to remove downloadedGroupKey.
1311     ExecutionException expected =
1312         assertThrows(
1313             ExecutionException.class,
1314             () -> fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get());
1315     assertThat(expected).hasCauseThat().isInstanceOf(IOException.class);
1316 
1317     verify(mockFileGroupsMetadata).remove(pendingGroupKey);
1318     verify(mockFileGroupsMetadata).remove(downloadedGroupKey);
1319     verify(mockFileGroupsMetadata, never()).addStaleGroup(any(DataFileGroupInternal.class));
1320 
1321     verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
1322   }
1323 
1324   @Test
removeFileGroup_removesSideloadedGroup()1325   public void removeFileGroup_removesSideloadedGroup() throws Exception {
1326     // Create sideloaded group
1327     DataFileGroupInternal sideloadedGroup =
1328         DataFileGroupInternal.newBuilder()
1329             .setGroupName(TEST_GROUP)
1330             .addFile(
1331                 DataFile.newBuilder()
1332                     .setFileId("sideloaded_file")
1333                     .setUrlToDownload("file:/test")
1334                     .setChecksumType(DataFile.ChecksumType.NONE)
1335                     .build())
1336             .build();
1337 
1338     writePendingFileGroup(testKey, sideloadedGroup);
1339     writeDownloadedFileGroup(testKey, sideloadedGroup);
1340 
1341     fileGroupManager.removeFileGroup(testKey, /* pendingOnly= */ false).get();
1342 
1343     assertThat(readPendingFileGroup(testKey)).isNull();
1344     assertThat(readDownloadedFileGroup(testKey)).isNull();
1345   }
1346 
1347   @Test
1348   public void
removeFileGroup_whenMultipleVariantsExist_whenNoVariantSpecified_removesEmptyVariantGroup()1349       removeFileGroup_whenMultipleVariantsExist_whenNoVariantSpecified_removesEmptyVariantGroup()
1350           throws Exception {
1351     // Create 3 variants of a group (default (no variant), en, fr) and have them all added. When
1352     // removeFileGroups is called and the group key given does not include a variant, ensure that
1353     // the default group is removed.
1354 
1355     GroupKey defaultGroupKey =
1356         GroupKey.newBuilder()
1357             .setGroupName(TEST_GROUP)
1358             .setOwnerPackage(context.getPackageName())
1359             .build();
1360     GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build();
1361     GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build();
1362 
1363     DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1364     DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build();
1365     DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build();
1366 
1367     writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup);
1368     writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup);
1369     writePendingFileGroup(getPendingKey(frGroupKey), frFileGroup);
1370 
1371     writeSharedFiles(
1372         sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1373     writeSharedFiles(
1374         sharedFilesMetadata, enFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1375     writeSharedFiles(
1376         sharedFilesMetadata, frFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1377 
1378     // Assert that all file groups share the same file even through the variants are different
1379     assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
1380 
1381     {
1382       // Perfrom removal once and check that the default group gets removed
1383       fileGroupManager.removeFileGroup(defaultGroupKey, /* pendingOnly= */ false).get();
1384 
1385       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
1386           .comparingElementsUsing(GROUP_KEY_TO_VARIANT)
1387           .containsExactly("en", "fr");
1388       assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1389           .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT)
1390           .containsExactly("en", "fr");
1391 
1392       assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
1393     }
1394 
1395     {
1396       // Perform remove again and verify that there is no change in state
1397       fileGroupManager.removeFileGroup(defaultGroupKey, /* pendingOnly= */ false).get();
1398 
1399       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
1400           .comparingElementsUsing(GROUP_KEY_TO_VARIANT)
1401           .containsExactly("en", "fr");
1402       assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1403           .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT)
1404           .containsExactly("en", "fr");
1405 
1406       assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
1407     }
1408   }
1409 
1410   @Test
removeFileGroup_whenMultipleVariantsExist_whenVariantSpecified_removesVariantGroup()1411   public void removeFileGroup_whenMultipleVariantsExist_whenVariantSpecified_removesVariantGroup()
1412       throws Exception {
1413     // Create 3 variants of a group (default (no variant), en, fr) and have them all added. When
1414     // removeFileGroups is called and the group key given includes a variant, ensure that only
1415     // the group with that variant is removed.
1416 
1417     GroupKey defaultGroupKey =
1418         GroupKey.newBuilder()
1419             .setGroupName(TEST_GROUP)
1420             .setOwnerPackage(context.getPackageName())
1421             .build();
1422     GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build();
1423     GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build();
1424 
1425     DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1426     DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build();
1427     DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build();
1428 
1429     writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup);
1430     writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup);
1431     writePendingFileGroup(getPendingKey(frGroupKey), frFileGroup);
1432 
1433     writeSharedFiles(
1434         sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1435     writeSharedFiles(
1436         sharedFilesMetadata, enFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1437     writeSharedFiles(
1438         sharedFilesMetadata, frFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1439 
1440     // Assert that all file groups share the same file even through the variants are different
1441     assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
1442 
1443     {
1444       // Perfrom removal once and check that the en group gets removed
1445       fileGroupManager.removeFileGroup(enGroupKey, /* pendingOnly= */ false).get();
1446 
1447       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
1448           .comparingElementsUsing(GROUP_KEY_TO_VARIANT)
1449           .containsExactly("", "fr");
1450       assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1451           .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT)
1452           .containsExactly("", "fr");
1453 
1454       assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
1455     }
1456 
1457     {
1458       // Perform remove again and verify that there is no change in state
1459       fileGroupManager.removeFileGroup(enGroupKey, /* pendingOnly= */ false).get();
1460 
1461       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
1462           .comparingElementsUsing(GROUP_KEY_TO_VARIANT)
1463           .containsExactly("", "fr");
1464       assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1465           .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT)
1466           .containsExactly("", "fr");
1467 
1468       assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
1469     }
1470   }
1471 
1472   @Test
testRemoveFileGroups_whenNoGroupsExist_performsNoRemovals()1473   public void testRemoveFileGroups_whenNoGroupsExist_performsNoRemovals() throws Exception {
1474     GroupKey groupKey1 =
1475         GroupKey.newBuilder()
1476             .setGroupName(TEST_GROUP)
1477             .setOwnerPackage(context.getPackageName())
1478             .build();
1479     GroupKey groupKey2 =
1480         GroupKey.newBuilder()
1481             .setGroupName(TEST_GROUP_2)
1482             .setOwnerPackage(context.getPackageName())
1483             .build();
1484 
1485     fileGroupManager.removeFileGroups(ImmutableList.of(groupKey1, groupKey2)).get();
1486 
1487     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
1488     assertThat(fileGroupsMetadata.getAllGroupKeys().get()).isEmpty();
1489   }
1490 
1491   @Test
testRemoveFileGroups_whenNoMatchingKeysExist_performsNoRemovals()1492   public void testRemoveFileGroups_whenNoMatchingKeysExist_performsNoRemovals() throws Exception {
1493     // Create a pending and downloaded version of a file group
1494     // Pending group includes 2 files: 1 that is shared with downloaded group and one that will be
1495     // marked pending
1496     DataFileGroupInternal pendingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
1497     DataFileGroupInternal downloadedFileGroup =
1498         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1499 
1500     GroupKey.Builder groupKeyBuilder =
1501         GroupKey.newBuilder().setGroupName(TEST_GROUP).setOwnerPackage(context.getPackageName());
1502     GroupKey pendingGroupKey = groupKeyBuilder.setDownloaded(false).build();
1503     GroupKey downloadedGroupKey = groupKeyBuilder.setDownloaded(true).build();
1504     GroupKey nonMatchingGroupKey1 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build();
1505     GroupKey nonMatchingGroupKey2 = groupKeyBuilder.setGroupName(TEST_GROUP_3).build();
1506 
1507     // Write file group and shared file metadata
1508     // NOTE: pending group contains all files in downloaded group, so we only need to write shared
1509     // file state once.
1510     writePendingFileGroup(pendingGroupKey, pendingFileGroup);
1511     writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup);
1512     writeSharedFiles(
1513         sharedFilesMetadata,
1514         pendingFileGroup,
1515         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS));
1516 
1517     fileGroupManager
1518         .removeFileGroups(ImmutableList.of(nonMatchingGroupKey1, nonMatchingGroupKey2))
1519         .get();
1520 
1521     assertThat(readPendingFileGroup(pendingGroupKey)).isNotNull();
1522     assertThat(readDownloadedFileGroup(downloadedGroupKey)).isNotNull();
1523     assertThat(fileGroupsMetadata.getAllFreshGroups().get()).hasSize(2);
1524     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
1525 
1526     verify(mockDownloader, times(0)).stopDownloading(any(), any());
1527   }
1528 
1529   @Test
testRemoveFileGroups_whenMatchingPendingGroups_performsRemove()1530   public void testRemoveFileGroups_whenMatchingPendingGroups_performsRemove() throws Exception {
1531     // Create 2 pending groups that will be removed, 1 pending group that shouldn't be removed, and
1532     // 1 downloaded group that shouldn't be removed
1533     DataFileGroupInternal pendingGroupToRemove1 =
1534         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder().build();
1535     DataFileGroupInternal pendingGroupToRemove2 =
1536         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
1537     DataFileGroupInternal pendingGroupToKeep =
1538         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 1).toBuilder().build();
1539     DataFileGroupInternal downloadedGroupToKeep =
1540         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_4, 1);
1541 
1542     GroupKey.Builder groupKeyBuilder =
1543         GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(false);
1544     GroupKey pendingGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP).build();
1545     GroupKey pendingGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build();
1546     GroupKey pendingGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP_3).build();
1547     GroupKey downloadedGroupKeyToKeep =
1548         groupKeyBuilder.setGroupName(TEST_GROUP_4).setDownloaded(true).build();
1549 
1550     writePendingFileGroup(pendingGroupKeyToRemove1, pendingGroupToRemove1);
1551     writePendingFileGroup(pendingGroupKeyToRemove2, pendingGroupToRemove2);
1552     writePendingFileGroup(pendingGroupKeyToKeep, pendingGroupToKeep);
1553     writeDownloadedFileGroup(downloadedGroupKeyToKeep, downloadedGroupToKeep);
1554 
1555     writeSharedFiles(
1556         sharedFilesMetadata,
1557         pendingGroupToRemove1,
1558         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1559     writeSharedFiles(
1560         sharedFilesMetadata,
1561         pendingGroupToRemove2,
1562         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1563     writeSharedFiles(
1564         sharedFilesMetadata, pendingGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1565     writeSharedFiles(
1566         sharedFilesMetadata, downloadedGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1567 
1568     fileGroupManager
1569         .removeFileGroups(ImmutableList.of(pendingGroupKeyToRemove1, pendingGroupKeyToRemove2))
1570         .get();
1571 
1572     // Construct Pending File Uris to check which downloads were stopped
1573     NewFileKey pendingFileKey1 =
1574         MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove1)[0];
1575     NewFileKey pendingFileKey2 =
1576         MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove2)[0];
1577     NewFileKey pendingFileKey3 =
1578         MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToKeep)[0];
1579     Uri pendingFileUri1 =
1580         DirectoryUtil.getOnDeviceUri(
1581             context,
1582             pendingFileKey1.getAllowedReaders(),
1583             pendingGroupToRemove1.getFile(0).getFileId(),
1584             pendingFileKey1.getChecksum(),
1585             mockSilentFeedback,
1586             /* instanceId= */ Optional.absent(),
1587             /* androidShared= */ false);
1588     Uri pendingFileUri2 =
1589         DirectoryUtil.getOnDeviceUri(
1590             context,
1591             pendingFileKey2.getAllowedReaders(),
1592             pendingGroupToRemove2.getFile(0).getFileId(),
1593             pendingFileKey2.getChecksum(),
1594             mockSilentFeedback,
1595             /* instanceId= */ Optional.absent(),
1596             /* androidShared= */ false);
1597     Uri pendingFileUri3 =
1598         DirectoryUtil.getOnDeviceUri(
1599             context,
1600             pendingFileKey3.getAllowedReaders(),
1601             pendingGroupToKeep.getFile(0).getFileId(),
1602             pendingFileKey3.getChecksum(),
1603             mockSilentFeedback,
1604             /* instanceId= */ Optional.absent(),
1605             /* androidShared= */ false);
1606 
1607     // Assert that matching pending groups are removed
1608     assertThat(readPendingFileGroup(pendingGroupKeyToRemove1)).isNull();
1609     assertThat(readPendingFileGroup(pendingGroupKeyToRemove2)).isNull();
1610     assertThat(readPendingFileGroup(pendingGroupKeyToKeep)).isNotNull();
1611     assertThat(readDownloadedFileGroup(downloadedGroupKeyToKeep)).isNotNull();
1612     assertThat(fileGroupsMetadata.getAllFreshGroups().get()).hasSize(2);
1613     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
1614 
1615     verify(mockDownloader).stopDownloading(pendingFileKey1.getChecksum(), pendingFileUri1);
1616     verify(mockDownloader).stopDownloading(pendingFileKey2.getChecksum(), pendingFileUri2);
1617     verify(mockDownloader, times(0))
1618         .stopDownloading(pendingFileKey3.getChecksum(), pendingFileUri3);
1619   }
1620 
1621   @Test
testRemoveFileGroups_whenMatchingDownloadedGroups_performsRemove()1622   public void testRemoveFileGroups_whenMatchingDownloadedGroups_performsRemove() throws Exception {
1623     // Create 2 downloaded groups that will be removed, 1 downloaded group that shouldn't be
1624     // removed, and 1 pending group that shouldn't be removed
1625     DataFileGroupInternal downloadedGroupToRemove1 =
1626         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1627     DataFileGroupInternal downloadedGroupToRemove2 =
1628         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
1629     DataFileGroupInternal downloadedGroupToKeep =
1630         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 1);
1631     DataFileGroupInternal pendingGroupToKeep =
1632         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_4, 1).toBuilder().build();
1633 
1634     GroupKey.Builder groupKeyBuilder =
1635         GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(true);
1636     GroupKey downloadedGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP).build();
1637     GroupKey downloadedGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build();
1638     GroupKey downloadedGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP_3).build();
1639     GroupKey pendingGroupKeyToKeep =
1640         groupKeyBuilder.setGroupName(TEST_GROUP_4).setDownloaded(false).build();
1641 
1642     writeDownloadedFileGroup(downloadedGroupKeyToRemove1, downloadedGroupToRemove1);
1643     writeDownloadedFileGroup(downloadedGroupKeyToRemove2, downloadedGroupToRemove2);
1644     writeDownloadedFileGroup(downloadedGroupKeyToKeep, downloadedGroupToKeep);
1645     writePendingFileGroup(pendingGroupKeyToKeep, pendingGroupToKeep);
1646 
1647     writeSharedFiles(
1648         sharedFilesMetadata,
1649         downloadedGroupToRemove1,
1650         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1651     writeSharedFiles(
1652         sharedFilesMetadata,
1653         downloadedGroupToRemove2,
1654         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1655     writeSharedFiles(
1656         sharedFilesMetadata, downloadedGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1657     writeSharedFiles(
1658         sharedFilesMetadata, pendingGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1659 
1660     fileGroupManager
1661         .removeFileGroups(
1662             ImmutableList.of(downloadedGroupKeyToRemove1, downloadedGroupKeyToRemove2))
1663         .get();
1664 
1665     // Construct Pending File Uri to check that it isn't cancelled
1666     NewFileKey pendingFileKey1 =
1667         MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToKeep)[0];
1668     Uri pendingFileUri1 =
1669         DirectoryUtil.getOnDeviceUri(
1670             context,
1671             pendingFileKey1.getAllowedReaders(),
1672             pendingGroupToKeep.getFile(0).getFileId(),
1673             pendingFileKey1.getChecksum(),
1674             mockSilentFeedback,
1675             /* instanceId= */ Optional.absent(),
1676             /* androidShared= */ false);
1677 
1678     // Assert that matching pending groups are removed
1679     assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove1)).isNull();
1680     assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove2)).isNull();
1681     assertThat(readDownloadedFileGroup(downloadedGroupKeyToKeep)).isNotNull();
1682     assertThat(readPendingFileGroup(pendingGroupKeyToKeep)).isNotNull();
1683 
1684     assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1685         .containsExactly(
1686             GroupKeyAndGroup.create(downloadedGroupKeyToKeep, downloadedGroupToKeep),
1687             GroupKeyAndGroup.create(pendingGroupKeyToKeep, pendingGroupToKeep));
1688     assertThat(fileGroupsMetadata.getAllStaleGroups().get())
1689         .containsExactly(
1690             downloadedGroupToRemove1.toBuilder()
1691                 .setBookkeeping(
1692                     downloadedGroupToRemove1.getBookkeeping().toBuilder()
1693                         .setStaleExpirationDate(1)
1694                         .build())
1695                 .build(),
1696             downloadedGroupToRemove2.toBuilder()
1697                 .setBookkeeping(
1698                     downloadedGroupToRemove2.getBookkeeping().toBuilder()
1699                         .setStaleExpirationDate(1)
1700                         .build())
1701                 .build());
1702 
1703     verify(mockDownloader, times(0))
1704         .stopDownloading(pendingFileKey1.getChecksum(), pendingFileUri1);
1705   }
1706 
1707   @Test
testRemoveFileGroups_whenMatchingBothVersions_performsRemove()1708   public void testRemoveFileGroups_whenMatchingBothVersions_performsRemove() throws Exception {
1709     // Create 2 file groups, each with 2 versions (downloaded and pending)
1710     DataFileGroupInternal downloadedGroupToRemove1 =
1711         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1712     DataFileGroupInternal downloadedGroupToRemove2 =
1713         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
1714     DataFileGroupInternal pendingGroupToRemove1 =
1715         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder().build();
1716     DataFileGroupInternal pendingGroupToRemove2 =
1717         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
1718 
1719     GroupKey.Builder groupKeyBuilder =
1720         GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(true);
1721     GroupKey downloadedGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP).build();
1722     GroupKey downloadedGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build();
1723     GroupKey pendingGroupKeyToRemove1 =
1724         groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build();
1725     GroupKey pendingGroupKeyToRemove2 =
1726         groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(false).build();
1727 
1728     writeDownloadedFileGroup(downloadedGroupKeyToRemove1, downloadedGroupToRemove1);
1729     writeDownloadedFileGroup(downloadedGroupKeyToRemove2, downloadedGroupToRemove2);
1730     writePendingFileGroup(pendingGroupKeyToRemove1, pendingGroupToRemove1);
1731     writePendingFileGroup(pendingGroupKeyToRemove2, pendingGroupToRemove2);
1732 
1733     writeSharedFiles(
1734         sharedFilesMetadata,
1735         downloadedGroupToRemove1,
1736         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1737     writeSharedFiles(
1738         sharedFilesMetadata,
1739         downloadedGroupToRemove2,
1740         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
1741     writeSharedFiles(
1742         sharedFilesMetadata,
1743         pendingGroupToRemove1,
1744         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1745     writeSharedFiles(
1746         sharedFilesMetadata,
1747         pendingGroupToRemove2,
1748         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
1749 
1750     // NOTE: the downloaded version of keys are used in this call, but this shouldn't be relevant;
1751     // both downloaded and pending versions of group keys should be checked for removal.
1752     fileGroupManager
1753         .removeFileGroups(
1754             ImmutableList.of(downloadedGroupKeyToRemove1, downloadedGroupKeyToRemove2))
1755         .get();
1756 
1757     // Construct Pending File Uri to check that its download was cancelled
1758     NewFileKey pendingFileKey1 =
1759         MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove1)[0];
1760     NewFileKey pendingFileKey2 =
1761         MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove2)[0];
1762     Uri pendingFileUri1 =
1763         DirectoryUtil.getOnDeviceUri(
1764             context,
1765             pendingFileKey1.getAllowedReaders(),
1766             pendingGroupToRemove1.getFile(0).getFileId(),
1767             pendingFileKey1.getChecksum(),
1768             mockSilentFeedback,
1769             /* instanceId= */ Optional.absent(),
1770             /* androidShared= */ false);
1771     Uri pendingFileUri2 =
1772         DirectoryUtil.getOnDeviceUri(
1773             context,
1774             pendingFileKey2.getAllowedReaders(),
1775             pendingGroupToRemove2.getFile(0).getFileId(),
1776             pendingFileKey2.getChecksum(),
1777             mockSilentFeedback,
1778             /* instanceId= */ Optional.absent(),
1779             /* androidShared= */ false);
1780 
1781     // Assert that matching pending groups are removed
1782     assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove1)).isNull();
1783     assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove2)).isNull();
1784     assertThat(readPendingFileGroup(pendingGroupKeyToRemove1)).isNull();
1785     assertThat(readPendingFileGroup(pendingGroupKeyToRemove2)).isNull();
1786 
1787     assertThat(fileGroupsMetadata.getAllFreshGroups().get()).isEmpty();
1788     assertThat(fileGroupsMetadata.getAllStaleGroups().get())
1789         .containsExactly(
1790             downloadedGroupToRemove1.toBuilder()
1791                 .setBookkeeping(
1792                     downloadedGroupToRemove1.getBookkeeping().toBuilder()
1793                         .setStaleExpirationDate(1)
1794                         .build())
1795                 .build(),
1796             downloadedGroupToRemove2.toBuilder()
1797                 .setBookkeeping(
1798                     downloadedGroupToRemove2.getBookkeeping().toBuilder()
1799                         .setStaleExpirationDate(1)
1800                         .build())
1801                 .build());
1802 
1803     verify(mockDownloader, times(1))
1804         .stopDownloading(pendingFileKey1.getChecksum(), pendingFileUri1);
1805     verify(mockDownloader, times(1))
1806         .stopDownloading(pendingFileKey2.getChecksum(), pendingFileUri2);
1807   }
1808 
1809   @Test
testRemoveFileGroups_whenFilesAreReferencedByOtherGroups_doesNotCancelDownloads()1810   public void testRemoveFileGroups_whenFilesAreReferencedByOtherGroups_doesNotCancelDownloads()
1811       throws Exception {
1812     // Setup 2 pending groups to remove that each contain a file referenced by a 3rd pending group
1813     // that doesn't get removed. The pending file downloads referenced by the 3rd group should not
1814     // be cancelled.
1815     DataFileGroupInternal pendingGroupToKeep =
1816         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
1817     DataFileGroupInternal pendingGroupToRemove1 =
1818         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1).toBuilder()
1819             .addFile(pendingGroupToKeep.getFile(0))
1820             .build();
1821     DataFileGroupInternal pendingGroupToRemove2 =
1822         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 1).toBuilder()
1823             .addFile(pendingGroupToKeep.getFile(1))
1824             .build();
1825 
1826     GroupKey.Builder groupKeyBuilder =
1827         GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(false);
1828     GroupKey pendingGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP).build();
1829     GroupKey pendingGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build();
1830     GroupKey pendingGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_3).build();
1831 
1832     writePendingFileGroup(pendingGroupKeyToKeep, pendingGroupToKeep);
1833     writePendingFileGroup(pendingGroupKeyToRemove1, pendingGroupToRemove1);
1834     writePendingFileGroup(pendingGroupKeyToRemove2, pendingGroupToRemove2);
1835 
1836     writeSharedFiles(
1837         sharedFilesMetadata,
1838         pendingGroupToKeep,
1839         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS));
1840     writeSharedFiles(
1841         sharedFilesMetadata,
1842         pendingGroupToRemove1,
1843         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS));
1844     writeSharedFiles(
1845         sharedFilesMetadata,
1846         pendingGroupToRemove2,
1847         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS));
1848 
1849     fileGroupManager
1850         .removeFileGroups(ImmutableList.of(pendingGroupKeyToRemove1, pendingGroupKeyToRemove2))
1851         .get();
1852 
1853     assertThat(readPendingFileGroup(pendingGroupKeyToRemove1)).isNull();
1854     assertThat(readPendingFileGroup(pendingGroupKeyToRemove2)).isNull();
1855     assertThat(readPendingFileGroup(pendingGroupKeyToKeep)).isNotNull();
1856     assertThat(fileGroupsMetadata.getAllFreshGroups().get())
1857         .containsExactly(GroupKeyAndGroup.create(pendingGroupKeyToKeep, pendingGroupToKeep));
1858 
1859     // Get On Device Uris to check if file downloads were cancelled
1860     List<Uri> uncancelledFileUris = getOnDeviceUrisForFileGroup(pendingGroupToKeep);
1861     verify(mockDownloader, times(0)).stopDownloading(any(), eq(uncancelledFileUris.get(0)));
1862     verify(mockDownloader, times(0)).stopDownloading(any(), eq(uncancelledFileUris.get(1)));
1863 
1864     verify(mockDownloader, times(1))
1865         .stopDownloading(
1866             pendingGroupToRemove1.getFile(0).getChecksum(),
1867             getOnDeviceUrisForFileGroup(pendingGroupToRemove1).get(0));
1868     verify(mockDownloader, times(1))
1869         .stopDownloading(
1870             pendingGroupToRemove2.getFile(0).getChecksum(),
1871             getOnDeviceUrisForFileGroup(pendingGroupToRemove2).get(0));
1872   }
1873 
1874   @Test
testRemoveFileGroups_whenRemovePendingGroupFails_doesNotContinue()1875   public void testRemoveFileGroups_whenRemovePendingGroupFails_doesNotContinue() throws Exception {
1876     // Use Mocks to simulate failure scenario
1877     resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager);
1878 
1879     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1880 
1881     GroupKey.Builder groupKeyBuilder =
1882         GroupKey.newBuilder().setOwnerPackage(context.getPackageName());
1883     GroupKey pendingGroupKey =
1884         groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build();
1885     GroupKey downloadedGroupKey =
1886         groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build();
1887 
1888     when(mockFileGroupsMetadata.read(pendingGroupKey))
1889         .thenReturn(Futures.immediateFuture(pendingGroup));
1890     when(mockFileGroupsMetadata.read(getPendingKey(downloadedGroupKey)))
1891         .thenReturn(Futures.immediateFuture(null));
1892     when(mockFileGroupsMetadata.removeAllGroupsWithKeys(groupKeysCaptor.capture()))
1893         .thenReturn(Futures.immediateFuture(false));
1894 
1895     ExecutionException ex =
1896         assertThrows(
1897             ExecutionException.class,
1898             () ->
1899                 fileGroupManager
1900                     .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey))
1901                     .get());
1902     assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);
1903 
1904     verify(mockFileGroupsMetadata, times(0)).addStaleGroup(any());
1905     verify(mockSharedFileManager, times(0)).cancelDownload(any());
1906     verify(mockLogger, times(1)).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
1907     verify(mockFileGroupsMetadata, times(1)).removeAllGroupsWithKeys(any());
1908     List<GroupKey> attemptedRemoveKeys = groupKeysCaptor.getValue();
1909     assertThat(attemptedRemoveKeys).containsExactly(pendingGroupKey);
1910   }
1911 
1912   @Test
testRemoveFileGroups_whenRemoveDownloadedGroupFails_doesNotContinue()1913   public void testRemoveFileGroups_whenRemoveDownloadedGroupFails_doesNotContinue()
1914       throws Exception {
1915     // Use Mock FileGroupsMetadata to simulate failure scenario
1916     resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager);
1917 
1918     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1919     DataFileGroupInternal downloadedGroup =
1920         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
1921 
1922     GroupKey.Builder groupKeyBuilder =
1923         GroupKey.newBuilder().setOwnerPackage(context.getPackageName());
1924     GroupKey pendingGroupKey =
1925         groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build();
1926     GroupKey downloadedGroupKey =
1927         groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build();
1928 
1929     // Mock variations of group key reads
1930     when(mockFileGroupsMetadata.read(pendingGroupKey))
1931         .thenReturn(Futures.immediateFuture(pendingGroup));
1932     when(mockFileGroupsMetadata.read(getPendingKey(downloadedGroupKey)))
1933         .thenReturn(Futures.immediateFuture(null));
1934     when(mockFileGroupsMetadata.read(downloadedGroupKey))
1935         .thenReturn(Futures.immediateFuture(downloadedGroup));
1936     when(mockFileGroupsMetadata.read(getDownloadedKey(pendingGroupKey)))
1937         .thenReturn(Futures.immediateFuture(null));
1938 
1939     // Return true for pending groups removed, but false for downloaded groups
1940     when(mockFileGroupsMetadata.removeAllGroupsWithKeys(groupKeysCaptor.capture()))
1941         .thenReturn(Futures.immediateFuture(true))
1942         .thenReturn(Futures.immediateFuture(false));
1943 
1944     ExecutionException ex =
1945         assertThrows(
1946             ExecutionException.class,
1947             () ->
1948                 fileGroupManager
1949                     .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey))
1950                     .get());
1951     assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);
1952 
1953     verify(mockFileGroupsMetadata, times(0)).addStaleGroup(any());
1954     verify(mockSharedFileManager, times(0)).cancelDownload(any());
1955     verify(mockLogger, times(1)).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
1956     verify(mockFileGroupsMetadata, times(2)).removeAllGroupsWithKeys(any());
1957     List<List<GroupKey>> removeCallInvocations = groupKeysCaptor.getAllValues();
1958     assertThat(removeCallInvocations.get(0)).containsExactly(pendingGroupKey);
1959     assertThat(removeCallInvocations.get(1)).containsExactly(downloadedGroupKey);
1960   }
1961 
1962   @Test
testRemoveFileGroups_whenAddingStaleGroupFails_doesNotContinue()1963   public void testRemoveFileGroups_whenAddingStaleGroupFails_doesNotContinue() throws Exception {
1964     // Use Mock FileGroupsMetadata to simulate failure scenario
1965     resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager);
1966 
1967     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
1968     DataFileGroupInternal downloadedGroup =
1969         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
1970 
1971     GroupKey.Builder groupKeyBuilder =
1972         GroupKey.newBuilder().setOwnerPackage(context.getPackageName());
1973     GroupKey pendingGroupKey =
1974         groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build();
1975     GroupKey downloadedGroupKey =
1976         groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build();
1977 
1978     // Mock read group key variations
1979     when(mockFileGroupsMetadata.read(pendingGroupKey))
1980         .thenReturn(Futures.immediateFuture(pendingGroup));
1981     when(mockFileGroupsMetadata.read(getPendingKey(downloadedGroupKey)))
1982         .thenReturn(Futures.immediateFuture(null));
1983     when(mockFileGroupsMetadata.read(downloadedGroupKey))
1984         .thenReturn(Futures.immediateFuture(downloadedGroup));
1985     when(mockFileGroupsMetadata.read(getDownloadedKey(pendingGroupKey)))
1986         .thenReturn(Futures.immediateFuture(null));
1987 
1988     // Always return true for remove calls
1989     when(mockFileGroupsMetadata.removeAllGroupsWithKeys(groupKeysCaptor.capture()))
1990         .thenReturn(Futures.immediateFuture(true));
1991 
1992     // Fail when attempting to add a stale group
1993     when(mockFileGroupsMetadata.addStaleGroup(downloadedGroup))
1994         .thenReturn(Futures.immediateFuture(false));
1995 
1996     ExecutionException ex =
1997         assertThrows(
1998             ExecutionException.class,
1999             () ->
2000                 fileGroupManager
2001                     .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey))
2002                     .get());
2003     assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class);
2004 
2005     verify(mockFileGroupsMetadata, times(1)).addStaleGroup(downloadedGroup);
2006     verify(mockSharedFileManager, times(0)).cancelDownload(any());
2007     verify(mockLogger, times(1)).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
2008     verify(mockFileGroupsMetadata, times(2)).removeAllGroupsWithKeys(any());
2009     List<List<GroupKey>> removeCallInvocations = groupKeysCaptor.getAllValues();
2010     assertThat(removeCallInvocations.get(0)).containsExactly(pendingGroupKey);
2011     assertThat(removeCallInvocations.get(1)).containsExactly(downloadedGroupKey);
2012   }
2013 
2014   @Test
testRemoveFileGroups_whenCancellingPendingDownloadFails_doesNotContinue()2015   public void testRemoveFileGroups_whenCancellingPendingDownloadFails_doesNotContinue()
2016       throws Exception {
2017     // Use Mock FileGroupsMetadata to simulate failure scenario
2018     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
2019 
2020     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2021     DataFileGroupInternal downloadedGroup =
2022         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
2023 
2024     GroupKey.Builder groupKeyBuilder =
2025         GroupKey.newBuilder().setOwnerPackage(context.getPackageName());
2026     GroupKey pendingGroupKey =
2027         groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build();
2028     GroupKey downloadedGroupKey =
2029         groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build();
2030 
2031     writePendingFileGroup(pendingGroupKey, pendingGroup);
2032     writeDownloadedFileGroup(downloadedGroupKey, downloadedGroup);
2033     writeSharedFiles(
2034         sharedFilesMetadata, pendingGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2035     writeSharedFiles(
2036         sharedFilesMetadata, downloadedGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
2037 
2038     // Fail when cancelling download
2039     NewFileKey[] pendingFileKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroup);
2040     when(mockSharedFileManager.cancelDownload(pendingFileKeys[0]))
2041         .thenReturn(Futures.immediateFailedFuture(new Exception("Test failure")));
2042 
2043     ExecutionException ex =
2044         assertThrows(
2045             ExecutionException.class,
2046             () ->
2047                 fileGroupManager
2048                     .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey))
2049                     .get());
2050     assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class);
2051 
2052     assertThat(readPendingFileGroup(pendingGroupKey)).isNull();
2053     assertThat(readDownloadedFileGroup(downloadedGroupKey)).isNull();
2054     assertThat(fileGroupsMetadata.getAllFreshGroups().get()).isEmpty();
2055     assertThat(fileGroupsMetadata.getAllStaleGroups().get())
2056         .containsExactly(
2057             downloadedGroup.toBuilder()
2058                 .setBookkeeping(
2059                     downloadedGroup.getBookkeeping().toBuilder().setStaleExpirationDate(1).build())
2060                 .build());
2061   }
2062 
2063   @Test
testRemoveFileGroups_whenMultipleVariantsExists_removesVariantsSpecified()2064   public void testRemoveFileGroups_whenMultipleVariantsExists_removesVariantsSpecified()
2065       throws Exception {
2066     // Create multiple variants of a group (default (empty), en, fr) and remove the default (empty)
2067     // variant and en keys. Ensure that only the fr group remains.
2068     GroupKey defaultGroupKey =
2069         GroupKey.newBuilder()
2070             .setGroupName(TEST_GROUP)
2071             .setOwnerPackage(context.getPackageName())
2072             .build();
2073     GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build();
2074     GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build();
2075 
2076     DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2077     DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build();
2078     DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build();
2079 
2080     writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup);
2081     writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup);
2082     writePendingFileGroup(getPendingKey(frGroupKey), frFileGroup);
2083 
2084     writeSharedFiles(
2085         sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2086     writeSharedFiles(
2087         sharedFilesMetadata, enFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2088     writeSharedFiles(
2089         sharedFilesMetadata, frFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2090 
2091     // Assert that all file groups share the same file even through the variants are different
2092     assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
2093 
2094     {
2095       // Perfrom removal once and check that the correct groups get removed
2096       fileGroupManager.removeFileGroups(ImmutableList.of(defaultGroupKey, enGroupKey)).get();
2097 
2098       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
2099           .comparingElementsUsing(GROUP_KEY_TO_VARIANT)
2100           .containsExactly("fr");
2101       assertThat(fileGroupsMetadata.getAllFreshGroups().get())
2102           .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT)
2103           .containsExactly("fr");
2104 
2105       assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
2106     }
2107 
2108     {
2109       // Perform remove again and verify that there is no change in state
2110       fileGroupManager.removeFileGroups(ImmutableList.of(defaultGroupKey, enGroupKey)).get();
2111 
2112       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
2113           .comparingElementsUsing(GROUP_KEY_TO_VARIANT)
2114           .containsExactly("fr");
2115       assertThat(fileGroupsMetadata.getAllFreshGroups().get())
2116           .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT)
2117           .containsExactly("fr");
2118 
2119       assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1);
2120     }
2121   }
2122 
2123   @Test
testGetDownloadedGroup()2124   public void testGetDownloadedGroup() throws Exception {
2125     assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull();
2126 
2127     DataFileGroupInternal dataFileGroup =
2128         MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP, 2);
2129     writeDownloadedFileGroup(testKey, dataFileGroup);
2130 
2131     DataFileGroupInternal downloadedGroup = fileGroupManager.getFileGroup(testKey, true).get();
2132     MddTestUtil.assertMessageEquals(dataFileGroup, downloadedGroup);
2133   }
2134 
2135   @Test
testGetDownloadedGroup_whenMultipleVariantsExists_getsCorrectGroup()2136   public void testGetDownloadedGroup_whenMultipleVariantsExists_getsCorrectGroup()
2137       throws Exception {
2138     GroupKey defaultGroupKey =
2139         GroupKey.newBuilder()
2140             .setGroupName(TEST_GROUP)
2141             .setOwnerPackage(context.getPackageName())
2142             .build();
2143     GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build();
2144 
2145     // Initially, assert that groups don't exist
2146     assertThat(fileGroupManager.getFileGroup(defaultGroupKey, true).get()).isNull();
2147     assertThat(fileGroupManager.getFileGroup(enGroupKey, true).get()).isNull();
2148 
2149     // Create groups and write them
2150     DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2151     DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build();
2152 
2153     writeDownloadedFileGroup(getDownloadedKey(defaultGroupKey), defaultFileGroup);
2154     writeDownloadedFileGroup(getDownloadedKey(enGroupKey), enFileGroup);
2155 
2156     // Assert the correct group is returned for each key
2157     DataFileGroupInternal groupForDefaultKey =
2158         fileGroupManager.getFileGroup(defaultGroupKey, true).get();
2159     MddTestUtil.assertMessageEquals(defaultFileGroup, groupForDefaultKey);
2160 
2161     DataFileGroupInternal groupForEnKey = fileGroupManager.getFileGroup(enGroupKey, true).get();
2162     MddTestUtil.assertMessageEquals(enFileGroup, groupForEnKey);
2163   }
2164 
2165   @Test
testGetPendingGroup()2166   public void testGetPendingGroup() throws Exception {
2167     assertThat(fileGroupManager.getFileGroup(testKey, false).get()).isNull();
2168 
2169     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
2170     writePendingFileGroup(testKey, dataFileGroup);
2171 
2172     DataFileGroupInternal pendingGroup = fileGroupManager.getFileGroup(testKey, false).get();
2173     MddTestUtil.assertMessageEquals(dataFileGroup, pendingGroup);
2174   }
2175 
2176   @Test
testGetPendingGroup_whenMultipleVariantsExists_getsCorrectGroup()2177   public void testGetPendingGroup_whenMultipleVariantsExists_getsCorrectGroup() throws Exception {
2178     GroupKey defaultGroupKey =
2179         GroupKey.newBuilder()
2180             .setGroupName(TEST_GROUP)
2181             .setOwnerPackage(context.getPackageName())
2182             .build();
2183     GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build();
2184 
2185     // Initially, assert that groups don't exist
2186     assertThat(fileGroupManager.getFileGroup(defaultGroupKey, false).get()).isNull();
2187     assertThat(fileGroupManager.getFileGroup(enGroupKey, false).get()).isNull();
2188 
2189     // Create groups and write them
2190     DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2191     DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build();
2192 
2193     writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup);
2194     writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup);
2195 
2196     // Assert the correct group is returned for each key
2197     DataFileGroupInternal groupForDefaultKey =
2198         fileGroupManager.getFileGroup(defaultGroupKey, false).get();
2199     MddTestUtil.assertMessageEquals(defaultFileGroup, groupForDefaultKey);
2200 
2201     DataFileGroupInternal groupForEnKey = fileGroupManager.getFileGroup(enGroupKey, false).get();
2202     MddTestUtil.assertMessageEquals(enFileGroup, groupForEnKey);
2203   }
2204 
2205   @Test
testSetGroupActivation_deactivationRemovesGroupsRequiringActivation()2206   public void testSetGroupActivation_deactivationRemovesGroupsRequiringActivation()
2207       throws Exception {
2208     flags.enableDelayedDownload = Optional.of(true);
2209 
2210     // Create 2 groups, one of which requires device side activation.
2211     DataFileGroupInternal.Builder fileGroup1 =
2212         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder();
2213     DownloadConditions.Builder downloadConditions =
2214         DownloadConditions.newBuilder()
2215             .setActivatingCondition(ActivatingCondition.DEVICE_ACTIVATED);
2216     fileGroup1.setDownloadConditions(downloadConditions);
2217 
2218     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 3);
2219 
2220     // Activate both group keys and add groups to FileGroupManager.
2221     assertThat(fileGroupManager.setGroupActivation(testKey, true).get()).isTrue();
2222 
2223     assertThat(fileGroupManager.addGroupForDownload(testKey, fileGroup1.build()).get()).isTrue();
2224 
2225     assertThat(fileGroupManager.setGroupActivation(testKey2, true).get()).isTrue();
2226 
2227     assertThat(fileGroupManager.addGroupForDownload(testKey2, fileGroup2).get()).isTrue();
2228 
2229     // Add a downloaded version of the second group, that requires device side activation.
2230     DataFileGroupInternal downloadedfileGroup2 =
2231         MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP_2, 1);
2232     downloadConditions = DownloadConditions.newBuilder();
2233     downloadedfileGroup2 =
2234         downloadedfileGroup2.toBuilder()
2235             .setDownloadConditions(
2236                 downloadConditions.setActivatingCondition(ActivatingCondition.DEVICE_ACTIVATED))
2237             .build();
2238     writeDownloadedFileGroup(testKey2, downloadedfileGroup2);
2239 
2240     // Deactivate both group keys, and check that the groups that required activation are deleted.
2241     assertThat(fileGroupManager.setGroupActivation(testKey, false).get()).isTrue();
2242     // Setting group activation to false will only remove groups that have
2243     // ActivatingCondition.DEVICE_ACTIVATED. So the pending version will remain, while the
2244     // downloaded one is removed.
2245     assertThat(fileGroupManager.setGroupActivation(testKey2, false).get()).isTrue();
2246 
2247     assertThat(readPendingFileGroup(testKey)).isNull();
2248     assertThat(readPendingFileGroup(testKey2)).isNotNull();
2249     assertThat(readDownloadedFileGroup(testKey2)).isNull();
2250   }
2251 
2252   @Test
testImportFilesIntoFileGroup_whenExistingGroupDoesNotExist_fails()2253   public void testImportFilesIntoFileGroup_whenExistingGroupDoesNotExist_fails() throws Exception {
2254     DataFile inlineFile =
2255         DataFile.newBuilder()
2256             .setFileId("inline-file")
2257             .setChecksum("abc")
2258             .setUrlToDownload("inlinefile:sha1:abc")
2259             .build();
2260     ImmutableList<DataFile> updatedDataFileList = ImmutableList.of(inlineFile);
2261 
2262     GroupKey groupKey = GroupKey.newBuilder().setGroupName("non-existing-group").build();
2263 
2264     ExecutionException ex =
2265         assertThrows(
2266             ExecutionException.class,
2267             () ->
2268                 fileGroupManager
2269                     .importFilesIntoFileGroup(
2270                         groupKey,
2271                         /* buildId= */ 0,
2272                         /* variantId= */ "",
2273                         updatedDataFileList,
2274                         /* inlineFileMap= */ ImmutableMap.of(),
2275                         /* customPropertyOptional= */ Optional.absent(),
2276                         noCustomValidation())
2277                     .get());
2278 
2279     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2280     DownloadException dex = (DownloadException) ex.getCause();
2281     assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
2282   }
2283 
2284   @Test
testImportFilesIntoFileGroup_whenExistingPendingGroupDoesNotMatchIdentifiers_fails()2285   public void testImportFilesIntoFileGroup_whenExistingPendingGroupDoesNotMatchIdentifiers_fails()
2286       throws Exception {
2287     // Set up existing pending file group
2288     DataFileGroupInternal existingFileGroup =
2289         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
2290             .addFile(
2291                 DataFile.newBuilder()
2292                     .setFileId("inline-file")
2293                     .setChecksum("abc")
2294                     .setUrlToDownload("inlinefile:sha1:abc")
2295                     .build())
2296             .build();
2297     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2298 
2299     writePendingFileGroup(getPendingKey(groupKey), existingFileGroup);
2300     writeSharedFiles(
2301         sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2302 
2303     ExecutionException ex =
2304         assertThrows(
2305             ExecutionException.class,
2306             () ->
2307                 fileGroupManager
2308                     .importFilesIntoFileGroup(
2309                         groupKey,
2310                         /* buildId= */ 1,
2311                         /* variantId= */ "",
2312                         /* updatedDataFileList= */ ImmutableList.of(),
2313                         /* inlineFileMap= */ ImmutableMap.of(),
2314                         /* customPropertyOptional= */ Optional.absent(),
2315                         noCustomValidation())
2316                     .get());
2317 
2318     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2319     DownloadException dex = (DownloadException) ex.getCause();
2320     assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
2321   }
2322 
2323   @Test
2324   public void
testImportFilesIntoFileGroup_whenExistingDownloadedGroupDoesNotMatchIdentifiers_fails()2325       testImportFilesIntoFileGroup_whenExistingDownloadedGroupDoesNotMatchIdentifiers_fails()
2326           throws Exception {
2327     // Set up existing downloaded file group
2328     DataFileGroupInternal existingFileGroup =
2329         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
2330             .addFile(
2331                 DataFile.newBuilder()
2332                     .setFileId("inline-file")
2333                     .setChecksum("abc")
2334                     .setUrlToDownload("inlinefile:sha1:abc")
2335                     .build())
2336             .build();
2337     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2338 
2339     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingFileGroup);
2340     writeSharedFiles(
2341         sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
2342 
2343     ExecutionException ex =
2344         assertThrows(
2345             ExecutionException.class,
2346             () ->
2347                 fileGroupManager
2348                     .importFilesIntoFileGroup(
2349                         groupKey,
2350                         /* buildId= */ 0,
2351                         /* variantId= */ "testvariant",
2352                         /* updatedDataFileList= */ ImmutableList.of(),
2353                         /* inlineFileMap= */ ImmutableMap.of(),
2354                         /* customPropertyOptional= */ Optional.absent(),
2355                         noCustomValidation())
2356                     .get());
2357 
2358     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2359     DownloadException dex = (DownloadException) ex.getCause();
2360     assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
2361   }
2362 
2363   @Test
testImportFilesIntoFileGroup_whenBuildIdDoesNotMatch_fails()2364   public void testImportFilesIntoFileGroup_whenBuildIdDoesNotMatch_fails() throws Exception {
2365     // Set up existing pending/downloaded groups and check that they do not match due to build ID
2366     // differences.
2367 
2368     // Any can pack proto messages only, so use StringValue.
2369     Any customProperty =
2370         Any.parseFrom(
2371             StringValue.of("testCustomProperty").toByteString(),
2372             ExtensionRegistryLite.getEmptyRegistry());
2373     DataFileGroupInternal existingDownloadedFileGroup =
2374         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
2375             .setBuildId(1)
2376             .setVariantId("testvariant")
2377             .setCustomProperty(customProperty)
2378             .build();
2379     DataFileGroupInternal existingPendingFileGroup =
2380         existingDownloadedFileGroup.toBuilder().setBuildId(2).build();
2381 
2382     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2383 
2384     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
2385     writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup);
2386 
2387     ExecutionException ex =
2388         assertThrows(
2389             ExecutionException.class,
2390             () ->
2391                 fileGroupManager
2392                     .importFilesIntoFileGroup(
2393                         groupKey,
2394                         /* buildId= */ 3,
2395                         /* variantId= */ "testvariant",
2396                         /* updatedDataFileList= */ ImmutableList.of(),
2397                         /* inlineFileMap= */ ImmutableMap.of(),
2398                         /* customPropertyOptional= */ Optional.of(customProperty),
2399                         noCustomValidation())
2400                     .get());
2401 
2402     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2403     DownloadException dex = (DownloadException) ex.getCause();
2404     assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
2405   }
2406 
2407   @Test
testImportFilesIntoFileGroup_whenVariantIdDoesNotMatch_fails()2408   public void testImportFilesIntoFileGroup_whenVariantIdDoesNotMatch_fails() throws Exception {
2409     // Set up existing pending/downloaded groups and check that they do not match due to variant ID
2410     // differences.
2411 
2412     // Any can pack proto messages only, so use StringValue.
2413     Any customProperty =
2414         Any.parseFrom(
2415             StringValue.of("testCustomProperty").toByteString(),
2416             ExtensionRegistryLite.getEmptyRegistry());
2417     DataFileGroupInternal existingDownloadedFileGroup =
2418         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
2419             .setBuildId(1)
2420             .setVariantId("testvariant")
2421             .setCustomProperty(customProperty)
2422             .build();
2423     DataFileGroupInternal existingPendingFileGroup =
2424         existingDownloadedFileGroup.toBuilder().setVariantId("testvariant2").build();
2425 
2426     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2427 
2428     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
2429     writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup);
2430 
2431     ExecutionException ex =
2432         assertThrows(
2433             ExecutionException.class,
2434             () ->
2435                 fileGroupManager
2436                     .importFilesIntoFileGroup(
2437                         groupKey,
2438                         /* buildId= */ 1,
2439                         /* variantId= */ "testvariant3",
2440                         /* updatedDataFileList= */ ImmutableList.of(),
2441                         /* inlineFileMap= */ ImmutableMap.of(),
2442                         /* customPropertyOptional= */ Optional.of(customProperty),
2443                         noCustomValidation())
2444                     .get());
2445 
2446     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2447     DownloadException dex = (DownloadException) ex.getCause();
2448     assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
2449   }
2450 
2451   @Test
testImportFilesIntoFileGroup_whenCustomPropertyDoesNotMatch_whenDueToMismatch_fails()2452   public void testImportFilesIntoFileGroup_whenCustomPropertyDoesNotMatch_whenDueToMismatch_fails()
2453       throws Exception {
2454     // Set up existing pending/downloaded groups and check that they do not match due to custom
2455     // property differences.
2456 
2457     // Any can pack proto messages only, so use StringValue.
2458     Any downloadedCustomProperty =
2459         Any.parseFrom(
2460             StringValue.of("testDownloadedCustomProperty").toByteString(),
2461             ExtensionRegistryLite.getEmptyRegistry());
2462     Any pendingCustomProperty =
2463         Any.parseFrom(
2464             StringValue.of("testPendingCustomProperty").toByteString(),
2465             ExtensionRegistryLite.getEmptyRegistry());
2466     Any mismatchedCustomProperty =
2467         Any.parseFrom(
2468             StringValue.of("testMismatcheCustomProperty").toByteString(),
2469             ExtensionRegistryLite.getEmptyRegistry());
2470 
2471     DataFileGroupInternal existingDownloadedFileGroup =
2472         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
2473             .setBuildId(1)
2474             .setVariantId("testvariant")
2475             .setCustomProperty(downloadedCustomProperty)
2476             .build();
2477     DataFileGroupInternal existingPendingFileGroup =
2478         existingDownloadedFileGroup.toBuilder().setCustomProperty(pendingCustomProperty).build();
2479 
2480     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2481 
2482     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
2483     writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup);
2484 
2485     ExecutionException ex =
2486         assertThrows(
2487             ExecutionException.class,
2488             () ->
2489                 fileGroupManager
2490                     .importFilesIntoFileGroup(
2491                         groupKey,
2492                         /* buildId= */ 1,
2493                         /* variantId= */ "testvariant",
2494                         /* updatedDataFileList= */ ImmutableList.of(),
2495                         /* inlineFileMap= */ ImmutableMap.of(),
2496                         /* customPropertyOptional= */ Optional.of(mismatchedCustomProperty),
2497                         noCustomValidation())
2498                     .get());
2499 
2500     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2501     DownloadException dex = (DownloadException) ex.getCause();
2502     assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
2503   }
2504 
2505   @Test
2506   public void
testImportFilesIntoFileGroup_whenCustomPropertyDoesNotMatch_whenDueToBeingAbsent_fails()2507       testImportFilesIntoFileGroup_whenCustomPropertyDoesNotMatch_whenDueToBeingAbsent_fails()
2508           throws Exception {
2509     // Set up existing pending/downloaded groups and check that they do not match due to custom
2510     // property differences.
2511 
2512     // Any can pack proto messages only, so use StringValue.
2513     Any downloadedCustomProperty =
2514         Any.parseFrom(
2515             StringValue.of("testDownloadedCustomProperty").toByteString(),
2516             ExtensionRegistryLite.getEmptyRegistry());
2517     Any pendingCustomProperty =
2518         Any.parseFrom(
2519             StringValue.of("testPendingCustomProperty").toByteString(),
2520             ExtensionRegistryLite.getEmptyRegistry());
2521 
2522     DataFileGroupInternal existingDownloadedFileGroup =
2523         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
2524             .setBuildId(1)
2525             .setVariantId("testvariant")
2526             .setCustomProperty(downloadedCustomProperty)
2527             .build();
2528     DataFileGroupInternal existingPendingFileGroup =
2529         existingDownloadedFileGroup.toBuilder().setCustomProperty(pendingCustomProperty).build();
2530 
2531     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2532 
2533     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
2534     writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup);
2535 
2536     ExecutionException ex =
2537         assertThrows(
2538             ExecutionException.class,
2539             () ->
2540                 fileGroupManager
2541                     .importFilesIntoFileGroup(
2542                         groupKey,
2543                         /* buildId= */ 1,
2544                         /* variantId= */ "testvariant",
2545                         /* updatedDataFileList= */ ImmutableList.of(),
2546                         /* inlineFileMap= */ ImmutableMap.of(),
2547                         /* customPropertyOptional= */ Optional.absent(),
2548                         noCustomValidation())
2549                     .get());
2550 
2551     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2552     DownloadException dex = (DownloadException) ex.getCause();
2553     assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR);
2554   }
2555 
2556   @Test
testImportFilesIntoFileGroup_whenUnableToReserveNewFiles_fails()2557   public void testImportFilesIntoFileGroup_whenUnableToReserveNewFiles_fails() throws Exception {
2558     // Reset with mock SharedFileManager to force failures
2559     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
2560 
2561     // Create existing file group and a new file group that will add an inline file (which needs to
2562     // be reserved in SharedFileManager).
2563     DataFileGroupInternal existingFileGroup =
2564         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2565     ImmutableList<DataFile> updatedDataFileList =
2566         ImmutableList.of(
2567             DataFile.newBuilder()
2568                 .setFileId("inline-file")
2569                 .setChecksum("abc")
2570                 .setUrlToDownload("inlinefile:sha1:abc")
2571                 .build());
2572 
2573     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2574 
2575     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingFileGroup);
2576     writeSharedFiles(
2577         sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
2578 
2579     NewFileKey newInlineFileKey =
2580         SharedFilesMetadata.createKeyFromDataFile(
2581             updatedDataFileList.get(0), existingFileGroup.getAllowedReadersEnum());
2582     NewFileKey existingFileKey =
2583         SharedFilesMetadata.createKeyFromDataFile(
2584             existingFileGroup.getFile(0), existingFileGroup.getAllowedReadersEnum());
2585 
2586     when(mockSharedFileManager.reserveFileEntry(newInlineFileKey))
2587         .thenReturn(Futures.immediateFuture(false));
2588     when(mockSharedFileManager.reserveFileEntry(existingFileKey))
2589         .thenReturn(Futures.immediateFuture(true));
2590 
2591     ExecutionException ex =
2592         assertThrows(
2593             ExecutionException.class,
2594             () ->
2595                 fileGroupManager
2596                     .importFilesIntoFileGroup(
2597                         groupKey,
2598                         /* buildId= */ 0,
2599                         /* variantId= */ "",
2600                         /* updatedDataFileList= */ updatedDataFileList,
2601                         /* inlineFileMap= */ ImmutableMap.of(),
2602                         /* customPropertyOptional= */ Optional.absent(),
2603                         noCustomValidation())
2604                     .get());
2605     assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class);
2606     DownloadException dex = (DownloadException) ex.getCause();
2607     assertThat(dex.getDownloadResultCode())
2608         .isEqualTo(DownloadResultCode.UNABLE_TO_RESERVE_FILE_ENTRY);
2609   }
2610 
2611   @Test
2612   public void
testImportFilesIntoFileGroup_whenNoNewInlineFilesSpecifiedAndFilesDownloaded_completes()2613       testImportFilesIntoFileGroup_whenNoNewInlineFilesSpecifiedAndFilesDownloaded_completes()
2614           throws Exception {
2615     // Create a group that has 1 standard file and 1 inline file, both downloaded
2616     DataFileGroupInternal dataFileGroup =
2617         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
2618             .addFile(
2619                 DataFile.newBuilder()
2620                     .setFileId("inline-file")
2621                     .setChecksum("abc")
2622                     .setUrlToDownload("inlinefile:sha1:abc")
2623                     .build())
2624             .build();
2625     ImmutableMap<String, FileSource> inlineFileMap =
2626         ImmutableMap.of(
2627             "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2628 
2629     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2630 
2631     writeDownloadedFileGroup(getDownloadedKey(groupKey), dataFileGroup);
2632     writeSharedFiles(
2633         sharedFilesMetadata,
2634         dataFileGroup,
2635         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
2636 
2637     fileGroupManager
2638         .importFilesIntoFileGroup(
2639             groupKey,
2640             /* buildId= */ 0,
2641             /* variantId= */ "",
2642             /* updatedDataFileList= */ ImmutableList.of(),
2643             inlineFileMap,
2644             /* customPropertyOptional= */ Optional.absent(),
2645             noCustomValidation())
2646         .get();
2647 
2648     // Since no new files were specified, the group should remain the same (downloaded).
2649     DataFileGroupInternal downloadedFileGroup =
2650         fileGroupsMetadata.read(getDownloadedKey(groupKey)).get();
2651     assertThat(downloadedFileGroup.getGroupName()).isEqualTo(dataFileGroup.getGroupName());
2652     assertThat(downloadedFileGroup.getBuildId()).isEqualTo(dataFileGroup.getBuildId());
2653     assertThat(downloadedFileGroup.getVariantId()).isEqualTo(dataFileGroup.getVariantId());
2654     assertThat(downloadedFileGroup.getFileList())
2655         .containsExactlyElementsIn(dataFileGroup.getFileList());
2656   }
2657 
2658   @Test
testImportFilesIntoFileGroup_whenNoNewFilesSpecifiedAndFilesPending_completes()2659   public void testImportFilesIntoFileGroup_whenNoNewFilesSpecifiedAndFilesPending_completes()
2660       throws Exception {
2661     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2662 
2663     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2664 
2665     writePendingFileGroup(getPendingKey(groupKey), dataFileGroup);
2666     writeSharedFiles(
2667         sharedFilesMetadata, dataFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2668 
2669     fileGroupManager
2670         .importFilesIntoFileGroup(
2671             groupKey,
2672             /* buildId= */ 0,
2673             /* variantId= */ "",
2674             /* updatedDataFileList= */ ImmutableList.of(),
2675             /* inlineFileMap= */ ImmutableMap.of(),
2676             /* customPropertyOptional= */ Optional.absent(),
2677             noCustomValidation())
2678         .get();
2679 
2680     // Since no new files were specified, the group should remain the same (pending).
2681     DataFileGroupInternal pendingFileGroup = fileGroupsMetadata.read(getPendingKey(groupKey)).get();
2682     assertThat(pendingFileGroup.getGroupName()).isEqualTo(dataFileGroup.getGroupName());
2683     assertThat(pendingFileGroup.getBuildId()).isEqualTo(dataFileGroup.getBuildId());
2684     assertThat(pendingFileGroup.getVariantId()).isEqualTo(dataFileGroup.getVariantId());
2685     assertThat(pendingFileGroup.getFileList()).isEqualTo(dataFileGroup.getFileList());
2686   }
2687 
2688   @Test
testImportFilesIntoFileGroup_whenImportingInlineFileAndPending_mergesGroup()2689   public void testImportFilesIntoFileGroup_whenImportingInlineFileAndPending_mergesGroup()
2690       throws Exception {
2691     // Set up an existing pending file group and a new inline file to merge
2692     DataFileGroupInternal existingPendingFileGroup =
2693         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2694     ImmutableList<DataFile> updatedDataFileList =
2695         ImmutableList.of(
2696             DataFile.newBuilder()
2697                 .setFileId("inline-file")
2698                 .setChecksum("abc")
2699                 .setUrlToDownload("inlinefile:sha1:abc")
2700                 .build());
2701     ImmutableMap<String, FileSource> inlineFileMap =
2702         ImmutableMap.of(
2703             "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2704 
2705     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2706 
2707     writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup);
2708     writeSharedFiles(
2709         sharedFilesMetadata,
2710         existingPendingFileGroup,
2711         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2712 
2713     // TODO: remove once SFM can perform import
2714     // write inline file as downloaded so FGM can find it
2715     NewFileKey newInlineFileKey =
2716         SharedFilesMetadata.createKeyFromDataFile(
2717             updatedDataFileList.get(0), existingPendingFileGroup.getAllowedReadersEnum());
2718     SharedFile newInlineSharedFile =
2719         SharedFile.newBuilder()
2720             .setFileName(updatedDataFileList.get(0).getFileId())
2721             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
2722             .build();
2723     sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get();
2724 
2725     fileGroupManager
2726         .importFilesIntoFileGroup(
2727             groupKey,
2728             /* buildId= */ 0,
2729             /* variantId= */ "",
2730             updatedDataFileList,
2731             inlineFileMap,
2732             /* customPropertyOptional= */ Optional.absent(),
2733             noCustomValidation())
2734         .get();
2735 
2736     // Check that resulting file group remains pending, but should have both files merged together
2737     DataFileGroupInternal pendingFileGroupAfterImport =
2738         fileGroupsMetadata.read(getPendingKey(groupKey)).get();
2739     assertThat(pendingFileGroupAfterImport.getFileCount()).isEqualTo(2);
2740     assertThat(pendingFileGroupAfterImport.getFileList())
2741         .containsExactly(existingPendingFileGroup.getFile(0), updatedDataFileList.get(0));
2742   }
2743 
2744   @Test
testImportFilesIntoFileGroup_whenImportingInlineFileAndDownloaded_mergesGroup()2745   public void testImportFilesIntoFileGroup_whenImportingInlineFileAndDownloaded_mergesGroup()
2746       throws Exception {
2747     // Set up an existing downloaded file group and a new file group with an inline file to merge
2748     DataFileGroupInternal existingDownloadedFileGroup =
2749         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2750     ImmutableList<DataFile> updatedDataFileList =
2751         ImmutableList.of(
2752             DataFile.newBuilder()
2753                 .setFileId("inline-file")
2754                 .setChecksum("abc")
2755                 .setUrlToDownload("inlinefile:sha1:abc")
2756                 .build());
2757     ImmutableMap<String, FileSource> inlineFileMap =
2758         ImmutableMap.of(
2759             "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2760 
2761     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2762 
2763     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
2764     writeSharedFiles(
2765         sharedFilesMetadata,
2766         existingDownloadedFileGroup,
2767         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
2768 
2769     // TODO: remove once SFM can perform import
2770     // write inline file as downloaded so FGM can find it
2771     NewFileKey newInlineFileKey =
2772         SharedFilesMetadata.createKeyFromDataFile(
2773             updatedDataFileList.get(0), existingDownloadedFileGroup.getAllowedReadersEnum());
2774     SharedFile newInlineSharedFile =
2775         SharedFile.newBuilder()
2776             .setFileName(updatedDataFileList.get(0).getFileId())
2777             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
2778             .build();
2779     sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get();
2780 
2781     fileGroupManager
2782         .importFilesIntoFileGroup(
2783             groupKey,
2784             /* buildId= */ 0,
2785             /* variantId= */ "",
2786             updatedDataFileList,
2787             inlineFileMap,
2788             /* customPropertyOptional= */ Optional.absent(),
2789             noCustomValidation())
2790         .get();
2791 
2792     // Check that resulting file group is downloaded, but should have both files merged together
2793     DataFileGroupInternal downloadedFileGroupAfterImport =
2794         fileGroupsMetadata.read(getDownloadedKey(groupKey)).get();
2795     assertThat(downloadedFileGroupAfterImport.getFileCount()).isEqualTo(2);
2796     assertThat(downloadedFileGroupAfterImport.getFileList())
2797         .containsExactly(existingDownloadedFileGroup.getFile(0), updatedDataFileList.get(0));
2798   }
2799 
2800   @Test
testImportFilesIntoFileGroup_whenMatchesDownloadedButNotPending_importsToDownloaded()2801   public void testImportFilesIntoFileGroup_whenMatchesDownloadedButNotPending_importsToDownloaded()
2802       throws Exception {
2803     // Set up an existing pending file group, an existing downloaded file group and a new file
2804     // group that matches the downloaded file group
2805     DataFileGroupInternal existingPendingFileGroup =
2806         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2807     DataFileGroupInternal existingDownloadedFileGroup =
2808         existingPendingFileGroup.toBuilder()
2809             .clearFile()
2810             .addFile(MddTestUtil.createDataFile("downloaded-file", 0))
2811             .setBuildId(10)
2812             .build();
2813     ImmutableList<DataFile> updatedDataFileList =
2814         ImmutableList.of(
2815             DataFile.newBuilder()
2816                 .setFileId("inline-file")
2817                 .setChecksum("abc")
2818                 .setUrlToDownload("inlinefile:abc")
2819                 .build());
2820     ImmutableMap<String, FileSource> inlineFileMap =
2821         ImmutableMap.of(
2822             "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2823 
2824     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2825 
2826     writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup);
2827     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
2828 
2829     writeSharedFiles(
2830         sharedFilesMetadata,
2831         existingPendingFileGroup,
2832         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2833     writeSharedFiles(
2834         sharedFilesMetadata,
2835         existingDownloadedFileGroup,
2836         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
2837 
2838     // TODO: remove once SFM can perform import
2839     // write inline file as downloaded so FGM can find it
2840     NewFileKey newInlineFileKey =
2841         SharedFilesMetadata.createKeyFromDataFile(
2842             updatedDataFileList.get(0), existingDownloadedFileGroup.getAllowedReadersEnum());
2843     SharedFile newInlineSharedFile =
2844         SharedFile.newBuilder()
2845             .setFileName(updatedDataFileList.get(0).getFileId())
2846             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
2847             .build();
2848     sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get();
2849 
2850     fileGroupManager
2851         .importFilesIntoFileGroup(
2852             groupKey,
2853             /* buildId= */ 10,
2854             /* variantId= */ "",
2855             updatedDataFileList,
2856             inlineFileMap,
2857             /* customPropertyOptional= */ Optional.absent(),
2858             noCustomValidation())
2859         .get();
2860 
2861     // Check that downloaded file group now contains the merged file group and pending group remains
2862     // the same.
2863     DataFileGroupInternal downloadedFileGroupAfterImport =
2864         fileGroupsMetadata.read(getDownloadedKey(groupKey)).get();
2865     assertThat(downloadedFileGroupAfterImport.getBuildId())
2866         .isEqualTo(existingDownloadedFileGroup.getBuildId());
2867     assertThat(downloadedFileGroupAfterImport.getFileCount()).isEqualTo(2);
2868     assertThat(downloadedFileGroupAfterImport.getFileList())
2869         .containsExactly(existingDownloadedFileGroup.getFile(0), updatedDataFileList.get(0));
2870     assertThat(fileGroupsMetadata.read(getPendingKey(groupKey)).get())
2871         .isEqualTo(existingPendingFileGroup);
2872   }
2873 
2874   @Test
testImportFilesIntoFileGroup_whenMatchesPendingButNotDownloaded_importsToPending()2875   public void testImportFilesIntoFileGroup_whenMatchesPendingButNotDownloaded_importsToPending()
2876       throws Exception {
2877     // Set up an existing pending file group, an existing downloaded file group and a new file
2878     // group that matches the pending file group
2879     DataFileGroupInternal existingPendingFileGroup =
2880         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
2881     DataFileGroupInternal existingDownloadedFileGroup =
2882         existingPendingFileGroup.toBuilder()
2883             .clearFile()
2884             .addFile(MddTestUtil.createDataFile("downloaded-file", 0))
2885             .setBuildId(10)
2886             .build();
2887     ImmutableList<DataFile> updatedDataFileList =
2888         ImmutableList.of(
2889             DataFile.newBuilder()
2890                 .setFileId("inline-file")
2891                 .setChecksum("abc")
2892                 .setUrlToDownload("inlinefile:abc")
2893                 .build());
2894     ImmutableMap<String, FileSource> inlineFileMap =
2895         ImmutableMap.of(
2896             "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
2897 
2898     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2899 
2900     writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup);
2901     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
2902 
2903     writeSharedFiles(
2904         sharedFilesMetadata,
2905         existingPendingFileGroup,
2906         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
2907     writeSharedFiles(
2908         sharedFilesMetadata,
2909         existingDownloadedFileGroup,
2910         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
2911 
2912     // TODO: remove once SFM can perform import
2913     // write inline file as downloaded so FGM can find it
2914     NewFileKey newInlineFileKey =
2915         SharedFilesMetadata.createKeyFromDataFile(
2916             updatedDataFileList.get(0), existingPendingFileGroup.getAllowedReadersEnum());
2917     SharedFile newInlineSharedFile =
2918         SharedFile.newBuilder()
2919             .setFileName(updatedDataFileList.get(0).getFileId())
2920             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
2921             .build();
2922     sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get();
2923 
2924     fileGroupManager
2925         .importFilesIntoFileGroup(
2926             groupKey,
2927             /* buildId= */ 0,
2928             /* variantId= */ "",
2929             updatedDataFileList,
2930             inlineFileMap,
2931             /* customPropertyOptional= */ Optional.absent(),
2932             noCustomValidation())
2933         .get();
2934 
2935     // Check that pending file group now contains the merged file group and downloaded group remains
2936     // the same.
2937     DataFileGroupInternal pendingFileGroupAfterImport =
2938         fileGroupsMetadata.read(getPendingKey(groupKey)).get();
2939     assertThat(pendingFileGroupAfterImport.getBuildId())
2940         .isEqualTo(existingPendingFileGroup.getBuildId());
2941     assertThat(pendingFileGroupAfterImport.getFileCount()).isEqualTo(2);
2942     assertThat(pendingFileGroupAfterImport.getFileList())
2943         .containsExactly(existingPendingFileGroup.getFile(0), updatedDataFileList.get(0));
2944     assertThat(fileGroupsMetadata.read(getDownloadedKey(groupKey)).get())
2945         .isEqualTo(existingDownloadedFileGroup);
2946   }
2947 
2948   @Test
testImportFilesIntoFileGroup_whenPerformingImport_choosesFileSourceById()2949   public void testImportFilesIntoFileGroup_whenPerformingImport_choosesFileSourceById()
2950       throws Exception {
2951     // Use mockSharedFileManager to check startImport call
2952     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
2953 
2954     FileSource testFileSource =
2955         FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE"));
2956 
2957     // Setup file group with inline file to import
2958     DataFileGroupInternal fileGroup =
2959         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
2960             .addFile(
2961                 DataFile.newBuilder()
2962                     .setFileId("inline-file")
2963                     .setChecksum("abc")
2964                     .setUrlToDownload("inlinefile:sha1:abc")
2965                     .build())
2966             .build();
2967 
2968     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
2969     NewFileKey inlineFileKey =
2970         SharedFilesMetadata.createKeyFromDataFile(
2971             fileGroup.getFile(0), fileGroup.getAllowedReadersEnum());
2972 
2973     writePendingFileGroup(getPendingKey(groupKey), fileGroup);
2974 
2975     // Setup mock SFM with successful calls
2976     when(mockSharedFileManager.reserveFileEntry(inlineFileKey))
2977         .thenReturn(Futures.immediateFuture(true));
2978     when(mockSharedFileManager.getFileStatus(inlineFileKey))
2979         .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_IN_PROGRESS));
2980     when(mockSharedFileManager.startImport(
2981             groupKeyCaptor.capture(),
2982             eq(fileGroup.getFile(0)),
2983             eq(inlineFileKey),
2984             any(),
2985             fileSourceCaptor.capture()))
2986         .thenReturn(Futures.immediateVoidFuture());
2987 
2988     fileGroupManager
2989         .importFilesIntoFileGroup(
2990             groupKey,
2991             /* buildId= */ 0,
2992             /* variantId= */ "",
2993             /* updatedDataFileList= */ ImmutableList.of(),
2994             /* inlineFileMap= */ ImmutableMap.of("inline-file", testFileSource),
2995             /* customPropertyOptional= */ Optional.absent(),
2996             noCustomValidation())
2997         .get();
2998 
2999     // Check that SFM startImport was called with expected inputs
3000     verify(mockSharedFileManager, times(1)).startImport(any(), any(), any(), any(), any());
3001     assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(TEST_GROUP);
3002     assertThat(fileSourceCaptor.getValue()).isEqualTo(testFileSource);
3003   }
3004 
3005   @Test
testImportFilesIntoFileGroup_whenFileAlreadyDownloaded_doesNotAttemptToImport()3006   public void testImportFilesIntoFileGroup_whenFileAlreadyDownloaded_doesNotAttemptToImport()
3007       throws Exception {
3008     // Use mockSharedFileManager to check startImport call
3009     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
3010 
3011     FileSource testFileSource =
3012         FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE"));
3013 
3014     // Create an existing downloaded file group and attempt to import again with a given source.
3015     // Since the file is already marked DOWNLOAD_COMPLETE, the import should not be invoked.
3016     DataFileGroupInternal existingDownloadedFileGroup =
3017         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
3018             .addFile(
3019                 DataFile.newBuilder()
3020                     .setFileId("inline-file")
3021                     .setChecksum("abc")
3022                     .setUrlToDownload("inlinefile:abc")
3023                     .build())
3024             .build();
3025 
3026     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
3027     NewFileKey inlineFileKey =
3028         SharedFilesMetadata.createKeyFromDataFile(
3029             existingDownloadedFileGroup.getFile(0),
3030             existingDownloadedFileGroup.getAllowedReadersEnum());
3031 
3032     writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup);
3033 
3034     // Setup mock SFM with successful calls
3035     when(mockSharedFileManager.reserveFileEntry(inlineFileKey))
3036         .thenReturn(Futures.immediateFuture(true));
3037     when(mockSharedFileManager.getFileStatus(inlineFileKey))
3038         .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_COMPLETE));
3039 
3040     fileGroupManager
3041         .importFilesIntoFileGroup(
3042             groupKey,
3043             /* buildId= */ 0,
3044             /* variantId= */ "",
3045             /* updatedDataFileList= */ ImmutableList.of(),
3046             /* inlineFileMap= */ ImmutableMap.of("inline-file", testFileSource),
3047             /* customPropertyOptional= */ Optional.absent(),
3048             noCustomValidation())
3049         .get();
3050 
3051     // Check that SFM startImport was not called
3052     verify(mockSharedFileManager, times(0)).startImport(any(), any(), any(), any(), any());
3053   }
3054 
3055   @Test
testImportFilesIntoFileGroups_whenFileSourceNotProvided_fails()3056   public void testImportFilesIntoFileGroups_whenFileSourceNotProvided_fails() throws Exception {
3057     // create a file group added to MDD with an inline file and check that import call fails if
3058     // source is not provided.
3059     DataFileGroupInternal existingFileGroup =
3060         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
3061             .addFile(
3062                 DataFile.newBuilder()
3063                     .setFileId("inline-file")
3064                     .setChecksum("abc")
3065                     .setUrlToDownload("inlinefile:abc")
3066                     .build())
3067             .build();
3068 
3069     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
3070 
3071     writePendingFileGroup(getPendingKey(groupKey), existingFileGroup);
3072 
3073     writeSharedFiles(
3074         sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS));
3075 
3076     ExecutionException ex =
3077         assertThrows(
3078             ExecutionException.class,
3079             () ->
3080                 fileGroupManager
3081                     .importFilesIntoFileGroup(
3082                         groupKey,
3083                         /* buildId= */ 0,
3084                         /* variantId= */ "",
3085                         /* updatedDataFileList= */ ImmutableList.of(),
3086                         /* inlineFileMap= */ ImmutableMap.of(),
3087                         /* customPropertyOptional= */ Optional.absent(),
3088                         noCustomValidation())
3089                     .get());
3090     assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class);
3091     AggregateException aex = (AggregateException) ex.getCause();
3092     assertThat(aex.getFailures()).hasSize(1);
3093     assertThat(aex.getFailures().get(0)).isInstanceOf(DownloadException.class);
3094     DownloadException dex = (DownloadException) aex.getFailures().get(0);
3095     assertThat(dex.getDownloadResultCode())
3096         .isEqualTo(DownloadResultCode.MISSING_INLINE_FILE_SOURCE);
3097   }
3098 
3099   @Test
testImportFilesIntoFileGroup_whenImportFails_preventsMetadataUpdate()3100   public void testImportFilesIntoFileGroup_whenImportFails_preventsMetadataUpdate()
3101       throws Exception {
3102     // Use mockSharedFileManager to mock a failure for an import
3103     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
3104 
3105     FileSource testFileSource1 =
3106         FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE_1"));
3107     FileSource testFileSource2 =
3108         FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE_2"));
3109 
3110     // Setup empty file group
3111     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0);
3112 
3113     // Setup list of files that should be imported
3114     ImmutableList<DataFile> updatedDataFileList =
3115         ImmutableList.of(
3116             DataFile.newBuilder()
3117                 .setFileId("inline-file-1")
3118                 .setChecksum("abc")
3119                 .setUrlToDownload("inlinefile:sha1:abc")
3120                 .build(),
3121             DataFile.newBuilder()
3122                 .setFileId("inline-file-2")
3123                 .setChecksum("def")
3124                 .setUrlToDownload("inlinefile:sha1:def")
3125                 .build());
3126 
3127     GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build();
3128     NewFileKey inlineFileKey1 =
3129         SharedFilesMetadata.createKeyFromDataFile(
3130             updatedDataFileList.get(0), fileGroup.getAllowedReadersEnum());
3131     NewFileKey inlineFileKey2 =
3132         SharedFilesMetadata.createKeyFromDataFile(
3133             updatedDataFileList.get(1), fileGroup.getAllowedReadersEnum());
3134 
3135     writeDownloadedFileGroup(getDownloadedKey(groupKey), fileGroup);
3136 
3137     // Setup mock calls to SFM
3138     when(mockSharedFileManager.reserveFileEntry(any())).thenReturn(Futures.immediateFuture(true));
3139 
3140     // Mock that inline file 1 completed, but inline file 2 failed
3141     when(mockSharedFileManager.getFileStatus(inlineFileKey1))
3142         .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_IN_PROGRESS));
3143     when(mockSharedFileManager.getFileStatus(inlineFileKey2))
3144         .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_FAILED));
3145     when(mockSharedFileManager.startImport(
3146             any(), eq(updatedDataFileList.get(0)), eq(inlineFileKey1), any(), any()))
3147         .thenReturn(Futures.immediateVoidFuture());
3148     when(mockSharedFileManager.startImport(
3149             any(), eq(updatedDataFileList.get(1)), eq(inlineFileKey2), any(), any()))
3150         .thenReturn(
3151             Futures.immediateFailedFuture(
3152                 DownloadException.builder()
3153                     .setDownloadResultCode(DownloadResultCode.DOWNLOAD_TRANSFORM_IO_ERROR)
3154                     .build()));
3155 
3156     ExecutionException ex =
3157         assertThrows(
3158             ExecutionException.class,
3159             () ->
3160                 fileGroupManager
3161                     .importFilesIntoFileGroup(
3162                         groupKey,
3163                         /* buildId= */ 0,
3164                         /* variantId= */ "",
3165                         updatedDataFileList,
3166                         /* inlineFileMap= */ ImmutableMap.of(
3167                             "inline-file-1", testFileSource1, "inline-file-2", testFileSource2),
3168                         /* customPropertyOptional= */ Optional.absent(),
3169                         noCustomValidation())
3170                     .get());
3171 
3172     // Check for expected cause
3173     assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class);
3174     AggregateException aex = (AggregateException) ex.getCause();
3175     assertThat(aex.getFailures()).hasSize(1);
3176     assertThat(aex.getFailures().get(0)).isInstanceOf(DownloadException.class);
3177     DownloadException dex = (DownloadException) aex.getFailures().get(0);
3178     assertThat(dex.getDownloadResultCode())
3179         .isEqualTo(DownloadResultCode.DOWNLOAD_TRANSFORM_IO_ERROR);
3180 
3181     // Check that existing (empty) group remains in metadata. iow, the files from
3182     // updatedDataFileList were not added since the import failed.
3183     DataFileGroupInternal existingFileGroup =
3184         fileGroupsMetadata.read(getDownloadedKey(groupKey)).get();
3185     assertThat(existingFileGroup.getFileList()).isEmpty();
3186 
3187     // Check that SFM startImport was called with expected inputs
3188     verify(mockSharedFileManager, times(2)).startImport(any(), any(), any(), any(), any());
3189   }
3190 
3191   @Test
testImportFilesIntoFileGroup_skipsSideloadedFile()3192   public void testImportFilesIntoFileGroup_skipsSideloadedFile() throws Exception {
3193     // Create sideloaded group with inline file
3194     DataFileGroupInternal sideloadedGroup =
3195         DataFileGroupInternal.newBuilder()
3196             .setGroupName(TEST_GROUP)
3197             .addFile(
3198                 DataFile.newBuilder()
3199                     .setFileId("sideloaded_file")
3200                     .setUrlToDownload("file:/test")
3201                     .setChecksumType(DataFile.ChecksumType.NONE)
3202                     .build())
3203             .addFile(
3204                 DataFile.newBuilder()
3205                     .setFileId("inline_file")
3206                     .setUrlToDownload("inlinefile:sha1:checksum")
3207                     .setChecksum("checksum")
3208                     .build())
3209             .build();
3210     ImmutableMap<String, FileSource> inlineFileMap =
3211         ImmutableMap.of(
3212             "inline_file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
3213     NewFileKey inlineFileKey =
3214         SharedFilesMetadata.createKeyFromDataFile(
3215             sideloadedGroup.getFile(1), sideloadedGroup.getAllowedReadersEnum());
3216 
3217     // Write group as pending since we are waiting on inline file
3218     writePendingFileGroup(testKey, sideloadedGroup);
3219 
3220     // Write inline file as succeeded so we skip SFM's import call
3221     SharedFile inlineSharedFile =
3222         SharedFile.newBuilder()
3223             .setFileName(sideloadedGroup.getFile(1).getFileId())
3224             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
3225             .build();
3226     sharedFilesMetadata.write(inlineFileKey, inlineSharedFile).get();
3227 
3228     fileGroupManager
3229         .importFilesIntoFileGroup(
3230             testKey,
3231             sideloadedGroup.getBuildId(),
3232             sideloadedGroup.getVariantId(),
3233             /* updatedDataFileList= */ ImmutableList.of(),
3234             inlineFileMap,
3235             /* customPropertyOptional= */ Optional.absent(),
3236             noCustomValidation())
3237         .get();
3238 
3239     assertThat(readPendingFileGroup(testKey)).isNull();
3240     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
3241   }
3242 
3243   @Test
testDownloadPendingGroup_success()3244   public void testDownloadPendingGroup_success() throws Exception {
3245     // Write 1 group to the pending shared prefs.
3246     DataFileGroupInternal fileGroup =
3247         createDataFileGroup(
3248             TEST_GROUP,
3249             /* fileCount= */ 2,
3250             /* downloadAttemptCount= */ 3,
3251             /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L);
3252     ExtraHttpHeader extraHttpHeader =
3253         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
3254 
3255     fileGroup =
3256         fileGroup.toBuilder()
3257             .setOwnerPackage(context.getPackageName())
3258             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3259             .setTrafficTag(TRAFFIC_TAG)
3260             .addGroupExtraHttpHeaders(extraHttpHeader)
3261             .build();
3262     writePendingFileGroup(testKey, fileGroup);
3263 
3264     writeSharedFiles(
3265         sharedFilesMetadata,
3266         fileGroup,
3267         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
3268 
3269     fileGroupManager
3270         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
3271         .get();
3272 
3273     // Verify that pending key is removed if download is complete.
3274     assertThat(readPendingFileGroup(testKey)).isNull();
3275 
3276     // Verify that downloaded key is written into metadata if download is complete.
3277     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
3278     verify(mockLogger)
3279         .logEventSampled(
3280             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
3281             TEST_GROUP,
3282             0,
3283             /* buildId= */ 0,
3284             /* variantId= */ "");
3285     verify(mockLogger)
3286         .logMddDownloadResult(
3287             MddDownloadResult.Code.SUCCESS,
3288             createFileGroupDetails(fileGroup)
3289                 .setOwnerPackage(context.getPackageName())
3290                 .clearFileCount()
3291                 .build());
3292     verify(mockLogger)
3293         .logMddDownloadLatency(
3294             createFileGroupDetails(fileGroup).build(),
3295             createMddDownloadLatency(
3296                 /* downloadAttemptCount= */ 4,
3297                 /* downloadLatencyMs= */ 0L,
3298                 /* totalLatencyMs= */ 500L));
3299   }
3300 
3301   @Test
testDownloadPendingGroup_withFailingCustomValidator()3302   public void testDownloadPendingGroup_withFailingCustomValidator() throws Exception {
3303     // Write 1 group to the pending shared prefs.
3304     DataFileGroupInternal fileGroup =
3305         createDataFileGroup(
3306             TEST_GROUP,
3307             /* fileCount= */ 2,
3308             /* downloadAttemptCount= */ 3,
3309             /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L);
3310     ExtraHttpHeader extraHttpHeader =
3311         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
3312 
3313     fileGroup =
3314         fileGroup.toBuilder()
3315             .setOwnerPackage(context.getPackageName())
3316             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3317             .setTrafficTag(TRAFFIC_TAG)
3318             .addGroupExtraHttpHeaders(extraHttpHeader)
3319             .build();
3320     writePendingFileGroup(testKey, fileGroup);
3321 
3322     writeSharedFiles(
3323         sharedFilesMetadata,
3324         fileGroup,
3325         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
3326 
3327     AsyncFunction<DataFileGroupInternal, Boolean> failingValidator =
3328         unused -> Futures.immediateFuture(false);
3329     ListenableFuture<DataFileGroupInternal> downloadFuture =
3330         fileGroupManager.downloadFileGroup(
3331             testKey, DownloadConditions.getDefaultInstance(), failingValidator);
3332 
3333     ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get);
3334     assertThat(exception).hasCauseThat().isInstanceOf(DownloadException.class);
3335     DownloadException cause = (DownloadException) exception.getCause();
3336     assertThat(cause).isNotNull();
3337     assertThat(cause).hasMessageThat().contains("CUSTOM_FILEGROUP_VALIDATION_FAILED");
3338 
3339     // Verify that pending key was removed. This will ensure the files are eligible for garbage
3340     // collection.
3341     assertThat(readPendingFileGroup(testKey)).isNull();
3342 
3343     verify(mockLogger)
3344         .logEventSampled(
3345             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
3346             TEST_GROUP,
3347             /* fileGroupVersionNumber= */ 0,
3348             /* buildId= */ 0,
3349             /* variantId= */ "");
3350 
3351     ArgumentCaptor<MddDownloadResult.Code> resultCodeCaptor =
3352         ArgumentCaptor.forClass(MddDownloadResult.Code.class);
3353     ArgumentCaptor<DataDownloadFileGroupStats> groupDetailsCaptor =
3354         ArgumentCaptor.forClass(DataDownloadFileGroupStats.class);
3355     verify(mockLogger)
3356         .logMddDownloadResult(resultCodeCaptor.capture(), groupDetailsCaptor.capture());
3357 
3358     // Also clearing the file group version number becaused it ends up not being attached since
3359     // the pending group was removed.
3360     DataDownloadFileGroupStats expectedGroupDetails =
3361         createFileGroupDetails(fileGroup).clearFileCount().clearFileGroupVersionNumber().build();
3362 
3363     assertThat(resultCodeCaptor.getAllValues())
3364         .containsExactly(MddDownloadResult.Code.CUSTOM_FILEGROUP_VALIDATION_FAILED);
3365     assertThat(groupDetailsCaptor.getAllValues()).containsExactly(expectedGroupDetails);
3366   }
3367 
3368   @Test
testDownloadFileGroup_failed()3369   public void testDownloadFileGroup_failed() throws Exception {
3370     // Write 1 group to the pending shared prefs.
3371     DataFileGroupInternal fileGroup =
3372         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
3373             .setVariantId("test-variant")
3374             .setBuildId(10)
3375             .build();
3376     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
3377     fileGroup =
3378         fileGroup.toBuilder()
3379             .setOwnerPackage(context.getPackageName())
3380             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3381             .build();
3382     writePendingFileGroup(testKey, fileGroup);
3383 
3384     // Not all files are downloaded.
3385     writeSharedFiles(
3386         sharedFilesMetadata,
3387         fileGroup,
3388         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED));
3389 
3390     // First file failed.
3391     Uri failingFileUri =
3392         DirectoryUtil.getOnDeviceUri(
3393             context,
3394             keys[0].getAllowedReaders(),
3395             fileGroup.getFile(0).getFileId(),
3396             fileGroup.getFile(0).getChecksum(),
3397             mockSilentFeedback,
3398             /* instanceId= */ Optional.absent(),
3399             false);
3400     fileDownloadFails(keys[0], failingFileUri, DownloadResultCode.LOW_DISK_ERROR);
3401 
3402     // Second file succeeded.
3403     Uri succeedingFileUri =
3404         DirectoryUtil.getOnDeviceUri(
3405             context,
3406             keys[1].getAllowedReaders(),
3407             fileGroup.getFile(1).getFileId(),
3408             fileGroup.getFile(1).getChecksum(),
3409             mockSilentFeedback,
3410             /* instanceId= */ Optional.absent(),
3411             false);
3412     fileDownloadSucceeds(keys[1], succeedingFileUri);
3413 
3414     ListenableFuture<DataFileGroupInternal> downloadFuture =
3415         fileGroupManager.downloadFileGroup(
3416             testKey, DownloadConditions.getDefaultInstance(), noCustomValidation());
3417 
3418     ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get);
3419     assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class);
3420     AggregateException cause = (AggregateException) exception.getCause();
3421     assertThat(cause).isNotNull();
3422     ImmutableList<Throwable> failures = cause.getFailures();
3423     assertThat(failures).hasSize(1);
3424     assertThat(failures.get(0)).isInstanceOf(DownloadException.class);
3425     assertThat(failures.get(0)).hasMessageThat().contains("LOW_DISK_ERROR");
3426 
3427     // Verify that the pending group is still part of pending groups prefs.
3428     assertThat(readPendingFileGroup(testKey)).isNotNull();
3429 
3430     // Verify that the pending group is not changed from pending to downloaded.
3431     assertThat(readDownloadedFileGroup(testKey)).isNull();
3432 
3433     verify(mockLogger)
3434         .logEventSampled(
3435             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
3436             TEST_GROUP,
3437             /* fileGroupVersionNumber= */ 0,
3438             /* buildId= */ 10,
3439             /* variantId= */ "test-variant");
3440 
3441     ArgumentCaptor<MddDownloadResult.Code> resultCodeCaptor =
3442         ArgumentCaptor.forClass(MddDownloadResult.Code.class);
3443     ArgumentCaptor<DataDownloadFileGroupStats> groupDetailsCaptor =
3444         ArgumentCaptor.forClass(DataDownloadFileGroupStats.class);
3445     verify(mockLogger)
3446         .logMddDownloadResult(resultCodeCaptor.capture(), groupDetailsCaptor.capture());
3447 
3448     DataDownloadFileGroupStats expectedGroupDetails =
3449         createFileGroupDetails(fileGroup).clearFileCount().build();
3450 
3451     assertThat(resultCodeCaptor.getAllValues())
3452         .containsExactly(MddDownloadResult.Code.LOW_DISK_ERROR);
3453     assertThat(groupDetailsCaptor.getAllValues()).containsExactly(expectedGroupDetails);
3454   }
3455 
3456   @Test
testDownloadFileGroup_failedWithMultipleExceptions()3457   public void testDownloadFileGroup_failedWithMultipleExceptions() throws Exception {
3458     // Write 1 group to the pending shared prefs.
3459     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3);
3460     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
3461     fileGroup =
3462         fileGroup.toBuilder()
3463             .setOwnerPackage(context.getPackageName())
3464             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3465             .build();
3466     writePendingFileGroup(testKey, fileGroup);
3467 
3468     // Not all files are downloaded.
3469     writeSharedFiles(
3470         sharedFilesMetadata,
3471         fileGroup,
3472         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED));
3473 
3474     // First file succeeded.
3475     Uri succeedingFileUri =
3476         DirectoryUtil.getOnDeviceUri(
3477             context,
3478             keys[0].getAllowedReaders(),
3479             fileGroup.getFile(0).getFileId(),
3480             fileGroup.getFile(0).getChecksum(),
3481             mockSilentFeedback,
3482             /* instanceId= */ Optional.absent(),
3483             false);
3484     fileDownloadSucceeds(keys[0], succeedingFileUri);
3485 
3486     // Second file failed with download transform I/O error.
3487     Uri failingFileUri1 =
3488         DirectoryUtil.getOnDeviceUri(
3489             context,
3490             keys[1].getAllowedReaders(),
3491             fileGroup.getFile(1).getFileId(),
3492             fileGroup.getFile(1).getChecksum(),
3493             mockSilentFeedback,
3494             /* instanceId= */ Optional.absent(),
3495             false);
3496     fileDownloadFails(keys[1], failingFileUri1, DownloadResultCode.DOWNLOAD_TRANSFORM_IO_ERROR);
3497 
3498     // Third file failed with android downloader http error.
3499     Uri failingFileUri2 =
3500         DirectoryUtil.getOnDeviceUri(
3501             context,
3502             keys[2].getAllowedReaders(),
3503             fileGroup.getFile(2).getFileId(),
3504             fileGroup.getFile(2).getChecksum(),
3505             mockSilentFeedback,
3506             /* instanceId= */ Optional.absent(),
3507             false);
3508     fileDownloadFails(keys[2], failingFileUri2, DownloadResultCode.ANDROID_DOWNLOADER_HTTP_ERROR);
3509 
3510     ListenableFuture<DataFileGroupInternal> downloadFuture =
3511         fileGroupManager.downloadFileGroup(
3512             testKey, DownloadConditions.getDefaultInstance(), noCustomValidation());
3513 
3514     // Ensure that all exceptions are aggregated.
3515     ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get);
3516     assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class);
3517     AggregateException cause = (AggregateException) exception.getCause();
3518     assertThat(cause).isNotNull();
3519     ImmutableList<Throwable> failures = cause.getFailures();
3520     assertThat(failures).hasSize(2);
3521     assertThat(failures.get(0)).isInstanceOf(DownloadException.class);
3522     assertThat(failures.get(0)).hasMessageThat().contains("DOWNLOAD_TRANSFORM_IO_ERROR");
3523     assertThat(failures.get(1)).isInstanceOf(DownloadException.class);
3524     assertThat(failures.get(1)).hasMessageThat().contains("ANDROID_DOWNLOADER_HTTP_ERROR");
3525 
3526     // Verify that the pending group is still part of pending groups prefs.
3527     assertThat(readPendingFileGroup(testKey)).isNotNull();
3528 
3529     // Verify that the pending group is not changed from pending to downloaded.
3530     assertThat(readDownloadedFileGroup(testKey)).isNull();
3531 
3532     verify(mockLogger)
3533         .logEventSampled(
3534             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
3535             TEST_GROUP,
3536             /* fileGroupVersionNumber= */ 0,
3537             /* buildId= */ 0,
3538             /* variantId= */ "");
3539 
3540     ArgumentCaptor<MddDownloadResult.Code> resultCodeCaptor =
3541         ArgumentCaptor.forClass(MddDownloadResult.Code.class);
3542     ArgumentCaptor<DataDownloadFileGroupStats> groupDetailsCaptor =
3543         ArgumentCaptor.forClass(DataDownloadFileGroupStats.class);
3544     verify(mockLogger, times(2))
3545         .logMddDownloadResult(resultCodeCaptor.capture(), groupDetailsCaptor.capture());
3546 
3547     DataDownloadFileGroupStats expectedGroupDetails =
3548         createFileGroupDetails(fileGroup).clearFileCount().build();
3549 
3550     assertThat(resultCodeCaptor.getAllValues())
3551         .containsExactly(
3552             MddDownloadResult.Code.DOWNLOAD_TRANSFORM_IO_ERROR,
3553             MddDownloadResult.Code.ANDROID_DOWNLOADER_HTTP_ERROR);
3554     assertThat(groupDetailsCaptor.getAllValues())
3555         .containsExactly(expectedGroupDetails, expectedGroupDetails);
3556   }
3557 
3558   @Test
testDownloadFileGroup_failedWithUnknownError()3559   public void testDownloadFileGroup_failedWithUnknownError() throws Exception {
3560     // Write 1 group to the pending shared prefs.
3561     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
3562     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
3563     fileGroup =
3564         fileGroup.toBuilder()
3565             .setOwnerPackage(context.getPackageName())
3566             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3567             .build();
3568     writePendingFileGroup(testKey, fileGroup);
3569 
3570     writeSharedFiles(
3571         sharedFilesMetadata,
3572         fileGroup,
3573         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.DOWNLOAD_COMPLETE));
3574 
3575     // First file failed.
3576     Uri failingFileUri =
3577         DirectoryUtil.getOnDeviceUri(
3578             context,
3579             keys[0].getAllowedReaders(),
3580             fileGroup.getFile(0).getFileId(),
3581             fileGroup.getFile(0).getChecksum(),
3582             mockSilentFeedback,
3583             /* instanceId= */ Optional.absent(),
3584             false);
3585     // The file status is set to DOWNLOAD_FAILED but the downloader returns an immediateVoidFuture.
3586     // An UNKNOWN_ERROR is logged.
3587     fileDownloadFails(keys[0], failingFileUri, /* failureCode= */ null);
3588 
3589     ListenableFuture<DataFileGroupInternal> downloadFuture =
3590         fileGroupManager.downloadFileGroup(
3591             testKey, DownloadConditions.getDefaultInstance(), noCustomValidation());
3592 
3593     ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get);
3594     assertThat(exception).hasMessageThat().contains("UNKNOWN_ERROR");
3595 
3596     // Verify that the pending group is still part of pending groups prefs.
3597     assertThat(readPendingFileGroup(testKey)).isNotNull();
3598 
3599     // Verify that the pending group is not changed from pending to downloaded.
3600     assertThat(readDownloadedFileGroup(testKey)).isNull();
3601 
3602     verify(mockLogger)
3603         .logEventSampled(
3604             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
3605             TEST_GROUP,
3606             /* fileGroupVersionNumber= */ 0,
3607             /* buildId= */ 0,
3608             /* variantId= */ "");
3609   }
3610 
3611   @Test
testDownloadFileGroup_pending()3612   public void testDownloadFileGroup_pending() throws Exception {
3613     // Write 1 group to the pending shared prefs.
3614     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
3615     fileGroup =
3616         fileGroup.toBuilder()
3617             .setOwnerPackage(context.getPackageName())
3618             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3619             .build();
3620     writePendingFileGroup(testKey, fileGroup);
3621 
3622     // Not all files are downloaded.
3623     writeSharedFiles(
3624         sharedFilesMetadata,
3625         fileGroup,
3626         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.SUBSCRIBED));
3627 
3628     when(mockDownloader.startDownloading(
3629             any(String.class),
3630             any(GroupKey.class),
3631             anyInt(),
3632             anyLong(),
3633             any(String.class),
3634             any(Uri.class),
3635             any(String.class),
3636             anyInt(),
3637             any(DownloadConditions.class),
3638             isA(DownloaderCallbackImpl.class),
3639             anyInt(),
3640             anyList()))
3641         .thenReturn(Futures.immediateVoidFuture());
3642 
3643     ListenableFuture<DataFileGroupInternal> downloadFuture =
3644         fileGroupManager.downloadFileGroup(
3645             testKey, DownloadConditions.getDefaultInstance(), noCustomValidation());
3646 
3647     ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get);
3648     assertThat(exception).hasMessageThat().contains("UNKNOWN_ERROR");
3649 
3650     // Verify that the pending group is still part of pending groups prefs.
3651     assertThat(readPendingFileGroup(testKey)).isNotNull();
3652 
3653     // Verify that the pending group is not changed from pending to downloaded.
3654     assertThat(readDownloadedFileGroup(testKey)).isNull();
3655 
3656     verify(mockLogger)
3657         .logEventSampled(
3658             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
3659             TEST_GROUP,
3660             /* fileGroupVersionNumber= */ 0,
3661             /* buildId= */ 0,
3662             /* variantId= */ "");
3663     verify(mockLogger)
3664         .logMddDownloadResult(
3665             MddDownloadResult.Code.UNKNOWN_ERROR,
3666             createFileGroupDetails(fileGroup)
3667                 .setOwnerPackage(context.getPackageName())
3668                 .clearFileCount()
3669                 .build());
3670   }
3671 
3672   @Test
testDownloadFileGroup_alreadyDownloaded()3673   public void testDownloadFileGroup_alreadyDownloaded() throws Exception {
3674     // Write 1 group to the downloaded shared prefs.
3675     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
3676     fileGroup =
3677         fileGroup.toBuilder()
3678             .setOwnerPackage(context.getPackageName())
3679             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3680             .build();
3681     writeDownloadedFileGroup(testKey, fileGroup);
3682 
3683     List<GroupKey> originalKeys = fileGroupsMetadata.getAllGroupKeys().get();
3684 
3685     ListenableFuture<DataFileGroupInternal> downloadFuture =
3686         fileGroupManager.downloadFileGroup(
3687             testKey, DownloadConditions.getDefaultInstance(), noCustomValidation());
3688 
3689     assertThat(downloadFuture.get()).isEqualTo(fileGroup);
3690 
3691     // Verify that the downloaded group is still part of downloaded groups prefs.
3692     DataFileGroupInternal downloadedGroup = readDownloadedFileGroup(testKey);
3693     assertThat(downloadedGroup).isEqualTo(fileGroup);
3694 
3695     // Verify that no group metadata is written or removed.
3696     assertThat(originalKeys).isEqualTo(fileGroupsMetadata.getAllGroupKeys().get());
3697   }
3698 
3699   @Test
testDownloadFileGroup_nullDownloadCondition()3700   public void testDownloadFileGroup_nullDownloadCondition() throws Exception {
3701     DownloadConditions downloadConditions =
3702         DownloadConditions.newBuilder()
3703             .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI)
3704             .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE)
3705             .build();
3706 
3707     // Write 1 group to the pending shared prefs.
3708     DataFileGroupInternal fileGroup =
3709         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
3710             .setDownloadConditions(downloadConditions)
3711             .build();
3712     writePendingFileGroup(testKey, fileGroup);
3713 
3714     writeSharedFiles(
3715         sharedFilesMetadata,
3716         fileGroup,
3717         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED));
3718 
3719     ArgumentCaptor<DownloadConditions> downloadConditionsCaptor =
3720         ArgumentCaptor.forClass(DownloadConditions.class);
3721     when(mockDownloader.startDownloading(
3722             any(String.class),
3723             any(GroupKey.class),
3724             anyInt(),
3725             anyLong(),
3726             any(String.class),
3727             any(Uri.class),
3728             any(String.class),
3729             anyInt(),
3730             downloadConditionsCaptor.capture(),
3731             isA(DownloaderCallbackImpl.class),
3732             anyInt(),
3733             anyList()))
3734         .then(
3735             new Answer<ListenableFuture<Void>>() {
3736               @Override
3737               public ListenableFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
3738                 writeSharedFiles(
3739                     sharedFilesMetadata,
3740                     fileGroup,
3741                     ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
3742                 return Futures.immediateVoidFuture();
3743               }
3744             });
3745 
3746     DataFileGroupInternal updatedFileGroup =
3747         fileGroup.toBuilder()
3748             .setBookkeeping(
3749                 DataFileGroupBookkeeping.newBuilder()
3750                     .setDownloadStartedCount(1)
3751                     .setGroupDownloadStartedTimestampInMillis(1000L))
3752             .build();
3753 
3754     // Calling with DownloadConditions = null will use the config from server.
3755     assertThat(
3756             fileGroupManager
3757                 .downloadFileGroup(testKey, null /*downloadConditions*/, noCustomValidation())
3758                 .get())
3759         .isEqualTo(updatedFileGroup);
3760     assertThat(downloadConditionsCaptor.getValue()).isEqualTo(downloadConditions);
3761   }
3762 
3763   @Test
testDownloadFileGroup_nonNullDownloadCondition()3764   public void testDownloadFileGroup_nonNullDownloadCondition() throws Exception {
3765     DownloadConditions downloadConditions =
3766         DownloadConditions.newBuilder()
3767             .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI)
3768             .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE)
3769             .build();
3770 
3771     // Write 1 group to the pending shared prefs.
3772     DataFileGroupInternal fileGroup =
3773         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
3774             .setDownloadConditions(downloadConditions)
3775             .build();
3776     writePendingFileGroup(testKey, fileGroup);
3777 
3778     writeSharedFiles(
3779         sharedFilesMetadata,
3780         fileGroup,
3781         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED));
3782 
3783     ArgumentCaptor<DownloadConditions> downloadConditionsCaptor =
3784         ArgumentCaptor.forClass(DownloadConditions.class);
3785     when(mockDownloader.startDownloading(
3786             any(String.class),
3787             any(GroupKey.class),
3788             anyInt(),
3789             anyLong(),
3790             any(String.class),
3791             any(Uri.class),
3792             any(String.class),
3793             anyInt(),
3794             downloadConditionsCaptor.capture(),
3795             isA(DownloaderCallbackImpl.class),
3796             anyInt(),
3797             anyList()))
3798         .then(
3799             new Answer<ListenableFuture<Void>>() {
3800               @Override
3801               public ListenableFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
3802                 writeSharedFiles(
3803                     sharedFilesMetadata,
3804                     fileGroup,
3805                     ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
3806                 return Futures.immediateVoidFuture();
3807               }
3808             });
3809 
3810     DownloadConditions downloadConditions2 =
3811         DownloadConditions.newBuilder()
3812             .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK)
3813             .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE)
3814             .build();
3815 
3816     DataFileGroupInternal updatedFileGroup =
3817         fileGroup.toBuilder()
3818             .setBookkeeping(
3819                 DataFileGroupBookkeeping.newBuilder()
3820                     .setDownloadStartedCount(1)
3821                     .setGroupDownloadStartedTimestampInMillis(1000L))
3822             .build();
3823 
3824     // downloadConditions2 will override the pendingGroup.downloadConditions
3825     assertThat(
3826             fileGroupManager
3827                 .downloadFileGroup(testKey, downloadConditions2, noCustomValidation())
3828                 .get())
3829         .isEqualTo(updatedFileGroup);
3830     assertThat(downloadConditionsCaptor.getValue()).isEqualTo(downloadConditions2);
3831   }
3832 
3833   @Test
testDownloadFileGroup_notFoundGroup()3834   public void testDownloadFileGroup_notFoundGroup() throws Exception {
3835     // Mock FileGroupsMetadata to test failure scenario.
3836     resetFileGroupManager(mockFileGroupsMetadata, sharedFileManager);
3837     // Can't find the group.
3838     ArgumentCaptor<GroupKey> groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class);
3839     when(mockFileGroupsMetadata.read(groupKeyCaptor.capture()))
3840         .thenReturn(Futures.immediateFuture(null));
3841 
3842     // Download not-found group will lead to failed future.
3843     ExecutionException exception =
3844         assertThrows(
3845             ExecutionException.class,
3846             () ->
3847                 fileGroupManager
3848                     .downloadFileGroup(testKey, null /*downloadConditions*/, noCustomValidation())
3849                     .get());
3850     assertThat(exception).hasCauseThat().isInstanceOf(DownloadException.class);
3851 
3852     // Make sure that file group manager attempted to read both pending key and downloaded key.
3853     assertThat(groupKeyCaptor.getAllValues())
3854         .containsAtLeast(getPendingKey(testKey), getDownloadedKey(testKey));
3855   }
3856 
3857   @Test
testDownloadFileGroup_downloadStartedTimestampAbsent()3858   public void testDownloadFileGroup_downloadStartedTimestampAbsent() throws Exception {
3859     // Write 1 group to the pending shared prefs.
3860     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
3861     fileGroup =
3862         fileGroup.toBuilder()
3863             .setOwnerPackage(context.getPackageName())
3864             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3865             .setTrafficTag(TRAFFIC_TAG)
3866             .build();
3867     writePendingFileGroup(testKey, fileGroup);
3868 
3869     writeSharedFiles(
3870         sharedFilesMetadata,
3871         fileGroup,
3872         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
3873 
3874     fileGroupManager
3875         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
3876         .get();
3877 
3878     DataFileGroupBookkeeping bookkeeping = readDownloadedFileGroup(testKey).getBookkeeping();
3879     assertThat(bookkeeping.hasGroupDownloadStartedTimestampInMillis()).isTrue();
3880     // Make sure that the download started timestamp is set to current time.
3881     assertThat(bookkeeping.getGroupDownloadStartedTimestampInMillis())
3882         .isEqualTo(testClock.currentTimeMillis());
3883     // Make sure that the download started count is accumulated.
3884     assertThat(bookkeeping.getDownloadStartedCount()).isEqualTo(1);
3885 
3886     verify(mockLogger)
3887         .logEventSampled(
3888             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
3889             fileGroup.getGroupName(),
3890             fileGroup.getFileGroupVersionNumber(),
3891             /* buildId= */ 0,
3892             /* variantId= */ "");
3893   }
3894 
3895   @Test
testDownloadFileGroup_downloadStartedTimestampPresent()3896   public void testDownloadFileGroup_downloadStartedTimestampPresent() throws Exception {
3897     // Write 1 group to the pending shared prefs.
3898     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
3899 
3900     fileGroup =
3901         fileGroup.toBuilder()
3902             .setOwnerPackage(context.getPackageName())
3903             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3904             .setTrafficTag(TRAFFIC_TAG)
3905             .setBookkeeping(
3906                 DataFileGroupBookkeeping.newBuilder()
3907                     .setGroupDownloadStartedTimestampInMillis(123456)
3908                     .setDownloadStartedCount(2))
3909             .build();
3910     writePendingFileGroup(testKey, fileGroup);
3911 
3912     writeSharedFiles(
3913         sharedFilesMetadata,
3914         fileGroup,
3915         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
3916 
3917     fileGroupManager
3918         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
3919         .get();
3920 
3921     DataFileGroupBookkeeping bookkeeping = readDownloadedFileGroup(testKey).getBookkeeping();
3922     assertThat(bookkeeping.hasGroupDownloadStartedTimestampInMillis()).isTrue();
3923     // Make sure that the download started timestamp is not changed.
3924     assertThat(bookkeeping.getGroupDownloadStartedTimestampInMillis()).isEqualTo(123456);
3925     // Make sure that the download started count is accumulated.
3926     assertThat(bookkeeping.getDownloadStartedCount()).isEqualTo(3);
3927 
3928     verify(mockLogger, never())
3929         .logEventSampled(
3930             eq(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED),
3931             any(String.class),
3932             anyInt(),
3933             anyLong(),
3934             any(String.class));
3935   }
3936 
3937   @Test
testDownloadFileGroup_updateBookkeepingOnDownloadFailed()3938   public void testDownloadFileGroup_updateBookkeepingOnDownloadFailed() throws Exception {
3939     // Mock FileGroupsMetadata to test failure scenario.
3940     resetFileGroupManager(mockFileGroupsMetadata, sharedFileManager);
3941     // Write 1 group to the pending shared prefs.
3942     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
3943     fileGroup =
3944         fileGroup.toBuilder()
3945             .setOwnerPackage(context.getPackageName())
3946             .setDownloadConditions(DownloadConditions.getDefaultInstance())
3947             .setTrafficTag(TRAFFIC_TAG)
3948             .build();
3949     GroupKey pendingKey = testKey.toBuilder().setDownloaded(false).build();
3950     when(mockFileGroupsMetadata.read(pendingKey)).thenReturn(Futures.immediateFuture(fileGroup));
3951 
3952     // All files are downloaded.
3953     writeSharedFiles(
3954         sharedFilesMetadata,
3955         fileGroup,
3956         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
3957 
3958     ArgumentCaptor<DataFileGroupInternal> fileGroupCaptor =
3959         ArgumentCaptor.forClass(DataFileGroupInternal.class);
3960     when(mockFileGroupsMetadata.write(eq(pendingKey), fileGroupCaptor.capture()))
3961         .thenReturn(Futures.immediateFuture(false));
3962 
3963     ExecutionException executionException =
3964         assertThrows(
3965             ExecutionException.class,
3966             () ->
3967                 fileGroupManager
3968                     .downloadFileGroup(
3969                         testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
3970                     .get());
3971     assertThat(executionException).hasCauseThat().isInstanceOf(DownloadException.class);
3972     DownloadException downloadException = (DownloadException) executionException.getCause();
3973     assertThat(downloadException).hasCauseThat().isInstanceOf(IOException.class);
3974     assertThat(downloadException.getDownloadResultCode())
3975         .isEqualTo(DownloadResultCode.UNABLE_TO_UPDATE_GROUP_METADATA_ERROR);
3976 
3977     DataFileGroupBookkeeping bookkeeping = fileGroupCaptor.getValue().getBookkeeping();
3978     assertThat(bookkeeping.hasGroupDownloadStartedTimestampInMillis()).isTrue();
3979     assertThat(bookkeeping.getGroupDownloadStartedTimestampInMillis())
3980         .isEqualTo(testClock.currentTimeMillis());
3981 
3982     verify(mockLogger, never())
3983         .logEventSampled(
3984             eq(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED),
3985             any(String.class),
3986             anyInt(),
3987             anyLong(),
3988             any(String.class));
3989     verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
3990   }
3991 
3992   @Test
testDownloadToBeSharedPendingGroup_success_lowSdk_notShared()3993   public void testDownloadToBeSharedPendingGroup_success_lowSdk_notShared() throws Exception {
3994     ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.R - 1);
3995     // Write 1 group to the pending shared prefs.
3996     DataFileGroupInternal fileGroup =
3997         createDataFileGroup(
3998             TEST_GROUP,
3999             /* fileCount= */ 0,
4000             /* downloadAttemptCount= */ 3,
4001             /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L);
4002     ExtraHttpHeader extraHttpHeader =
4003         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4004 
4005     fileGroup =
4006         fileGroup.toBuilder()
4007             .setOwnerPackage(context.getPackageName())
4008             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4009             .setTrafficTag(TRAFFIC_TAG)
4010             .addGroupExtraHttpHeaders(extraHttpHeader)
4011             .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0))
4012             .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1))
4013             .build();
4014     writePendingFileGroup(testKey, fileGroup);
4015 
4016     writeSharedFiles(
4017         sharedFilesMetadata,
4018         fileGroup,
4019         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
4020 
4021     fileGroupManager
4022         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4023         .get();
4024 
4025     // Verify that pending key is removed if download is complete.
4026     assertThat(readPendingFileGroup(testKey)).isNull();
4027 
4028     // Verify that downloaded key is written into metadata if download is complete.
4029     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4030     verify(mockLogger)
4031         .logEventSampled(
4032             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4033             TEST_GROUP,
4034             /* fileGroupVersionNumber= */ 0,
4035             /* buildId= */ 0,
4036             /* variantId= */ "");
4037     verify(mockLogger)
4038         .logMddDownloadResult(
4039             MddDownloadResult.Code.SUCCESS,
4040             createFileGroupDetails(fileGroup)
4041                 .setOwnerPackage(context.getPackageName())
4042                 .clearFileCount()
4043                 .build());
4044 
4045     verify(mockLogger)
4046         .logMddDownloadLatency(
4047             createFileGroupDetails(fileGroup).build(),
4048             createMddDownloadLatency(
4049                 /* downloadAttemptCount= */ 4,
4050                 /* downloadLatencyMs= */ 0L,
4051                 /* totalLatencyMs= */ 500L));
4052 
4053     // exists only called once in tryToShareBeforeDownload
4054     verify(mockBackend, never()).exists(any());
4055     // openForWrite is called in tryToShareBeforeDownload for copying the file and acquiring the
4056     // lease.
4057     verify(mockBackend, never()).openForWrite(any());
4058   }
4059 
4060   @Test
testDownloadFileGroup_success_oneFileAndroidSharedAndDownloaded()4061   public void testDownloadFileGroup_success_oneFileAndroidSharedAndDownloaded() throws Exception {
4062     // Write 1 group to the pending shared prefs.
4063     DataFileGroupInternal fileGroup =
4064         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
4065             .setVariantId("test-variant")
4066             .setBuildId(10)
4067             .build();
4068     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
4069     ExtraHttpHeader extraHttpHeader =
4070         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4071 
4072     fileGroup =
4073         fileGroup.toBuilder()
4074             .setOwnerPackage(context.getPackageName())
4075             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4076             .setTrafficTag(TRAFFIC_TAG)
4077             .addGroupExtraHttpHeaders(extraHttpHeader)
4078             .build();
4079     writePendingFileGroup(testKey, fileGroup);
4080 
4081     writeSharedFiles(
4082         sharedFilesMetadata,
4083         fileGroup,
4084         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE),
4085         /* androidShared */ ImmutableList.of(true, false));
4086 
4087     SharedFile file0 = sharedFileManager.getSharedFile(keys[0]).get();
4088     SharedFile file1 = sharedFileManager.getSharedFile(keys[1]).get();
4089     Uri blobUri = DirectoryUtil.getBlobUri(context, file0.getAndroidSharingChecksum());
4090     Uri leaseUri =
4091         DirectoryUtil.getBlobStoreLeaseUri(context, file0.getAndroidSharingChecksum(), 0);
4092 
4093     fileGroupManager
4094         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4095         .get();
4096 
4097     // Verify that pending key is removed if download is complete.
4098     assertThat(readPendingFileGroup(testKey)).isNull();
4099 
4100     // Verify that downloaded key is written into metadata if download is complete.
4101     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4102     verify(mockLogger)
4103         .logEventSampled(
4104             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4105             TEST_GROUP,
4106             /* fileGroupVersionNumber= */ 0,
4107             /* buildId= */ 10,
4108             /* variantId= */ "test-variant");
4109 
4110     verify(mockBackend, never()).exists(blobUri);
4111     verify(mockBackend, never()).openForWrite(blobUri);
4112     verify(mockBackend, never()).openForWrite(leaseUri);
4113 
4114     assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(file0);
4115     assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(file1);
4116 
4117     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
4118 
4119     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
4120     Void mddAndroidSharingLog = null;
4121     Void expectedLog = null;
4122     assertThat(mddAndroidSharingLog).isEqualTo(expectedLog);
4123   }
4124 
4125   @Test
testDownloadFileGroup_pending_oneBlobExistsBeforeDownload()4126   public void testDownloadFileGroup_pending_oneBlobExistsBeforeDownload() throws Exception {
4127     // Write 1 group to the pending shared prefs.
4128     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0);
4129     ExtraHttpHeader extraHttpHeader =
4130         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4131 
4132     fileGroup =
4133         fileGroup.toBuilder()
4134             .setOwnerPackage(context.getPackageName())
4135             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4136             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4137             .setTrafficTag(TRAFFIC_TAG)
4138             .addGroupExtraHttpHeaders(extraHttpHeader)
4139             .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0))
4140             .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1))
4141             .build();
4142 
4143     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
4144     writePendingFileGroup(testKey, fileGroup);
4145 
4146     writeSharedFiles(
4147         sharedFilesMetadata,
4148         fileGroup,
4149         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED));
4150 
4151     // File that can be shared
4152     DataFile file = fileGroup.getFile(0);
4153     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
4154     Uri leaseUri =
4155         DirectoryUtil.getBlobStoreLeaseUri(
4156             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
4157     // The file is available in the blob storage
4158     when(mockBackend.exists(blobUri)).thenReturn(true);
4159 
4160     // First file's download succeeds
4161     Uri onDeviceuri =
4162         DirectoryUtil.getOnDeviceUri(
4163             context,
4164             keys[0].getAllowedReaders(),
4165             file.getFileId(),
4166             keys[0].getChecksum(),
4167             mockSilentFeedback,
4168             /* instanceId= */ Optional.absent(),
4169             /* androidShared= */ false);
4170     fileDownloadSucceeds(keys[0], onDeviceuri);
4171 
4172     // Second file's download succeeds
4173     onDeviceuri =
4174         DirectoryUtil.getOnDeviceUri(
4175             context,
4176             keys[1].getAllowedReaders(),
4177             fileGroup.getFile(1).getFileId(),
4178             keys[1].getChecksum(),
4179             mockSilentFeedback,
4180             /* instanceId= */ Optional.absent(),
4181             /* androidShared= */ false);
4182     fileDownloadSucceeds(keys[1], onDeviceuri);
4183 
4184     fileGroupManager
4185         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4186         .get();
4187 
4188     // Verify that the pending group is not part of pending groups prefs.
4189     assertThat(readPendingFileGroup(testKey)).isNull();
4190 
4191     // Verify that the downloaded group is still part of downloaded groups prefs.
4192     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4193 
4194     verify(mockLogger)
4195         .logEventSampled(
4196             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4197             TEST_GROUP,
4198             /* fileGroupVersionNumber= */ 0,
4199             /* buildId= */ 0,
4200             /* variantId= */ "");
4201 
4202     verify(mockBackend).exists(blobUri);
4203     // openForWrite is called only once in tryToShareBeforeDownload for acquiring the lease.
4204     verify(mockBackend, never()).openForWrite(blobUri);
4205     verify(mockBackend).openForWrite(leaseUri);
4206 
4207     SharedFile expectedSharedFile0 =
4208         SharedFile.newBuilder()
4209             .setFileName("android_shared_sha256_1230")
4210             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4211             .setAndroidShared(true)
4212             .setAndroidSharingChecksum("sha256_1230")
4213             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4214             .build();
4215     SharedFile expectedSharedFile1 =
4216         SharedFile.newBuilder()
4217             .setFileName(fileGroup.getFile(1).getFileId())
4218             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4219             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4220             .build();
4221     assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0);
4222     assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1);
4223 
4224     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
4225 
4226     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
4227     Void mddAndroidSharingLog = null;
4228     Void expectedLog = null;
4229     assertThat(mddAndroidSharingLog).isEqualTo(expectedLog);
4230   }
4231 
4232   @Test
testDownloadFileGroup_pending_oneBlobExistsAfterDownload()4233   public void testDownloadFileGroup_pending_oneBlobExistsAfterDownload() throws Exception {
4234     // Write 1 group to the pending shared prefs.
4235     DataFileGroupInternal tmpFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0);
4236     ExtraHttpHeader extraHttpHeader =
4237         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4238 
4239     final DataFileGroupInternal fileGroup =
4240         tmpFileGroup.toBuilder()
4241             .setOwnerPackage(context.getPackageName())
4242             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4243             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4244             .setTrafficTag(TRAFFIC_TAG)
4245             .addGroupExtraHttpHeaders(extraHttpHeader)
4246             .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0))
4247             .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1))
4248             .build();
4249 
4250     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
4251     writePendingFileGroup(testKey, fileGroup);
4252 
4253     writeSharedFiles(
4254         sharedFilesMetadata,
4255         fileGroup,
4256         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.DOWNLOAD_COMPLETE));
4257 
4258     // File that can be shared
4259     DataFile file = fileGroup.getFile(0);
4260     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
4261     Uri leaseUri =
4262         DirectoryUtil.getBlobStoreLeaseUri(
4263             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
4264 
4265     // The file isn't available in the blob storage when tryToShareBeforeDownload is called
4266     when(mockBackend.exists(blobUri)).thenReturn(false);
4267 
4268     simulateDownload(file, file.getFileId());
4269     Uri onDeviceuri =
4270         DirectoryUtil.getOnDeviceUri(
4271             context,
4272             keys[0].getAllowedReaders(),
4273             file.getFileId(),
4274             keys[0].getChecksum(),
4275             mockSilentFeedback,
4276             /* instanceId= */ Optional.absent(),
4277             /* androidShared= */ false);
4278     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4279 
4280     when(mockDownloader.startDownloading(
4281             any(String.class),
4282             any(GroupKey.class),
4283             anyInt(),
4284             anyLong(),
4285             any(String.class),
4286             eq(onDeviceuri),
4287             any(String.class),
4288             anyInt(),
4289             any(DownloadConditions.class),
4290             isA(DownloaderCallbackImpl.class),
4291             anyInt(),
4292             anyList()))
4293         .then(
4294             new Answer<ListenableFuture<Void>>() {
4295               @Override
4296               public ListenableFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
4297                 // The file now exists in the shared storage
4298                 when(mockBackend.exists(blobUri)).thenReturn(true);
4299                 writeSharedFiles(
4300                     sharedFilesMetadata,
4301                     fileGroup,
4302                     ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
4303                 return Futures.immediateVoidFuture();
4304               }
4305             });
4306 
4307     fileGroupManager
4308         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4309         .get();
4310 
4311     // Verify that pending key is removed if download is complete.
4312     assertThat(readPendingFileGroup(testKey)).isNull();
4313 
4314     // Verify that downloaded key is written into metadata if download is complete.
4315     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4316     verify(mockLogger)
4317         .logEventSampled(
4318             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4319             TEST_GROUP,
4320             /* fileGroupVersionNumber= */ 0,
4321             /* buildId= */ 0,
4322             /* variantId= */ "");
4323 
4324     // exists called once in tryToShareBeforeDownload and once in tryToShareAfterDownload
4325     verify(mockBackend, times(2)).exists(blobUri);
4326     // openForWrite is called only once in tryToShareAfterDownload for acquiring the lease.
4327     verify(mockBackend, never()).openForWrite(blobUri);
4328     verify(mockBackend).openForWrite(leaseUri);
4329 
4330     SharedFile expectedSharedFile0 =
4331         SharedFile.newBuilder()
4332             .setFileName("android_shared_sha256_1230")
4333             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4334             .setAndroidShared(true)
4335             .setAndroidSharingChecksum("sha256_1230")
4336             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4337             .build();
4338     SharedFile expectedSharedFile1 =
4339         SharedFile.newBuilder()
4340             .setFileName(fileGroup.getFile(1).getFileId())
4341             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4342             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4343             .build();
4344     assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0);
4345     assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1);
4346 
4347     // Local copy has not been deleted.
4348     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4349 
4350     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
4351 
4352     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
4353     Void mddAndroidSharingLog = null;
4354     Void expectedLog = null;
4355     assertThat(mddAndroidSharingLog).isEqualTo(expectedLog);
4356   }
4357 
4358   @Test
testDownloadFileGroup_success_oneFileCanBeCopiedBeforeDownload()4359   public void testDownloadFileGroup_success_oneFileCanBeCopiedBeforeDownload() throws Exception {
4360     File tempFile = folder.newFile("blobFile");
4361     // Write 1 group to the pending shared prefs.
4362     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0);
4363     ExtraHttpHeader extraHttpHeader =
4364         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4365 
4366     fileGroup =
4367         fileGroup.toBuilder()
4368             .setOwnerPackage(context.getPackageName())
4369             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4370             .setTrafficTag(TRAFFIC_TAG)
4371             .addGroupExtraHttpHeaders(extraHttpHeader)
4372             .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0))
4373             .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1))
4374             .build();
4375     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
4376     writePendingFileGroup(testKey, fileGroup);
4377 
4378     // All files are downloaded.
4379     writeSharedFiles(
4380         sharedFilesMetadata,
4381         fileGroup,
4382         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
4383 
4384     DataFile file = fileGroup.getFile(0);
4385     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
4386     Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri(context, file.getAndroidSharingChecksum(), 0);
4387     // The file isn't available yet in the blob storage
4388     when(mockBackend.exists(blobUri)).thenReturn(false);
4389     // File that can be copied to the blob storage
4390     when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile));
4391 
4392     File onDeviceFile = simulateDownload(file, fileGroup.getFile(0).getFileId());
4393     Uri onDeviceuri =
4394         DirectoryUtil.getOnDeviceUri(
4395             context,
4396             keys[0].getAllowedReaders(),
4397             fileGroup.getFile(0).getFileId(),
4398             keys[0].getChecksum(),
4399             mockSilentFeedback,
4400             /* instanceId= */ Optional.absent(),
4401             /* androidShared= */ false);
4402     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4403 
4404     fileGroupManager
4405         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4406         .get();
4407 
4408     // Verify that pending key is removed if download is complete.
4409     assertThat(readPendingFileGroup(testKey)).isNull();
4410 
4411     // Verify that downloaded key is written into metadata if download is complete.
4412     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4413     verify(mockLogger)
4414         .logEventSampled(
4415             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4416             TEST_GROUP,
4417             /* fileGroupVersionNumber= */ 0,
4418             /* buildId= */ 0,
4419             /* variantId= */ "");
4420 
4421     // exists only called once in tryToShareBeforeDownload
4422     verify(mockBackend).exists(blobUri);
4423     // openForWrite is called in tryToShareBeforeDownload for copying the file and acquiring the
4424     // lease.
4425     verify(mockBackend).openForWrite(blobUri);
4426     verify(mockBackend).openForWrite(leaseUri);
4427 
4428     SharedFile expectedSharedFile0 =
4429         SharedFile.newBuilder()
4430             .setFileName("android_shared_sha256_1230")
4431             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4432             .setAndroidShared(true)
4433             .setAndroidSharingChecksum("sha256_1230")
4434             .setMaxExpirationDateSecs(0)
4435             .build();
4436     SharedFile expectedSharedFile1 =
4437         SharedFile.newBuilder()
4438             .setFileName(fileGroup.getFile(1).getFileId())
4439             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4440             .build();
4441     assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0);
4442     assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1);
4443 
4444     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4445     onDeviceFile.delete();
4446 
4447     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
4448 
4449     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
4450     Void mddAndroidSharingLog = null;
4451     Void expectedLog = null;
4452     assertThat(mddAndroidSharingLog).isEqualTo(expectedLog);
4453   }
4454 
4455   @Test
testDownloadFileGroup_oneFileCanBeCopiedAfterDownload()4456   public void testDownloadFileGroup_oneFileCanBeCopiedAfterDownload() throws Exception {
4457     File tempFile = folder.newFile("blobFile");
4458     // Write 1 group to the pending shared prefs.
4459     DataFileGroupInternal tmpFfileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0);
4460     ExtraHttpHeader extraHttpHeader =
4461         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4462 
4463     final DataFileGroupInternal fileGroup =
4464         tmpFfileGroup.toBuilder()
4465             .setOwnerPackage(context.getPackageName())
4466             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4467             .setTrafficTag(TRAFFIC_TAG)
4468             .addGroupExtraHttpHeaders(extraHttpHeader)
4469             .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0))
4470             .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1))
4471             .build();
4472     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
4473     writePendingFileGroup(testKey, fileGroup);
4474 
4475     writeSharedFiles(
4476         sharedFilesMetadata,
4477         fileGroup,
4478         ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.DOWNLOAD_COMPLETE));
4479 
4480     // File that can be copied to the blob storage
4481     DataFile file = fileGroup.getFile(0);
4482     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
4483     Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri(context, file.getAndroidSharingChecksum(), 0);
4484     // The file is available in the blob storage
4485     when(mockBackend.exists(blobUri)).thenReturn(false);
4486 
4487     simulateDownload(file, file.getFileId());
4488     Uri onDeviceuri =
4489         DirectoryUtil.getOnDeviceUri(
4490             context,
4491             keys[0].getAllowedReaders(),
4492             file.getFileId(),
4493             keys[0].getChecksum(),
4494             mockSilentFeedback,
4495             /* instanceId= */ Optional.absent(),
4496             /* androidShared= */ false);
4497     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4498 
4499     when(mockDownloader.startDownloading(
4500             any(String.class),
4501             any(GroupKey.class),
4502             anyInt(),
4503             anyLong(),
4504             any(String.class),
4505             eq(onDeviceuri),
4506             any(String.class),
4507             anyInt(),
4508             any(DownloadConditions.class),
4509             isA(DownloaderCallbackImpl.class),
4510             anyInt(),
4511             anyList()))
4512         .then(
4513             new Answer<ListenableFuture<Void>>() {
4514               @Override
4515               public ListenableFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
4516                 // The file will be copied in tryToShareAfterDownload
4517                 when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile));
4518                 writeSharedFiles(
4519                     sharedFilesMetadata,
4520                     fileGroup,
4521                     ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
4522                 return Futures.immediateVoidFuture();
4523               }
4524             });
4525 
4526     fileGroupManager
4527         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4528         .get();
4529 
4530     // Verify that pending key is removed if download is complete.
4531     assertThat(readPendingFileGroup(testKey)).isNull();
4532 
4533     // Verify that downloaded key is written into metadata if download is complete.
4534     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4535     verify(mockLogger)
4536         .logEventSampled(
4537             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4538             TEST_GROUP,
4539             /* fileGroupVersionNumber= */ 0,
4540             /* buildId= */ 0,
4541             /* variantId= */ "");
4542 
4543     // exists only called once in tryToShareBeforeDownload, once in tryToShareAfterDownload
4544     verify(mockBackend, times(2)).exists(blobUri);
4545     //  File copied once in tryToShareAfterDownload
4546     verify(mockBackend).openForWrite(blobUri);
4547     verify(mockBackend).openForWrite(leaseUri);
4548 
4549     SharedFile expectedSharedFile0 =
4550         SharedFile.newBuilder()
4551             .setFileName("android_shared_sha256_1230")
4552             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4553             .setAndroidShared(true)
4554             .setAndroidSharingChecksum("sha256_1230")
4555             .setMaxExpirationDateSecs(0)
4556             .build();
4557     SharedFile expectedSharedFile1 =
4558         SharedFile.newBuilder()
4559             .setFileName(fileGroup.getFile(1).getFileId())
4560             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4561             .build();
4562     assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0);
4563     assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1);
4564 
4565     // Local copy has not been deleted.
4566     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4567 
4568     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
4569 
4570     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
4571     Void mddAndroidSharingLog = null;
4572     Void expectedLog = null;
4573     assertThat(mddAndroidSharingLog).isEqualTo(expectedLog);
4574   }
4575 
4576   @Test
testDownloadFileGroup_nonToBeSharedFile_neverShared()4577   public void testDownloadFileGroup_nonToBeSharedFile_neverShared() throws Exception {
4578     // Write 1 group to the pending shared prefs.
4579     DataFileGroupInternal tmpFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
4580     ExtraHttpHeader extraHttpHeader =
4581         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4582 
4583     final DataFileGroupInternal fileGroup =
4584         tmpFileGroup.toBuilder()
4585             .setOwnerPackage(context.getPackageName())
4586             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4587             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4588             .setTrafficTag(TRAFFIC_TAG)
4589             .addGroupExtraHttpHeaders(extraHttpHeader)
4590             .build();
4591     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
4592     writePendingFileGroup(testKey, fileGroup);
4593 
4594     writeSharedFiles(sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED));
4595 
4596     DataFile file = fileGroup.getFile(0);
4597     File onDeviceFile = simulateDownload(file, file.getFileId());
4598     Uri onDeviceuri =
4599         DirectoryUtil.getOnDeviceUri(
4600             context,
4601             keys[0].getAllowedReaders(),
4602             file.getFileId(),
4603             keys[0].getChecksum(),
4604             mockSilentFeedback,
4605             /* instanceId= */ Optional.absent(),
4606             /* androidShared= */ false);
4607     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4608 
4609     fileDownloadSucceeds(keys[0], onDeviceuri);
4610 
4611     fileGroupManager
4612         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4613         .get();
4614 
4615     // Verify that pending key is removed if download is complete.
4616     assertThat(readPendingFileGroup(testKey)).isNull();
4617 
4618     // Verify that downloaded key is written into metadata if download is complete.
4619     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4620     verify(mockLogger)
4621         .logEventSampled(
4622             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4623             TEST_GROUP,
4624             /* fileGroupVersionNumber= */ 0,
4625             /* buildId= */ 0,
4626             /* variantId= */ "");
4627 
4628     verify(mockBackend, never()).exists(any());
4629     verify(mockBackend, never()).openForWrite(any());
4630 
4631     SharedFile expectedSharedFile =
4632         SharedFile.newBuilder()
4633             .setFileName(file.getFileId())
4634             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4635             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
4636             .build();
4637     assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile);
4638 
4639     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
4640     onDeviceFile.delete();
4641   }
4642 
4643   @Test
testDownloadFileGroup_androidSharingFails()4644   public void testDownloadFileGroup_androidSharingFails() throws Exception {
4645     // Write 1 group to the pending shared prefs.
4646     DataFileGroupInternal tmpFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0);
4647     ExtraHttpHeader extraHttpHeader =
4648         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
4649 
4650     final DataFileGroupInternal fileGroup =
4651         tmpFileGroup.toBuilder()
4652             .setOwnerPackage(context.getPackageName())
4653             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4654             .setTrafficTag(TRAFFIC_TAG)
4655             .addGroupExtraHttpHeaders(extraHttpHeader)
4656             .addFile(0, MddTestUtil.createDataFile(TEST_GROUP, 0))
4657             .addFile(1, MddTestUtil.createSharedDataFile(TEST_GROUP, 1))
4658             .build();
4659     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
4660     writePendingFileGroup(testKey, fileGroup);
4661 
4662     writeSharedFiles(
4663         sharedFilesMetadata,
4664         fileGroup,
4665         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
4666 
4667     // Second file fails with file storage I/O exception when called from tryToShareBeforeDownload
4668     // and tryToShareAfterDownload.
4669     DataFile file = fileGroup.getFile(1);
4670     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
4671     when(mockBackend.exists(blobUri)).thenThrow(new IOException());
4672 
4673     // Any error during sharing doesn't stop the download: the file will be stored locally.
4674     fileGroupManager
4675         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4676         .get();
4677 
4678     // Verify that pending key is removed if download is complete.
4679     assertThat(readPendingFileGroup(testKey)).isNull();
4680 
4681     // Verify that downloaded key is written into metadata if download is complete.
4682     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4683 
4684     verify(mockBackend, times(2)).exists(blobUri);
4685     verify(mockBackend, never()).openForWrite(any());
4686 
4687     SharedFile expectedSharedFile0 =
4688         SharedFile.newBuilder()
4689             .setFileName(fileGroup.getFile(0).getFileId())
4690             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
4691             .build();
4692     SharedFile expectedSharedFile1 =
4693         expectedSharedFile0.toBuilder().setFileName(fileGroup.getFile(1).getFileId()).build();
4694     assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0);
4695     assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1);
4696 
4697     verify(mockLogger)
4698         .logEventSampled(
4699             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4700             TEST_GROUP,
4701             /* fileGroupVersionNumber= */ 0,
4702             /* buildId= */ 0,
4703             /* variantId= */ "");
4704     verify(mockLogger)
4705         .logMddDownloadResult(
4706             MddDownloadResult.Code.SUCCESS,
4707             DataDownloadFileGroupStats.newBuilder()
4708                 .setFileGroupName(TEST_GROUP)
4709                 .setOwnerPackage(context.getPackageName())
4710                 .setFileGroupVersionNumber(0)
4711                 .setBuildId(0)
4712                 .setVariantId("")
4713                 .build());
4714 
4715     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
4716 
4717     verify(mockLogger, times(2))
4718         .logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
4719     Void mddAndroidSharingLogBeforeAndAfterDownload = null;
4720     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
4721         .containsExactly(
4722             mddAndroidSharingLogBeforeAndAfterDownload, mddAndroidSharingLogBeforeAndAfterDownload);
4723   }
4724 
4725   @Test
testDownloadFileGroup_skipsSideloadedFiles()4726   public void testDownloadFileGroup_skipsSideloadedFiles() throws Exception {
4727     // Create sideloaded group with normal file
4728     DataFileGroupInternal sideloadedGroup =
4729         DataFileGroupInternal.newBuilder()
4730             .setGroupName(TEST_GROUP)
4731             .addFile(
4732                 DataFile.newBuilder()
4733                     .setFileId("sideloaded_file")
4734                     .setUrlToDownload("file:/test")
4735                     .setChecksumType(DataFile.ChecksumType.NONE)
4736                     .build())
4737             .addFile(
4738                 DataFile.newBuilder()
4739                     .setFileId("normal_file")
4740                     .setUrlToDownload("https://url.to.download")
4741                     .setChecksumType(DataFile.ChecksumType.NONE)
4742                     .build())
4743             .build();
4744     NewFileKey normalFileKey =
4745         SharedFilesMetadata.createKeyFromDataFile(
4746             sideloadedGroup.getFile(1), sideloadedGroup.getAllowedReadersEnum());
4747 
4748     // Write group as pending since we are waiting on normal file
4749     writePendingFileGroup(testKey, sideloadedGroup);
4750     SharedFile normalSharedFile =
4751         SharedFile.newBuilder()
4752             .setFileName(sideloadedGroup.getFile(1).getFileId())
4753             .setFileStatus(FileStatus.SUBSCRIBED)
4754             .build();
4755     sharedFilesMetadata.write(normalFileKey, normalSharedFile).get();
4756 
4757     // Mock that download of normal file succeeds
4758     Uri normalFileUri =
4759         DirectoryUtil.getOnDeviceUri(
4760             context,
4761             normalFileKey.getAllowedReaders(),
4762             sideloadedGroup.getFile(1).getFileId(),
4763             sideloadedGroup.getFile(1).getChecksum(),
4764             mockSilentFeedback,
4765             /* instanceId= */ Optional.absent(),
4766             false);
4767     fileDownloadSucceeds(normalFileKey, normalFileUri);
4768 
4769     fileGroupManager
4770         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4771         .get();
4772 
4773     assertThat(readPendingFileGroup(testKey)).isNull();
4774     assertThat(readDownloadedFileGroup(testKey)).isNotNull();
4775 
4776     verify(mockDownloader)
4777         .startDownloading(
4778             eq(sideloadedGroup.getFile(1).getChecksum()),
4779             eq(testKey),
4780             anyInt(),
4781             anyLong(),
4782             any(String.class),
4783             eq(normalFileUri),
4784             eq(sideloadedGroup.getFile(1).getUrlToDownload()),
4785             anyInt(),
4786             any(),
4787             any(),
4788             anyInt(),
4789             anyList());
4790   }
4791 
4792   @Test
testDownloadFileGroup_whenMultipleVariantsExist_downloadsSpecifiedVariant()4793   public void testDownloadFileGroup_whenMultipleVariantsExist_downloadsSpecifiedVariant()
4794       throws Exception {
4795     GroupKey defaultGroupKey =
4796         GroupKey.newBuilder()
4797             .setGroupName(TEST_GROUP)
4798             .setOwnerPackage(context.getPackageName())
4799             .build();
4800     GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build();
4801 
4802     DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
4803     // Create EN with custom file ids so it doesn't overlap with the default file group.
4804     DataFileGroupInternal enFileGroup =
4805         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
4806             .addFile(MddTestUtil.createDataFile("en", 0))
4807             .addFile(MddTestUtil.createDataFile("en", 1))
4808             .build();
4809 
4810     writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup);
4811     writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup);
4812 
4813     writeSharedFiles(
4814         sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
4815 
4816     fileGroupManager
4817         .downloadFileGroup(
4818             defaultGroupKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4819         .get();
4820 
4821     // Verify that correct group was downloaded
4822     assertThat(readPendingFileGroup(defaultGroupKey)).isNull();
4823     assertThat(readDownloadedFileGroup(defaultGroupKey)).isNotNull();
4824 
4825     assertThat(readPendingFileGroup(enGroupKey)).isNotNull();
4826     assertThat(readDownloadedFileGroup(enGroupKey)).isNull();
4827 
4828     // Attempt to download en group and check that it is now downloaded
4829     writeSharedFiles(
4830         sharedFilesMetadata,
4831         enFileGroup,
4832         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
4833 
4834     fileGroupManager
4835         .downloadFileGroup(
4836             enGroupKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
4837         .get();
4838 
4839     // Verify that correct group was downloaded
4840     assertThat(readPendingFileGroup(defaultGroupKey)).isNull();
4841     assertThat(readDownloadedFileGroup(defaultGroupKey)).isNotNull();
4842 
4843     assertThat(readPendingFileGroup(enGroupKey)).isNull();
4844     assertThat(readDownloadedFileGroup(enGroupKey)).isNotNull();
4845   }
4846 
4847   @Test
testDownloadAllPendingGroups_onWifi()4848   public void testDownloadAllPendingGroups_onWifi() throws Exception {
4849     // Write 3 groups to the pending shared prefs.
4850     // MDD successfully downloaded filegroup1, partially downloaded filegroup2 and failed to
4851     // download filegroup3.
4852     DataFileGroupInternal fileGroup1 =
4853         createDataFileGroup(
4854             TEST_GROUP,
4855             /* fileCount= */ 2,
4856             /* downloadAttemptCount= */ 7,
4857             /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L);
4858     fileGroup1 =
4859         fileGroup1.toBuilder()
4860             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4861             .build();
4862     writePendingFileGroup(testKey, fileGroup1);
4863     // All files are downloaded.
4864     writeSharedFiles(
4865         sharedFilesMetadata,
4866         fileGroup1,
4867         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
4868 
4869     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
4870     fileGroup2 =
4871         fileGroup2.toBuilder()
4872             .setDownloadConditions(DownloadConditions.getDefaultInstance())
4873             .build();
4874     writePendingFileGroup(testKey2, fileGroup2);
4875     // Not all files are downloaded.
4876     writeSharedFiles(
4877         sharedFilesMetadata,
4878         fileGroup2,
4879         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS));
4880 
4881     GroupKey expectedKey2 = testKey2.toBuilder().setDownloaded(false).build();
4882     // The file status isn't changed to DOWNLOAD_COMPLETE, it remains DOWNLOAD_IN_PROGRESS.
4883     //  An UNKNOWN_ERROR is logged.
4884     when(mockDownloader.getInProgressFuture(any(String.class), any(Uri.class)))
4885         .thenReturn(Futures.immediateFuture(Optional.of(Futures.immediateVoidFuture())));
4886 
4887     DataFileGroupInternal tmpFileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2);
4888     final DataFileGroupInternal fileGroup3 =
4889         tmpFileGroup3.toBuilder()
4890             .setDownloadConditions(
4891                 DownloadConditions.newBuilder().setDownloadFirstOnWifiPeriodSecs(1000000))
4892             .build();
4893     writePendingFileGroup(testKey3, fileGroup3);
4894     // Not all files are downloaded.
4895     writeSharedFiles(
4896         sharedFilesMetadata,
4897         fileGroup3,
4898         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_FAILED));
4899 
4900     GroupKey expectedKey3 = testKey3.toBuilder().setDownloaded(false).build();
4901     // One file fails, status is DOWNLOAD_FAILED but the downloader returns an
4902     // immediateVoidFuture. An UNKNOWN_ERROR is logged.
4903     when(mockDownloader.startDownloading(
4904             any(String.class),
4905             eq(expectedKey3),
4906             anyInt(),
4907             anyLong(),
4908             any(String.class),
4909             any(Uri.class),
4910             any(String.class),
4911             anyInt(),
4912             any(DownloadConditions.class),
4913             isA(DownloaderCallbackImpl.class),
4914             anyInt(),
4915             anyList()))
4916         .then(
4917             new Answer<ListenableFuture<Void>>() {
4918               @Override
4919               public ListenableFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
4920                 writeSharedFiles(
4921                     sharedFilesMetadata,
4922                     fileGroup3,
4923                     ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_FAILED));
4924                 return Futures.immediateVoidFuture();
4925               }
4926             });
4927 
4928     fileGroupManager.scheduleAllPendingGroupsForDownload(true, noCustomValidation()).get();
4929 
4930     verify(mockLogger)
4931         .logMddDownloadLatency(
4932             createFileGroupDetails(fileGroup1).build(),
4933             createMddDownloadLatency(
4934                 /* downloadAttemptCount= */ 8,
4935                 /* downloadLatencyMs= */ 0L,
4936                 /* totalLatencyMs= */ 500L));
4937     verify(mockLogger)
4938         .logEventSampled(
4939             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4940             TEST_GROUP_2,
4941             /* fileGroupVersionNumber= */ 0,
4942             /* buildId= */ 0,
4943             /* variantId= */ "");
4944     verify(mockLogger)
4945         .logEventSampled(
4946             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
4947             TEST_GROUP_3,
4948             /* fileGroupVersionNumber= */ 0,
4949             /* buildId= */ 0,
4950             /* variantId= */ "");
4951 
4952     // Make sure that the successful download of fileGroup1, the failed downloads of fileGroup2 and
4953     // fileGroup3 are all logged to clearcut.
4954     verify(mockLogger)
4955         .logMddDownloadResult(
4956             MddDownloadResult.Code.SUCCESS,
4957             createFileGroupDetails(fileGroup1)
4958                 .setOwnerPackage(context.getPackageName())
4959                 .clearFileCount()
4960                 .build());
4961     verify(mockLogger)
4962         .logMddDownloadResult(
4963             MddDownloadResult.Code.UNKNOWN_ERROR,
4964             createFileGroupDetails(fileGroup2)
4965                 .setOwnerPackage(context.getPackageName())
4966                 .clearFileCount()
4967                 .build());
4968 
4969     verify(mockLogger)
4970         .logMddDownloadResult(
4971             MddDownloadResult.Code.UNKNOWN_ERROR,
4972             createFileGroupDetails(fileGroup3)
4973                 .setOwnerPackage(context.getPackageName())
4974                 .clearFileCount()
4975                 .build());
4976   }
4977 
4978   @Test
testDownloadAllPendingGroups_withoutWifi()4979   public void testDownloadAllPendingGroups_withoutWifi() throws Exception {
4980     // Write 2 groups to the pending shared prefs.
4981     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
4982     fileGroup1 =
4983         fileGroup1.toBuilder()
4984             .setDownloadConditions(
4985                 DownloadConditions.newBuilder()
4986                     .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK))
4987             .build();
4988     writePendingFileGroup(testKey, fileGroup1);
4989     writeSharedFiles(
4990         sharedFilesMetadata,
4991         fileGroup1,
4992         ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS));
4993 
4994     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 3);
4995     writePendingFileGroup(testKey2, fileGroup2);
4996     writeSharedFiles(
4997         sharedFilesMetadata,
4998         fileGroup2,
4999         ImmutableList.of(
5000             FileStatus.DOWNLOAD_IN_PROGRESS,
5001             FileStatus.DOWNLOAD_IN_PROGRESS,
5002             FileStatus.DOWNLOAD_IN_PROGRESS));
5003 
5004     fileGroupManager.scheduleAllPendingGroupsForDownload(false, noCustomValidation()).get();
5005 
5006     // Only the files in the first group will be downloaded.
5007     verify(mockDownloader, times(2)).getInProgressFuture(any(String.class), any(Uri.class));
5008 
5009     verifyNoMoreInteractions(mockDownloader);
5010   }
5011 
5012   @Test
testDownloadAllPendingGroups_wifiFirst_without_Wifi()5013   public void testDownloadAllPendingGroups_wifiFirst_without_Wifi() throws Exception {
5014     DataFileGroupInternal dataFileGroup =
5015         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5016             .setDownloadConditions(
5017                 DownloadConditions.newBuilder()
5018                     .setDeviceNetworkPolicy(
5019                         DeviceNetworkPolicy.DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK)
5020                     .setDownloadFirstOnWifiPeriodSecs(10))
5021             .build();
5022 
5023     testClock.set(1000);
5024 
5025     {
5026       // Check that pending groups contain the added file group.
5027       assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
5028       verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, 1000);
5029     }
5030 
5031     {
5032       // Set time so that it has not passed the wifi only period.
5033       testClock.set(2000);
5034       fileGroupManager.scheduleAllPendingGroupsForDownload(false, noCustomValidation()).get();
5035     }
5036 
5037     {
5038       // Set time so that it has passed the wifi only period.
5039       testClock.set(2000 + 10 * 1000);
5040       ArgumentCaptor<DownloadConditions> downloadConditionCaptor =
5041           ArgumentCaptor.forClass(DownloadConditions.class);
5042       when(mockDownloader.startDownloading(
5043               any(String.class),
5044               any(GroupKey.class),
5045               anyInt(),
5046               anyLong(),
5047               any(String.class),
5048               any(Uri.class),
5049               any(String.class),
5050               anyInt(),
5051               downloadConditionCaptor.capture(),
5052               isA(DownloaderCallbackImpl.class),
5053               anyInt(),
5054               anyList()))
5055           .thenReturn(Futures.immediateVoidFuture());
5056 
5057       fileGroupManager.scheduleAllPendingGroupsForDownload(false, noCustomValidation()).get();
5058 
5059       // verify that the group's DeviceNetworkPolicy changes to
5060       // DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK
5061       assertThat(downloadConditionCaptor.getValue().getDeviceNetworkPolicy())
5062           .isEqualTo(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK);
5063     }
5064   }
5065 
5066   @Test
testDownloadAllPendingGroups_startDownloadFails()5067   public void testDownloadAllPendingGroups_startDownloadFails() throws Exception {
5068     // Write 2 groups to the pending shared prefs.
5069     DataFileGroupInternal fileGroup1 =
5070         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5071             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5072             .build();
5073     writePendingFileGroup(testKey, fileGroup1);
5074     writeSharedFiles(
5075         sharedFilesMetadata,
5076         fileGroup1,
5077         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.SUBSCRIBED));
5078     NewFileKey[] keys1 = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup1);
5079 
5080     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
5081     writePendingFileGroup(testKey2, fileGroup2);
5082     writeSharedFiles(
5083         sharedFilesMetadata, fileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
5084 
5085     // Make the download call fail for one of the files in first group.
5086     Uri failingFileUri =
5087         DirectoryUtil.getOnDeviceUri(
5088             context,
5089             keys1[1].getAllowedReaders(),
5090             fileGroup1.getFile(1).getFileId(),
5091             fileGroup1.getFile(1).getChecksum(),
5092             mockSilentFeedback,
5093             /* instanceId= */ Optional.absent(),
5094             false);
5095     fileDownloadFails(keys1[1], failingFileUri, DownloadResultCode.LOW_DISK_ERROR);
5096 
5097     fileGroupManager.scheduleAllPendingGroupsForDownload(true, noCustomValidation()).get();
5098 
5099     verify(mockLogger)
5100         .logEventSampled(
5101             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
5102             TEST_GROUP,
5103             /* fileGroupVersionNumber= */ 0,
5104             /* buildId= */ 0,
5105             /* variantId= */ "");
5106     verify(mockLogger)
5107         .logEventSampled(
5108             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
5109             TEST_GROUP_2,
5110             /* fileGroupVersionNumber= */ 0,
5111             /* buildId= */ 0,
5112             /* variantId= */ "");
5113 
5114     verify(mockLogger)
5115         .logMddDownloadResult(
5116             MddDownloadResult.Code.LOW_DISK_ERROR,
5117             createFileGroupDetails(fileGroup1)
5118                 .clearFileCount()
5119                 .setOwnerPackage(context.getPackageName())
5120                 .build());
5121 
5122     verify(mockLogger)
5123         .logMddDownloadResult(
5124             MddDownloadResult.Code.SUCCESS,
5125             createFileGroupDetails(fileGroup2)
5126                 .clearFileCount()
5127                 .setOwnerPackage(context.getPackageName())
5128                 .build());
5129   }
5130 
5131   // case 1: the file is already shared in the blob storage.
5132   @Test
tryToShareBeforeDownload_alreadyShared()5133   public void tryToShareBeforeDownload_alreadyShared() throws Exception {
5134     DataFileGroupInternal fileGroup =
5135         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5136             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5137             .build();
5138 
5139     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5140     NewFileKey newFileKey =
5141         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5142     // Set the file metadata as already downloaded and shared
5143     SharedFile existingDownloadedSharedFile =
5144         SharedFile.newBuilder()
5145             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5146             .setFileName("")
5147             .setAndroidShared(true)
5148             .setAndroidSharingChecksum(file.getAndroidSharingChecksum())
5149             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5150             .build();
5151     sharedFilesMetadata.write(newFileKey, existingDownloadedSharedFile).get();
5152 
5153     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5154 
5155     verify(mockBackend, never()).exists(any());
5156     // openForWrite isn't called to update the lease because the current fileGroup's expiration date
5157     // is < maxExpirationDate.
5158     verify(mockBackend, never()).openForWrite(any());
5159 
5160     assertThat(sharedFileManager.getSharedFile(newFileKey).get())
5161         .isEqualTo(existingDownloadedSharedFile);
5162 
5163     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5164 
5165     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5166     Void mddAndroidSharingLog = null;
5167     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5168         .containsExactly(mddAndroidSharingLog);
5169     verifyNoMoreInteractions(mockLogger);
5170   }
5171 
5172   // case 2a: the to-be-shared file is available in the blob storage.
5173   @Test
tryToShareBeforeDownload_toBeSharedFile_blobExists()5174   public void tryToShareBeforeDownload_toBeSharedFile_blobExists() throws Exception {
5175     // Create a file group with expiration date smaller than the expiration date of the existing
5176     // SharedFile.
5177     DataFileGroupInternal fileGroup =
5178         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5179             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5180             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS - 1)
5181             .build();
5182 
5183     // Create a to-be-shared file
5184     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5185     NewFileKey newFileKey =
5186         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5187     // Set the file metadata as download pending and non shared
5188     SharedFile existingSharedFile =
5189         SharedFile.newBuilder()
5190             .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS)
5191             .setFileName("fileName")
5192             .setAndroidShared(false)
5193             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5194             .build();
5195 
5196     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5197 
5198     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5199     Uri leaseUri =
5200         DirectoryUtil.getBlobStoreLeaseUri(
5201             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
5202     // The file is available in the blob storage
5203     when(mockBackend.exists(blobUri)).thenReturn(true);
5204 
5205     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5206 
5207     // openForWrite is called only once for acquiring the lease.
5208     verify(mockBackend, never()).openForWrite(blobUri);
5209     verify(mockBackend).openForWrite(leaseUri);
5210     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5211     // Verify that the SharedFile retains the longest expiration date after the download.
5212     assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS);
5213     assertThat(sharedFile.getAndroidShared()).isTrue();
5214     assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri);
5215 
5216     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5217 
5218     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5219     Void mddAndroidSharingLog = null;
5220     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5221         .containsExactly(mddAndroidSharingLog);
5222     verifyNoMoreInteractions(mockLogger);
5223   }
5224 
5225   // case 3: the to-be-shared file is available in the local storage.
5226   @Test
tryToShareBeforeDownload_toBeSharedFile_canBeCopied()5227   public void tryToShareBeforeDownload_toBeSharedFile_canBeCopied() throws Exception {
5228     File tempFile = folder.newFile("blobFile");
5229     // Create a file group with expiration date bigger than the expiration date of the existing
5230     // SharedFile.
5231     DataFileGroupInternal fileGroup =
5232         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5233             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5234             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1)
5235             .build();
5236 
5237     // Create a to-be-shared file
5238     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5239     NewFileKey newFileKey =
5240         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5241     // Set the file metadata as downloaded and non shared
5242     SharedFile existingSharedFile =
5243         SharedFile.newBuilder()
5244             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5245             .setFileName("fileName")
5246             .setAndroidShared(false)
5247             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5248             .build();
5249     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5250 
5251     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5252     Uri leaseUri =
5253         DirectoryUtil.getBlobStoreLeaseUri(
5254             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS + 1);
5255     // The file isn't available in the blob storage
5256     when(mockBackend.exists(blobUri)).thenReturn(false);
5257     when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile));
5258 
5259     File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName());
5260     Uri onDeviceuri =
5261         DirectoryUtil.getOnDeviceUri(
5262             context,
5263             newFileKey.getAllowedReaders(),
5264             existingSharedFile.getFileName(),
5265             newFileKey.getChecksum(),
5266             mockSilentFeedback,
5267             /* instanceId= */ Optional.absent(),
5268             /* androidShared= */ false);
5269     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5270 
5271     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5272 
5273     // openForWrite is called once for writing the blob, once for acquiring the lease.
5274     verify(mockBackend).openForWrite(blobUri);
5275     verify(mockBackend).openForWrite(leaseUri);
5276 
5277     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5278     // Verify that the SharedFile has updated its expiration date after the download.
5279     assertThat(sharedFile.getMaxExpirationDateSecs())
5280         .isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS + 1);
5281     assertThat(sharedFile.getAndroidShared()).isTrue();
5282     assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri);
5283 
5284     // The local copy will be deleted in daily maintance
5285     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5286     onDeviceFile.delete();
5287 
5288     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5289 
5290     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5291 
5292     Void mddAndroidSharingLog = null;
5293     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5294         .containsExactly(mddAndroidSharingLog);
5295     verifyNoMoreInteractions(mockLogger);
5296   }
5297 
5298   // The file can't be shared and isn't available locally.
5299   @Test
tryToShareBeforeDownload_toBeSharedFile_cannotBeShared_neverDownloaded()5300   public void tryToShareBeforeDownload_toBeSharedFile_cannotBeShared_neverDownloaded()
5301       throws Exception {
5302     DataFileGroupInternal fileGroup =
5303         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5304             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5305             .build();
5306 
5307     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5308     NewFileKey newFileKey =
5309         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5310     SharedFile existingSharedFile =
5311         SharedFile.newBuilder()
5312             .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS)
5313             .setFileName("fileName")
5314             .setAndroidShared(false)
5315             .build();
5316     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5317 
5318     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5319     // The file isn't available in the blob storage
5320     when(mockBackend.exists(blobUri)).thenReturn(false);
5321 
5322     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5323 
5324     // We never acquire the lease nor update the max expiration date.
5325     verify(mockBackend).exists(blobUri);
5326     verify(mockBackend, never()).openForWrite(any());
5327 
5328     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5329     assertThat(sharedFile).isEqualTo(existingSharedFile);
5330 
5331     verifyNoInteractions(mockLogger);
5332   }
5333 
5334   // case 4: the non-to-be-shared file can't be shared and is available in the local storage.
5335   @Test
tryToShareBeforeDownload_nonToBeSharedFile_alreadyDownloaded_cannotBeShared()5336   public void tryToShareBeforeDownload_nonToBeSharedFile_alreadyDownloaded_cannotBeShared()
5337       throws Exception {
5338     DataFileGroupInternal fileGroup =
5339         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5340             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5341             .build();
5342     // non-to-be-shared file with ChecksumType SHA1
5343     DataFile file = MddTestUtil.createDataFile("fileId", 0);
5344     NewFileKey newFileKey =
5345         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5346     // Set the file metadata downloaded and non shared
5347     SharedFile existingSharedFile =
5348         SharedFile.newBuilder()
5349             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5350             .setFileName("fileName")
5351             .setAndroidShared(false)
5352             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5353             .build();
5354     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5355 
5356     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5357 
5358     verify(mockBackend, never()).exists(any());
5359     // We never acquire the lease since the file can't be shared.
5360     verify(mockBackend, never()).openForWrite(any());
5361 
5362     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5363     assertThat(sharedFile).isEqualTo(existingSharedFile);
5364 
5365     verify(mockSharedFileManager, never())
5366         .setAndroidSharedDownloadedFileEntry(any(), any(), anyLong());
5367 
5368     verifyNoInteractions(mockLogger);
5369   }
5370 
5371   @Test
tryToShareBeforeDownload_blobUriNotSupported()5372   public void tryToShareBeforeDownload_blobUriNotSupported() throws Exception {
5373     // FileStorage without BlobStoreBackend
5374     fileStorage =
5375         new SynchronousFileStorage(Arrays.asList(AndroidFileBackend.builder(context).build()));
5376     fileGroupManager =
5377         new FileGroupManager(
5378             context,
5379             mockLogger,
5380             mockSilentFeedback,
5381             fileGroupsMetadata,
5382             sharedFileManager,
5383             testClock,
5384             Optional.of(mockAccountSource),
5385             SEQUENTIAL_CONTROL_EXECUTOR,
5386             Optional.absent(),
5387             fileStorage,
5388             downloadStageManager,
5389             flags);
5390 
5391     DataFileGroupInternal fileGroup =
5392         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
5393             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5394             .build();
5395 
5396     // Create a to-be-shared file
5397     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5398     NewFileKey newFileKey =
5399         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5400     // Set the file metadata as download completed and non shared
5401     SharedFile existingSharedFile =
5402         SharedFile.newBuilder()
5403             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5404             .setFileName("fileName")
5405             .setAndroidShared(false)
5406             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5407             .build();
5408 
5409     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5410 
5411     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5412 
5413     verify(mockBackend, never()).openForWrite(any());
5414 
5415     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5416     assertThat(sharedFile).isEqualTo(existingSharedFile);
5417 
5418     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5419     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5420 
5421     Void mddAndroidSharingLog = null;
5422     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5423         .containsExactly(mddAndroidSharingLog);
5424     verifyNoMoreInteractions(mockLogger);
5425   }
5426 
5427   @Test
tryToShareBeforeDownload_setAndroidSharedDownloadedFileEntryReturnsFalse()5428   public void tryToShareBeforeDownload_setAndroidSharedDownloadedFileEntryReturnsFalse()
5429       throws Exception {
5430     // Mock SharedFileManager to test failure scenario.
5431     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
5432     DataFileGroupInternal fileGroup =
5433         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5434             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5435             .build();
5436 
5437     // Create a to-be-shared file
5438     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5439     NewFileKey newFileKey =
5440         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5441 
5442     SharedFile existingSharedFile =
5443         SharedFile.newBuilder()
5444             .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS)
5445             .setFileName("fileName")
5446             .setAndroidShared(false)
5447             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5448             .build();
5449 
5450     when(mockSharedFileManager.getSharedFile(newFileKey))
5451         .thenReturn(Futures.immediateFuture(existingSharedFile));
5452     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5453     Uri leaseUri =
5454         DirectoryUtil.getBlobStoreLeaseUri(
5455             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
5456     // The file is available in the blob storage
5457     when(mockBackend.exists(blobUri)).thenReturn(true);
5458 
5459     // Last operation fails
5460     when(mockSharedFileManager.setAndroidSharedDownloadedFileEntry(
5461             newFileKey, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS))
5462         .thenReturn(Futures.immediateFuture(false));
5463 
5464     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5465 
5466     // openForWrite is called only once for acquiring the lease.
5467     verify(mockBackend, never()).openForWrite(blobUri);
5468     verify(mockBackend).openForWrite(leaseUri);
5469 
5470     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5471 
5472     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5473     Void mddAndroidSharingLog = null;
5474     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5475         .containsExactly(mddAndroidSharingLog);
5476     verifyNoMoreInteractions(mockLogger);
5477   }
5478 
5479   @Test
tryToShareBeforeDownload_blobExistsThrowsIOException()5480   public void tryToShareBeforeDownload_blobExistsThrowsIOException() throws Exception {
5481     DataFileGroupInternal fileGroup =
5482         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5483             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5484             .build();
5485 
5486     // Create a to-be-shared file
5487     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5488     NewFileKey newFileKey =
5489         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5490 
5491     SharedFile existingSharedFile =
5492         SharedFile.newBuilder()
5493             .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS)
5494             .setFileName("fileName")
5495             .setAndroidShared(false)
5496             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5497             .build();
5498     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5499 
5500     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5501     Uri leaseUri =
5502         DirectoryUtil.getBlobStoreLeaseUri(
5503             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
5504 
5505     when(mockBackend.exists(blobUri)).thenReturn(true);
5506     when(mockBackend.openForWrite(leaseUri)).thenThrow(new IOException());
5507 
5508     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5509 
5510     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5511     assertThat(sharedFile).isEqualTo(existingSharedFile);
5512 
5513     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5514 
5515     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5516 
5517     Void mddAndroidSharingLog = null;
5518     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5519         .containsExactly(mddAndroidSharingLog);
5520     verifyNoMoreInteractions(mockLogger);
5521   }
5522 
5523   @Test
tryToShareBeforeDownload_fileStorageThrowsLimitExceededException()5524   public void tryToShareBeforeDownload_fileStorageThrowsLimitExceededException() throws Exception {
5525     // Create a file group with expiration date bigger than the expiration date of the existing
5526     // SharedFile.
5527     DataFileGroupInternal fileGroup =
5528         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
5529             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5530             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1)
5531             .build();
5532 
5533     // Create a to-be-shared file
5534     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5535     NewFileKey newFileKey =
5536         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5537 
5538     SharedFile existingSharedFile =
5539         SharedFile.newBuilder()
5540             .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS)
5541             .setFileName("fileName")
5542             .setAndroidShared(false)
5543             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5544             .build();
5545     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5546 
5547     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5548     Uri leaseUri =
5549         DirectoryUtil.getBlobStoreLeaseUri(
5550             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS + 1);
5551     // The file is available in the blob storage
5552     when(mockBackend.exists(blobUri)).thenReturn(true);
5553     // Writing the lease throws an exception
5554     when(mockBackend.openForWrite(leaseUri)).thenThrow(new LimitExceededException());
5555 
5556     fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get();
5557 
5558     verify(mockBackend, never()).openForWrite(blobUri);
5559     verify(mockBackend).openForWrite(leaseUri);
5560 
5561     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5562     // Since there was an exception, the existing shared file didn't update the expiration date.
5563     assertThat(sharedFile).isEqualTo(existingSharedFile);
5564 
5565     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5566     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5567 
5568     Void mddAndroidSharingLog = null;
5569     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5570         .containsExactly(mddAndroidSharingLog);
5571     verifyNoMoreInteractions(mockLogger);
5572   }
5573 
5574   @Test
tryToShareAfterDownload_alreadyShared_sameFileGroup()5575   public void tryToShareAfterDownload_alreadyShared_sameFileGroup() throws Exception {
5576     DataFileGroupInternal fileGroup =
5577         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5578             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5579             .build();
5580 
5581     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5582     NewFileKey newFileKey =
5583         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5584     SharedFile existingSharedFile =
5585         SharedFile.newBuilder()
5586             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5587             .setFileName("fileName")
5588             .setAndroidShared(true)
5589             .setAndroidSharingChecksum(file.getAndroidSharingChecksum())
5590             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5591             .build();
5592     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5593 
5594     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
5595 
5596     verify(mockBackend, never()).exists(any());
5597     verify(mockBackend, never()).openForWrite(any());
5598 
5599     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5600     assertThat(sharedFile).isEqualTo(existingSharedFile);
5601 
5602     verifyNoInteractions(mockLogger);
5603   }
5604 
5605   @Test
tryToShareAfterDownload_alreadyShared_differentFileGroup()5606   public void tryToShareAfterDownload_alreadyShared_differentFileGroup() throws Exception {
5607     // Create a file group with expiration date bigger than the expiration date of the existing
5608     // SharedFile.
5609     DataFileGroupInternal fileGroup =
5610         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5611             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5612             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5613             .build();
5614 
5615     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5616     NewFileKey newFileKey =
5617         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5618     SharedFile existingSharedFile =
5619         SharedFile.newBuilder()
5620             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5621             .setFileName("fileName")
5622             .setAndroidShared(true)
5623             .setAndroidSharingChecksum(file.getAndroidSharingChecksum())
5624             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS - 1)
5625             .build();
5626     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5627 
5628     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5629     Uri leaseUri =
5630         DirectoryUtil.getBlobStoreLeaseUri(
5631             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
5632 
5633     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
5634 
5635     // openForWrite is called only once for acquiring the lease.
5636     verify(mockBackend, never()).exists(any());
5637     verify(mockBackend).openForWrite(leaseUri);
5638 
5639     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5640     // Verify that the SharedFile has updated its expiration date after the download.
5641     assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS);
5642     assertThat(sharedFile.getAndroidShared()).isTrue();
5643     assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri);
5644 
5645     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5646 
5647     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5648     Void mddAndroidSharingLog = null;
5649     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5650         .containsExactly(mddAndroidSharingLog);
5651     verifyNoMoreInteractions(mockLogger);
5652   }
5653 
5654   @Test
tryToShareAfterDownload_toBeSharedFile_blobExists()5655   public void tryToShareAfterDownload_toBeSharedFile_blobExists() throws Exception {
5656     // Create a file group with expiration date bigger than the expiration date of the existing
5657     // SharedFile.
5658     DataFileGroupInternal fileGroup =
5659         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5660             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5661             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5662             .build();
5663 
5664     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5665     NewFileKey newFileKey =
5666         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5667     SharedFile existingSharedFile =
5668         SharedFile.newBuilder()
5669             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5670             .setFileName("fileName")
5671             .setAndroidShared(false)
5672             .build();
5673     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5674 
5675     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5676     Uri leaseUri =
5677         DirectoryUtil.getBlobStoreLeaseUri(
5678             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
5679     // The file is available in the blob storage
5680     when(mockBackend.exists(blobUri)).thenReturn(true);
5681 
5682     simulateDownload(file, existingSharedFile.getFileName());
5683     Uri onDeviceuri =
5684         DirectoryUtil.getOnDeviceUri(
5685             context,
5686             newFileKey.getAllowedReaders(),
5687             existingSharedFile.getFileName(),
5688             newFileKey.getChecksum(),
5689             mockSilentFeedback,
5690             /* instanceId= */ Optional.absent(),
5691             false);
5692     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5693 
5694     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
5695 
5696     verify(mockBackend).exists(blobUri);
5697     verify(mockBackend).openForWrite(leaseUri);
5698 
5699     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5700     // Verify that the SharedFile has updated its expiration date after the download.
5701     assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS);
5702     assertThat(sharedFile.getAndroidShared()).isTrue();
5703     assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri);
5704 
5705     // Local copy has not been deleted.
5706     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5707 
5708     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5709 
5710     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5711     Void mddAndroidSharingLog = null;
5712     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5713         .containsExactly(mddAndroidSharingLog);
5714     verifyNoMoreInteractions(mockLogger);
5715   }
5716 
5717   @Test
tryToShareAfterDownload_toBeSharedFile_canBeCopied()5718   public void tryToShareAfterDownload_toBeSharedFile_canBeCopied() throws Exception {
5719     // Create a file group with expiration date bigger than the expiration date of the existing
5720     // SharedFile.
5721     File tempFile = folder.newFile("blobFile");
5722     DataFileGroupInternal fileGroup =
5723         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5724             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5725             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5726             .build();
5727 
5728     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5729     NewFileKey newFileKey =
5730         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5731     SharedFile existingSharedFile =
5732         SharedFile.newBuilder()
5733             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5734             .setFileName("fileName")
5735             .setAndroidShared(false)
5736             .build();
5737     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5738 
5739     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
5740     Uri leaseUri =
5741         DirectoryUtil.getBlobStoreLeaseUri(
5742             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
5743     // The file isn't available yet in the blob storage
5744     when(mockBackend.exists(blobUri)).thenReturn(false);
5745     when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile));
5746 
5747     simulateDownload(file, existingSharedFile.getFileName());
5748     Uri onDeviceuri =
5749         DirectoryUtil.getOnDeviceUri(
5750             context,
5751             newFileKey.getAllowedReaders(),
5752             existingSharedFile.getFileName(),
5753             newFileKey.getChecksum(),
5754             mockSilentFeedback,
5755             /* instanceId= */ Optional.absent(),
5756             false);
5757     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5758 
5759     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
5760 
5761     // openForWrite is called once for writing the blob, once for acquiring the lease.
5762     verify(mockBackend).openForWrite(blobUri);
5763     verify(mockBackend).openForWrite(leaseUri);
5764 
5765     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5766     // Verify that the SharedFile has updated its expiration date after the download.
5767     assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS);
5768     assertThat(sharedFile.getAndroidShared()).isTrue();
5769     assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri);
5770 
5771     // Local copy has not been deleted.
5772     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5773 
5774     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5775 
5776     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5777     Void mddAndroidSharingLog = null;
5778     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5779         .containsExactly(mddAndroidSharingLog);
5780     verifyNoMoreInteractions(mockLogger);
5781   }
5782 
5783   @Test
tryToShareAfterDownload_nonToBeSharedFile_neverShared()5784   public void tryToShareAfterDownload_nonToBeSharedFile_neverShared() throws Exception {
5785     // Create a file group with expiration date bigger than the expiration date of the existing
5786     // SharedFile.
5787     DataFileGroupInternal fileGroup =
5788         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5789             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5790             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5791             .build();
5792 
5793     DataFile file = MddTestUtil.createDataFile("fileId", 0);
5794     NewFileKey newFileKey =
5795         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5796     SharedFile existingSharedFile =
5797         SharedFile.newBuilder()
5798             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5799             .setFileName("fileName")
5800             .setAndroidShared(false)
5801             .build();
5802     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5803 
5804     File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName());
5805     Uri onDeviceuri =
5806         DirectoryUtil.getOnDeviceUri(
5807             context,
5808             newFileKey.getAllowedReaders(),
5809             existingSharedFile.getFileName(),
5810             newFileKey.getChecksum(),
5811             mockSilentFeedback,
5812             /* instanceId= */ Optional.absent(),
5813             false);
5814     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5815 
5816     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
5817 
5818     verify(mockBackend, never()).exists(any());
5819     verify(mockBackend, never()).openForWrite(any());
5820 
5821     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5822     // Verify that the SharedFile has updated its expiration date after the download.
5823     SharedFile expectedSharedFile =
5824         existingSharedFile.toBuilder()
5825             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5826             .build();
5827     assertThat(sharedFile).isEqualTo(expectedSharedFile);
5828 
5829     // Local copy still available.
5830     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5831     onDeviceFile.delete();
5832 
5833     verifyNoInteractions(mockLogger);
5834   }
5835 
5836   @Test
tryToShareAfterDownload_toBeSharedFile_neverShared()5837   public void tryToShareAfterDownload_toBeSharedFile_neverShared() throws Exception {
5838     // Create a file group with expiration date bigger than the expiration date of the existing
5839     // SharedFile.
5840     DataFileGroupInternal fileGroup =
5841         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5842             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5843             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5844             .build();
5845 
5846     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5847     // This should never happened in a real scenario.
5848     file = file.toBuilder().setAndroidSharingChecksum("").build();
5849 
5850     NewFileKey newFileKey =
5851         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5852     SharedFile existingSharedFile =
5853         SharedFile.newBuilder()
5854             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5855             .setFileName("fileName")
5856             .setAndroidShared(false)
5857             .build();
5858     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5859 
5860     File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName());
5861     Uri onDeviceuri =
5862         DirectoryUtil.getOnDeviceUri(
5863             context,
5864             newFileKey.getAllowedReaders(),
5865             existingSharedFile.getFileName(),
5866             newFileKey.getChecksum(),
5867             mockSilentFeedback,
5868             /* instanceId= */ Optional.absent(),
5869             false);
5870     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5871 
5872     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
5873 
5874     verify(mockBackend, never()).exists(any());
5875     verify(mockBackend, never()).openForWrite(any());
5876 
5877     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5878     // Verify that the SharedFile has updated its expiration date after the download.
5879     SharedFile expectedSharedFile =
5880         existingSharedFile.toBuilder()
5881             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5882             .build();
5883     assertThat(sharedFile).isEqualTo(expectedSharedFile);
5884 
5885     // Local copy still available.
5886     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
5887     onDeviceFile.delete();
5888 
5889     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5890     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5891 
5892     Void mddAndroidSharingLog = null;
5893     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5894         .containsExactly(mddAndroidSharingLog);
5895 
5896     verifyNoMoreInteractions(mockLogger);
5897   }
5898 
5899   @Test
tryToShareAfterDownload_blobUriNotSupported()5900   public void tryToShareAfterDownload_blobUriNotSupported() throws Exception {
5901     // FileStorage without BlobStoreBackend
5902     fileStorage =
5903         new SynchronousFileStorage(Arrays.asList(AndroidFileBackend.builder(context).build()));
5904     fileGroupManager =
5905         new FileGroupManager(
5906             context,
5907             mockLogger,
5908             mockSilentFeedback,
5909             fileGroupsMetadata,
5910             sharedFileManager,
5911             testClock,
5912             Optional.of(mockAccountSource),
5913             SEQUENTIAL_CONTROL_EXECUTOR,
5914             Optional.absent(),
5915             fileStorage,
5916             downloadStageManager,
5917             flags);
5918 
5919     DataFileGroupInternal fileGroup =
5920         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder()
5921             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5922             .build();
5923 
5924     // Create a to-be-shared file
5925     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5926     NewFileKey newFileKey =
5927         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5928     // Set the file metadata as download completed and non shared
5929     SharedFile existingSharedFile =
5930         SharedFile.newBuilder()
5931             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
5932             .setFileName("fileName")
5933             .setAndroidShared(false)
5934             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5935             .build();
5936     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
5937 
5938     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
5939 
5940     verify(mockBackend, never()).openForWrite(any());
5941 
5942     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
5943     assertThat(sharedFile).isEqualTo(existingSharedFile);
5944 
5945     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5946     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5947 
5948     Void mddAndroidSharingLog = null;
5949     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5950         .containsExactly(mddAndroidSharingLog);
5951     verifyNoMoreInteractions(mockLogger);
5952   }
5953 
5954   @Test
tryToShareAfterDownload_nonExistentFile()5955   public void tryToShareAfterDownload_nonExistentFile() throws Exception {
5956     DataFileGroupInternal fileGroup =
5957         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5958             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5959             .build();
5960 
5961     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
5962     NewFileKey newFileKey =
5963         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
5964 
5965     ListenableFuture<Void> tryToShareFuture =
5966         fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey);
5967 
5968     ExecutionException exception = assertThrows(ExecutionException.class, tryToShareFuture::get);
5969     assertThat(exception).hasCauseThat().isInstanceOf(SharedFileMissingException.class);
5970 
5971     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
5972     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
5973 
5974     Void mddAndroidSharingLog = null;
5975     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
5976         .containsExactly(mddAndroidSharingLog);
5977     verifyNoMoreInteractions(mockLogger);
5978 
5979     verify(mockBackend, never()).exists(any());
5980     verify(mockBackend, never()).openForWrite(any());
5981     verify(mockSharedFileManager, never())
5982         .setAndroidSharedDownloadedFileEntry(any(), any(), anyLong());
5983     verify(mockSharedFileManager, never()).updateMaxExpirationDateSecs(newFileKey, 0);
5984   }
5985 
5986   @Test
tryToShareAfterDownload_updateMaxExpirationDateSecsReturnsFalse()5987   public void tryToShareAfterDownload_updateMaxExpirationDateSecsReturnsFalse() throws Exception {
5988     // Mock SharedFileManager to test failure scenario.
5989     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
5990     // Create a file group with expiration date bigger than the expiration date of the existing
5991     // SharedFile.
5992     DataFileGroupInternal fileGroup =
5993         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
5994             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
5995             .setDownloadConditions(DownloadConditions.getDefaultInstance())
5996             .build();
5997 
5998     DataFile file = MddTestUtil.createDataFile("fileId", 0);
5999     NewFileKey newFileKey =
6000         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
6001     SharedFile existingSharedFile =
6002         SharedFile.newBuilder()
6003             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
6004             .setFileName("fileName")
6005             .setAndroidShared(false)
6006             .build();
6007 
6008     when(mockSharedFileManager.getSharedFile(newFileKey))
6009         .thenReturn(Futures.immediateFuture(existingSharedFile));
6010     when(mockSharedFileManager.updateMaxExpirationDateSecs(
6011             newFileKey, FILE_GROUP_EXPIRATION_DATE_SECS))
6012         .thenReturn(Futures.immediateFuture(false));
6013 
6014     File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName());
6015     Uri onDeviceuri =
6016         DirectoryUtil.getOnDeviceUri(
6017             context,
6018             newFileKey.getAllowedReaders(),
6019             existingSharedFile.getFileName(),
6020             newFileKey.getChecksum(),
6021             mockSilentFeedback,
6022             /* instanceId= */ Optional.absent(),
6023             false);
6024     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6025 
6026     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
6027 
6028     verify(mockBackend, never()).exists(any());
6029     verify(mockBackend, never()).openForWrite(any());
6030     verify(mockSharedFileManager, never())
6031         .setAndroidSharedDownloadedFileEntry(any(), any(), anyLong());
6032     verify(mockSharedFileManager)
6033         .updateMaxExpirationDateSecs(newFileKey, FILE_GROUP_EXPIRATION_DATE_SECS);
6034     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6035     onDeviceFile.delete();
6036 
6037     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
6038     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
6039     Void mddAndroidSharingLog = null;
6040     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
6041         .containsExactly(mddAndroidSharingLog);
6042     verifyNoMoreInteractions(mockLogger);
6043   }
6044 
6045   @Test
tryToShareAfterDownload_setAndroidSharedDownloadedFileEntryReturnsFalse()6046   public void tryToShareAfterDownload_setAndroidSharedDownloadedFileEntryReturnsFalse()
6047       throws Exception {
6048     // Mock SharedFileManager to test failure scenario.
6049     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
6050     DataFileGroupInternal fileGroup =
6051         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6052             .setDownloadConditions(DownloadConditions.getDefaultInstance())
6053             .build();
6054 
6055     // Create a to-be-shared file
6056     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
6057     NewFileKey newFileKey =
6058         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
6059 
6060     SharedFile existingSharedFile =
6061         SharedFile.newBuilder()
6062             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
6063             .setFileName("fileName")
6064             .setAndroidShared(false)
6065             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
6066             .build();
6067 
6068     when(mockSharedFileManager.getSharedFile(newFileKey))
6069         .thenReturn(Futures.immediateFuture(existingSharedFile));
6070     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
6071     Uri leaseUri =
6072         DirectoryUtil.getBlobStoreLeaseUri(
6073             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
6074     // The file is available in the blob storage
6075     when(mockBackend.exists(blobUri)).thenReturn(true);
6076 
6077     // Last operation fails
6078     when(mockSharedFileManager.setAndroidSharedDownloadedFileEntry(
6079             newFileKey, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS))
6080         .thenReturn(Futures.immediateFuture(false));
6081     when(mockSharedFileManager.updateMaxExpirationDateSecs(newFileKey, 0))
6082         .thenReturn(Futures.immediateFuture(true));
6083 
6084     File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName());
6085     Uri onDeviceuri =
6086         DirectoryUtil.getOnDeviceUri(
6087             context,
6088             newFileKey.getAllowedReaders(),
6089             existingSharedFile.getFileName(),
6090             newFileKey.getChecksum(),
6091             mockSilentFeedback,
6092             /* instanceId= */ Optional.absent(),
6093             false);
6094     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6095 
6096     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
6097 
6098     verify(mockBackend).exists(blobUri);
6099     // openForWrite is called only once for acquiring the lease.
6100     verify(mockBackend, never()).openForWrite(blobUri);
6101     verify(mockBackend).openForWrite(leaseUri);
6102     verify(mockSharedFileManager).updateMaxExpirationDateSecs(newFileKey, 0);
6103     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6104     onDeviceFile.delete();
6105 
6106     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
6107     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
6108 
6109     Void mddAndroidSharingLog = null;
6110     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
6111         .containsExactly(mddAndroidSharingLog);
6112     verifyNoMoreInteractions(mockLogger);
6113   }
6114 
6115   @Test
tryToShareAfterDownload_copyBlobThrowsIOException()6116   public void tryToShareAfterDownload_copyBlobThrowsIOException() throws Exception {
6117     DataFileGroupInternal fileGroup =
6118         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6119             .setDownloadConditions(DownloadConditions.getDefaultInstance())
6120             .build();
6121 
6122     // Create a to-be-shared file
6123     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
6124     NewFileKey newFileKey =
6125         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
6126 
6127     SharedFile existingSharedFile =
6128         SharedFile.newBuilder()
6129             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
6130             .setFileName("fileName")
6131             .setAndroidShared(false)
6132             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
6133             .build();
6134     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
6135 
6136     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
6137     Uri leaseUri =
6138         DirectoryUtil.getBlobStoreLeaseUri(
6139             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS);
6140     // The file isn't available in the blob storage
6141     when(mockBackend.exists(blobUri)).thenReturn(false);
6142     // Copying the blob throws an exception
6143     when(mockBackend.openForWrite(blobUri)).thenThrow(new IOException());
6144 
6145     File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName());
6146     Uri onDeviceuri =
6147         DirectoryUtil.getOnDeviceUri(
6148             context,
6149             newFileKey.getAllowedReaders(),
6150             existingSharedFile.getFileName(),
6151             newFileKey.getChecksum(),
6152             mockSilentFeedback,
6153             /* instanceId= */ Optional.absent(),
6154             false);
6155     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6156 
6157     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
6158 
6159     verify(mockBackend, never()).openForWrite(leaseUri);
6160 
6161     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
6162     assertThat(sharedFile).isEqualTo(existingSharedFile);
6163 
6164     // Local copy still available.
6165     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6166     onDeviceFile.delete();
6167 
6168     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
6169     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
6170 
6171     Void mddAndroidSharingLog = null;
6172     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
6173         .containsExactly(mddAndroidSharingLog);
6174     verifyNoMoreInteractions(mockLogger);
6175   }
6176 
6177   @Test
tryToShareAfterDownload_fileStorageThrowsLimitExceededException()6178   public void tryToShareAfterDownload_fileStorageThrowsLimitExceededException() throws Exception {
6179     // Create a file group with expiration date bigger than the expiration date of the existing
6180     // SharedFile.
6181     DataFileGroupInternal fileGroup =
6182         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6183             .setDownloadConditions(DownloadConditions.getDefaultInstance())
6184             .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1)
6185             .build();
6186 
6187     // Create a to-be-shared file
6188     DataFile file = MddTestUtil.createSharedDataFile("fileId", 0);
6189     NewFileKey newFileKey =
6190         SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS);
6191 
6192     SharedFile existingSharedFile =
6193         SharedFile.newBuilder()
6194             .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
6195             .setFileName("fileName")
6196             .setAndroidShared(false)
6197             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS)
6198             .build();
6199     sharedFilesMetadata.write(newFileKey, existingSharedFile).get();
6200 
6201     Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum());
6202     Uri leaseUri =
6203         DirectoryUtil.getBlobStoreLeaseUri(
6204             context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS + 1);
6205     // The file is available in the blob storage
6206     when(mockBackend.exists(blobUri)).thenReturn(true);
6207 
6208     File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName());
6209     Uri onDeviceuri =
6210         DirectoryUtil.getOnDeviceUri(
6211             context,
6212             newFileKey.getAllowedReaders(),
6213             existingSharedFile.getFileName(),
6214             newFileKey.getChecksum(),
6215             mockSilentFeedback,
6216             /* instanceId= */ Optional.absent(),
6217             false);
6218     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6219 
6220     // Writing the lease throws an exception
6221     when(mockBackend.openForWrite(leaseUri)).thenThrow(new LimitExceededException());
6222 
6223     fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get();
6224 
6225     verify(mockBackend, never()).openForWrite(blobUri);
6226     verify(mockBackend).openForWrite(leaseUri);
6227 
6228     SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get();
6229     // Even if there was an exception, the SharedFile has updated its expiration date after the
6230     // download.
6231     SharedFile expectedSharedFile =
6232         existingSharedFile.toBuilder()
6233             .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1)
6234             .build();
6235     assertThat(sharedFile).isEqualTo(expectedSharedFile);
6236 
6237     // Local copy still available.
6238     assertThat(fileStorage.exists(onDeviceuri)).isTrue();
6239     onDeviceFile.delete();
6240 
6241     ArgumentCaptor<Void> mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class);
6242     verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture());
6243 
6244     Void mddAndroidSharingLog = null;
6245     assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues())
6246         .containsExactly(mddAndroidSharingLog);
6247     verifyNoMoreInteractions(mockLogger);
6248   }
6249 
6250   @Test
testVerifyPendingGroupDownloaded()6251   public void testVerifyPendingGroupDownloaded() throws Exception {
6252     // Write 2 groups to the pending shared prefs.
6253     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6254     writePendingFileGroup(testKey, fileGroup1);
6255     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6256     writePendingFileGroup(testKey2, fileGroup2);
6257 
6258     // Make the verify download call fail for one file in the first group.
6259     writeSharedFiles(
6260         sharedFilesMetadata,
6261         fileGroup1,
6262         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS));
6263     writeSharedFiles(
6264         sharedFilesMetadata,
6265         fileGroup2,
6266         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
6267 
6268     testClock.set(/* millis */ 1000);
6269 
6270     assertThat(
6271             fileGroupManager
6272                 .verifyGroupDownloaded(
6273                     testKey,
6274                     fileGroup1,
6275                     /* removePendingVersion= */ true,
6276                     noCustomValidation(),
6277                     DownloadStateLogger.forDownload(mockLogger))
6278                 .get())
6279         .isEqualTo(GroupDownloadStatus.PENDING);
6280     assertThat(
6281             fileGroupManager
6282                 .verifyGroupDownloaded(
6283                     testKey2,
6284                     fileGroup2,
6285                     /* removePendingVersion= */ true,
6286                     noCustomValidation(),
6287                     DownloadStateLogger.forDownload(mockLogger))
6288                 .get())
6289         .isEqualTo(GroupDownloadStatus.DOWNLOADED);
6290 
6291     // Verify that the pending group is still part of pending groups prefs.
6292     DataFileGroupInternal pendingGroup1 = readPendingFileGroup(testKey);
6293     assertThat(pendingGroup1).isEqualTo(fileGroup1);
6294 
6295     // Verify that the pending group is not written into metadata.
6296     assertThat(readDownloadedFileGroup(testKey)).isNull();
6297 
6298     fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000);
6299 
6300     // Verify that the completely downloaded group is written into metadata.
6301     DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2);
6302     assertThat(downloadedGroup2).isEqualTo(fileGroup2);
6303 
6304     verify(mockLogger)
6305         .logEventSampled(
6306             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6307             TEST_GROUP,
6308             /* fileGroupVersionNumber= */ 0,
6309             /* buildId= */ 0,
6310             /* variantId= */ "");
6311     verify(mockLogger)
6312         .logEventSampled(
6313             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6314             TEST_GROUP_2,
6315             /* fileGroupVersionNumber= */ 0,
6316             /* buildId= */ 0,
6317             /* variantId= */ "");
6318   }
6319 
6320   @Test
testVerifyAllPendingGroupsDownloaded()6321   public void testVerifyAllPendingGroupsDownloaded() throws Exception {
6322     // Write 2 groups to the pending shared prefs.
6323     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6324     writePendingFileGroup(testKey, fileGroup1);
6325     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6326     writePendingFileGroup(testKey2, fileGroup2);
6327 
6328     // Make the verify download call fail for one file in the first group.
6329     writeSharedFiles(
6330         sharedFilesMetadata,
6331         fileGroup1,
6332         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS));
6333     writeSharedFiles(
6334         sharedFilesMetadata,
6335         fileGroup2,
6336         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
6337 
6338     testClock.set(/* millis */ 1000);
6339     fileGroupManager.verifyAllPendingGroupsDownloaded(noCustomValidation()).get();
6340 
6341     // Verify that the pending group is still part of pending groups prefs.
6342     DataFileGroupInternal pendingGroup1 = readPendingFileGroup(testKey);
6343     MddTestUtil.assertMessageEquals(fileGroup1, pendingGroup1);
6344 
6345     // Verify that the pending group is not written into metadata.
6346     assertThat(readDownloadedFileGroup(testKey)).isNull();
6347 
6348     fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000);
6349 
6350     // Verify that the completely downloaded group is written into metadata.
6351     DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2);
6352     assertThat(downloadedGroup2).isEqualTo(fileGroup2);
6353 
6354     verify(mockLogger)
6355         .logEventSampled(
6356             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6357             TEST_GROUP,
6358             /* fileGroupVersionNumber= */ 0,
6359             /* buildId= */ 0,
6360             /* variantId= */ "");
6361     verify(mockLogger)
6362         .logEventSampled(
6363             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6364             TEST_GROUP_2,
6365             /* fileGroupVersionNumber= */ 0,
6366             /* buildId= */ 0,
6367             /* variantId= */ "");
6368   }
6369 
6370   @Test
testVerifyAllPendingGroupsDownloaded_existingDownloadedGroup()6371   public void testVerifyAllPendingGroupsDownloaded_existingDownloadedGroup() throws Exception {
6372     // Write 2 groups to the pending shared prefs.
6373     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6374     writePendingFileGroup(testKey, fileGroup1);
6375     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6376     writePendingFileGroup(testKey2, fileGroup2);
6377 
6378     // Also write 2 groups to the downloaded shared prefs.
6379     // fileGroup3 is the downloaded version if fileGroup1.
6380     DataFileGroupInternal fileGroup3 =
6381         MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP, 1);
6382     writeDownloadedFileGroup(testKey, fileGroup3);
6383     DataFileGroupInternal fileGroup4 =
6384         MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP_3, 2);
6385     writeDownloadedFileGroup(testKey3, fileGroup4);
6386 
6387     // All file are downloaded.
6388     writeSharedFiles(
6389         sharedFilesMetadata,
6390         fileGroup1,
6391         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
6392     writeSharedFiles(
6393         sharedFilesMetadata,
6394         fileGroup2,
6395         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
6396     writeSharedFiles(
6397         sharedFilesMetadata, fileGroup3, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE));
6398     writeSharedFiles(
6399         sharedFilesMetadata,
6400         fileGroup4,
6401         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
6402 
6403     testClock.set(/* millis */ 1000);
6404     fileGroupManager.verifyAllPendingGroupsDownloaded(noCustomValidation()).get();
6405 
6406     // Verify that pending key is removed if the group is downloaded.
6407     assertThat(readPendingFileGroup(testKey)).isNull();
6408     assertThat(readPendingFileGroup(testKey2)).isNull();
6409     assertThat(readPendingFileGroup(testKey3)).isNull();
6410 
6411     fileGroup1 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup1, 1000);
6412     fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000);
6413 
6414     // Verify that pending group is marked as downloaded group.
6415     DataFileGroupInternal downloadedGroup1 = readDownloadedFileGroup(testKey);
6416     assertThat(downloadedGroup1).isEqualTo(fileGroup1);
6417     DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2);
6418     assertThat(downloadedGroup2).isEqualTo(fileGroup2);
6419     DataFileGroupInternal downloadedGroup4 = readDownloadedFileGroup(testKey3);
6420     assertThat(downloadedGroup4).isEqualTo(fileGroup4);
6421 
6422     verify(mockLogger)
6423         .logEventSampled(
6424             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6425             TEST_GROUP,
6426             /* fileGroupVersionNumber= */ 0,
6427             /* buildId= */ 0,
6428             /* variantId= */ "");
6429     verify(mockLogger)
6430         .logEventSampled(
6431             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6432             TEST_GROUP_2,
6433             /* fileGroupVersionNumber= */ 0,
6434             /* buildId= */ 0,
6435             /* variantId= */ "");
6436 
6437     // fileGroup3 should have been scheduled for deletion.
6438     fileGroup3 =
6439         fileGroup3.toBuilder()
6440             .setBookkeeping(DataFileGroupBookkeeping.newBuilder().setStaleExpirationDate(1).build())
6441             .build();
6442     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).containsExactly(fileGroup3);
6443   }
6444 
6445   @Test
testGroupDownloadFailed()6446   public void testGroupDownloadFailed() throws Exception {
6447     // Write 2 groups to the pending shared prefs.
6448     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6449     writePendingFileGroup(testKey, fileGroup1);
6450     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6451     writePendingFileGroup(testKey2, fileGroup2);
6452 
6453     // Make the second file of the first group fail.
6454     writeSharedFiles(
6455         sharedFilesMetadata,
6456         fileGroup1,
6457         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_FAILED));
6458     writeSharedFiles(
6459         sharedFilesMetadata,
6460         fileGroup2,
6461         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
6462 
6463     fileGroupManager.verifyAllPendingGroupsDownloaded(noCustomValidation()).get();
6464 
6465     // Verify that pending key is removed if download is complete.
6466     assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1);
6467     assertThat(readPendingFileGroup(testKey2)).isNull();
6468 
6469     // Verify that downloaded key is written into metadata if download is complete.
6470     fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000);
6471     DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2);
6472     assertThat(downloadedGroup2).isEqualTo(fileGroup2);
6473 
6474     verify(mockLogger)
6475         .logEventSampled(
6476             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6477             TEST_GROUP,
6478             /* fileGroupVersionNumber= */ 0,
6479             /* buildId= */ 0,
6480             /* variantId= */ "");
6481     verify(mockLogger)
6482         .logEventSampled(
6483             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6484             TEST_GROUP_2,
6485             /* fileGroupVersionNumber= */ 0,
6486             /* buildId= */ 0,
6487             /* variantId= */ "");
6488   }
6489 
6490   @Test
testDeleteUninstalledAppGroups_noUninstalledApps()6491   public void testDeleteUninstalledAppGroups_noUninstalledApps() throws Exception {
6492     PackageManager packageManager = context.getPackageManager();
6493     final PackageInfo packageInfo = new PackageInfo();
6494     packageInfo.packageName = context.getPackageName();
6495     packageInfo.lastUpdateTime = System.currentTimeMillis();
6496     Shadows.shadowOf(packageManager).addPackage(packageInfo);
6497 
6498     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6499     writePendingFileGroup(testKey, fileGroup1);
6500 
6501     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6502     writePendingFileGroup(testKey2, fileGroup2);
6503 
6504     fileGroupManager.deleteUninstalledAppGroups().get();
6505 
6506     assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1);
6507     assertThat(readPendingFileGroup(testKey2)).isEqualTo(fileGroup2);
6508   }
6509 
6510   @Test
testDeleteUninstalledAppGroups_uninstalledApp()6511   public void testDeleteUninstalledAppGroups_uninstalledApp() throws Exception {
6512     PackageManager packageManager = context.getPackageManager();
6513     final PackageInfo packageInfo = new PackageInfo();
6514     packageInfo.packageName = context.getPackageName();
6515     packageInfo.lastUpdateTime = System.currentTimeMillis();
6516     Shadows.shadowOf(packageManager).addPackage(packageInfo);
6517 
6518     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6519     writePendingFileGroup(testKey, fileGroup1);
6520     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6521     GroupKey uninstalledAppKey =
6522         GroupKey.newBuilder().setGroupName(TEST_GROUP_2).setOwnerPackage("uninstalled.app").build();
6523     writeDownloadedFileGroup(uninstalledAppKey, fileGroup2);
6524 
6525     assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1);
6526     assertThat(readDownloadedFileGroup(uninstalledAppKey)).isEqualTo(fileGroup2);
6527 
6528     fileGroupManager.deleteUninstalledAppGroups().get();
6529 
6530     assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1);
6531     assertThat(readDownloadedFileGroup(uninstalledAppKey)).isNull();
6532   }
6533 
6534   @Test
testDeleteRemovedAccountGroups_noRemovedAccounts()6535   public void testDeleteRemovedAccountGroups_noRemovedAccounts() throws Exception {
6536     Account account1 = new Account("name1", "type1");
6537     Account account2 = new Account("name2", "type2");
6538 
6539     when(mockAccountSource.getAllAccounts()).thenReturn(ImmutableList.of(account1, account2));
6540 
6541     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6542     GroupKey key1 =
6543         GroupKey.newBuilder()
6544             .setGroupName(TEST_GROUP)
6545             .setOwnerPackage(context.getPackageName())
6546             .setAccount(AccountUtil.serialize(account1))
6547             .build();
6548 
6549     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6550     GroupKey key2 =
6551         GroupKey.newBuilder()
6552             .setGroupName(TEST_GROUP_2)
6553             .setOwnerPackage(context.getPackageName())
6554             .setAccount(AccountUtil.serialize(account2))
6555             .build();
6556 
6557     DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2);
6558     GroupKey key3 =
6559         GroupKey.newBuilder()
6560             .setGroupName(TEST_GROUP_3)
6561             .setOwnerPackage(context.getPackageName())
6562             .build();
6563 
6564     writeDownloadedFileGroup(key1, fileGroup1);
6565     writeDownloadedFileGroup(key2, fileGroup2);
6566     writeDownloadedFileGroup(key3, fileGroup3);
6567 
6568     fileGroupManager.deleteRemovedAccountGroups().get();
6569 
6570     assertThat(fileGroupsMetadata.getAllGroupKeys().get())
6571         .containsExactly(getDownloadedKey(key1), getDownloadedKey(key2), getDownloadedKey(key3));
6572 
6573     verifyNoInteractions(mockLogger);
6574   }
6575 
6576   @Test
testDeleteRemovedAccountGroups_removedAccounts()6577   public void testDeleteRemovedAccountGroups_removedAccounts() throws Exception {
6578     Account account1 = new Account("name1", "type1");
6579     Account account2 = new Account("name2", "type2");
6580 
6581     when(mockAccountSource.getAllAccounts()).thenReturn(ImmutableList.of(account1));
6582 
6583     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6584     GroupKey key1 =
6585         GroupKey.newBuilder()
6586             .setGroupName(TEST_GROUP)
6587             .setOwnerPackage(context.getPackageName())
6588             .setAccount(AccountUtil.serialize(account1))
6589             .build();
6590 
6591     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6592     GroupKey key2 =
6593         GroupKey.newBuilder()
6594             .setGroupName(TEST_GROUP_2)
6595             .setOwnerPackage(context.getPackageName())
6596             .setAccount(AccountUtil.serialize(account2))
6597             .build();
6598 
6599     DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2);
6600     GroupKey key3 =
6601         GroupKey.newBuilder()
6602             .setGroupName(TEST_GROUP_3)
6603             .setOwnerPackage(context.getPackageName())
6604             .build();
6605 
6606     writeDownloadedFileGroup(key1, fileGroup1);
6607     writeDownloadedFileGroup(key2, fileGroup2);
6608     writeDownloadedFileGroup(key3, fileGroup3);
6609 
6610     fileGroupManager.deleteRemovedAccountGroups().get();
6611 
6612     assertThat(fileGroupsMetadata.getAllGroupKeys().get())
6613         .containsExactly(getDownloadedKey(key1), getDownloadedKey(key3));
6614 
6615     verify(mockLogger)
6616         .logEventSampled(
6617             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6618             TEST_GROUP_2,
6619             /* fileGroupVersionNumber= */ 0,
6620             /* buildId= */ 0,
6621             /* variantId= */ "");
6622     verifyNoMoreInteractions(mockLogger);
6623   }
6624 
6625   @Test
testLogAndDeleteForMissingSharedFiles()6626   public void testLogAndDeleteForMissingSharedFiles() throws Exception {
6627     resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager);
6628 
6629     GroupKey downloadedGroupKeyWithFileMissing =
6630         GroupKey.newBuilder()
6631             .setGroupName(TEST_GROUP)
6632             .setOwnerPackage(context.getPackageName())
6633             .setDownloaded(true)
6634             .build();
6635     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
6636     writeDownloadedFileGroup(downloadedGroupKeyWithFileMissing, fileGroup1);
6637     NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup1);
6638     when(mockSharedFileManager.reVerifyFile(eq(keys[0]), eq(fileGroup1.getFile(0))))
6639         .thenReturn(Futures.immediateFailedFuture(new SharedFileMissingException()));
6640     when(mockSharedFileManager.reVerifyFile(eq(keys[1]), eq(fileGroup1.getFile(1))))
6641         .thenReturn(Futures.immediateFailedFuture(new SharedFileMissingException()));
6642 
6643     GroupKey pendingGroupKeyWithFileMissing =
6644         GroupKey.newBuilder()
6645             .setGroupName(TEST_GROUP_2)
6646             .setOwnerPackage(context.getPackageName())
6647             .setDownloaded(false)
6648             .build();
6649     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
6650     writePendingFileGroup(pendingGroupKeyWithFileMissing, fileGroup2);
6651     // Write only the first file metadata.
6652     NewFileKey[] keys2 = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup2);
6653     when(mockSharedFileManager.reVerifyFile(eq(keys2[0]), eq(fileGroup2.getFile(0))))
6654         .thenReturn(Futures.immediateFailedFuture(new SharedFileMissingException()));
6655     // mockSharedFileManager returns "OK" when verifying second file.
6656 
6657     GroupKey groupKeyWithNoFileMissing =
6658         GroupKey.newBuilder()
6659             .setGroupName(TEST_GROUP_3)
6660             .setOwnerPackage(context.getPackageName())
6661             .setDownloaded(true)
6662             .build();
6663     DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2);
6664     writeDownloadedFileGroup(groupKeyWithNoFileMissing, fileGroup3);
6665     // mockSharedFileManager always returns "OK" when verifying files.
6666 
6667     fileGroupManager.logAndDeleteForMissingSharedFiles().get();
6668 
6669     if (flags.deleteFileGroupsWithFilesMissing()) {
6670       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
6671           .containsExactly(groupKeyWithNoFileMissing);
6672     } else {
6673       assertThat(fileGroupsMetadata.getAllGroupKeys().get())
6674           .containsExactly(
6675               downloadedGroupKeyWithFileMissing,
6676               pendingGroupKeyWithFileMissing,
6677               groupKeyWithNoFileMissing);
6678     }
6679 
6680     verify(mockLogger, times(2))
6681         .logEventSampled(
6682             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6683             TEST_GROUP,
6684             /* fileGroupVersionNumber= */ 0,
6685             /* buildId= */ 0,
6686             /* variantId= */ "");
6687     verify(mockLogger)
6688         .logEventSampled(
6689             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
6690             TEST_GROUP_2,
6691             /* fileGroupVersionNumber= */ 0,
6692             /* buildId= */ 0,
6693             /* variantId= */ "");
6694     verifyNoMoreInteractions(mockLogger);
6695   }
6696 
6697   @Test
getOnDeviceUri_shortcutsForSideloadedFiles_delegatesToSharedFileManagerOtherwise()6698   public void getOnDeviceUri_shortcutsForSideloadedFiles_delegatesToSharedFileManagerOtherwise()
6699       throws Exception {
6700     // Ensure that sideloading is turned off
6701     flags.enableSideloading = Optional.of(true);
6702 
6703     // Create mixed group
6704     DataFileGroupInternal sideloadedGroup =
6705         DataFileGroupInternal.newBuilder()
6706             .setGroupName(TEST_GROUP)
6707             .addFile(
6708                 DataFile.newBuilder()
6709                     .setFileId("sideloaded_file")
6710                     .setUrlToDownload("file:/test")
6711                     .setChecksumType(DataFile.ChecksumType.NONE)
6712                     .build())
6713             .addFile(
6714                 DataFile.newBuilder()
6715                     .setFileId("standard_file")
6716                     .setUrlToDownload("https://url.to.download")
6717                     .setChecksumType(DataFile.ChecksumType.NONE)
6718                     .build())
6719             .addFile(
6720                 DataFile.newBuilder()
6721                     .setFileId("inline_file")
6722                     .setUrlToDownload("inlinefile:sha1:checksum")
6723                     .setChecksum("checksum")
6724                     .build())
6725             .build();
6726 
6727     // Write shared files so shared file manager can get uris
6728     NewFileKey[] newFileKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(sideloadedGroup);
6729 
6730     sharedFilesMetadata
6731         .write(
6732             newFileKeys[1],
6733             SharedFile.newBuilder()
6734                 .setFileName(sideloadedGroup.getFile(1).getFileId())
6735                 .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
6736                 .build())
6737         .get();
6738     sharedFilesMetadata
6739         .write(
6740             newFileKeys[2],
6741             SharedFile.newBuilder()
6742                 .setFileName(sideloadedGroup.getFile(2).getFileId())
6743                 .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
6744                 .build())
6745         .get();
6746 
6747     assertThat(
6748             fileGroupManager
6749                 .getOnDeviceUri(sideloadedGroup.getFile(0), sideloadedGroup)
6750                 .get()
6751                 .getScheme())
6752         .isEqualTo("file");
6753     assertThat(
6754             fileGroupManager
6755                 .getOnDeviceUri(sideloadedGroup.getFile(1), sideloadedGroup)
6756                 .get()
6757                 .getScheme())
6758         .isEqualTo("android");
6759     assertThat(
6760             fileGroupManager
6761                 .getOnDeviceUri(sideloadedGroup.getFile(2), sideloadedGroup)
6762                 .get()
6763                 .getScheme())
6764         .isEqualTo("android");
6765   }
6766 
6767   @Test
testAddGroupForDownload_withExperimentationConfig()6768   public void testAddGroupForDownload_withExperimentationConfig() throws Exception {
6769     flags.enableDownloadStageExperimentIdPropagation = Optional.of(true);
6770 
6771     Long buildId = 999L;
6772     Integer experimentId = 12345;
6773     DataFileGroupInternal dataFileGroup =
6774         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6775             .setBuildId(buildId)
6776             .build();
6777 
6778     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
6779   }
6780 
6781   @Test
testAddGroupForDownload_withExperimentationConfig_overwritesPendingExperimentIds()6782   public void testAddGroupForDownload_withExperimentationConfig_overwritesPendingExperimentIds()
6783       throws Exception {
6784     flags.enableDownloadStageExperimentIdPropagation = Optional.of(true);
6785 
6786     long buildId = 999L;
6787     long buildId2 = 100L;
6788     int experimentId = 12345;
6789     int experimentId2 = 23456;
6790 
6791     DataFileGroupInternal dataFileGroup =
6792         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6793             .setBuildId(buildId)
6794             .build();
6795 
6796     DataFileGroupInternal dataFileGroup2 =
6797         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6798             .setBuildId(buildId2)
6799             .build();
6800 
6801     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
6802     // Overwrite the group. The old experiment id should be deleted and the new experiment id should
6803     // be populated.
6804     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup2).get()).isTrue();
6805   }
6806 
6807   @Test
testDownloadPendingGroup_withExperimentationConfig_updatesExperimentIdToDownloaded()6808   public void testDownloadPendingGroup_withExperimentationConfig_updatesExperimentIdToDownloaded()
6809       throws Exception {
6810     flags.enableDownloadStageExperimentIdPropagation = Optional.of(true);
6811 
6812     int experimentIdDownloading = 12345;
6813     int experimentIdDownloaded = 23456;
6814     long buildId = 999L;
6815 
6816     ExtraHttpHeader extraHttpHeader =
6817         ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build();
6818 
6819     // Write 1 group to the pending shared prefs.
6820     DataFileGroupInternal fileGroup =
6821         createDataFileGroup(
6822                 TEST_GROUP,
6823                 /* fileCount= */ 2,
6824                 /* downloadAttemptCount= */ 3,
6825                 /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L)
6826             .toBuilder()
6827             .setBuildId(buildId)
6828             .setOwnerPackage(context.getPackageName())
6829             .setDownloadConditions(DownloadConditions.getDefaultInstance())
6830             .setTrafficTag(TRAFFIC_TAG)
6831             .addGroupExtraHttpHeaders(extraHttpHeader)
6832             .build();
6833 
6834     writePendingFileGroup(testKey, fileGroup);
6835 
6836     writeSharedFiles(
6837         sharedFilesMetadata,
6838         fileGroup,
6839         ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE));
6840 
6841     fileGroupManager
6842         .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation())
6843         .get();
6844   }
6845 
6846   @Test
testRemoveFileGroup_withExperimentationConfig_removesExperimentIds()6847   public void testRemoveFileGroup_withExperimentationConfig_removesExperimentIds()
6848       throws Exception {
6849     flags.enableDownloadStageExperimentIdPropagation = Optional.of(true);
6850 
6851     long buildId = 999L;
6852     int experimentId = 12345;
6853     DataFileGroupInternal dataFileGroup =
6854         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6855             .setBuildId(buildId)
6856             .build();
6857 
6858     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
6859     fileGroupManager.removeFileGroup(testKey, /* pendingOnly= */ false).get();
6860   }
6861 
6862   @Test
testRemoveFileGroups_withExperimentationConfig_removesExperimentIds()6863   public void testRemoveFileGroups_withExperimentationConfig_removesExperimentIds()
6864       throws Exception {
6865     flags.enableDownloadStageExperimentIdPropagation = Optional.of(true);
6866 
6867     long buildId = 999L;
6868     int experimentId = 12345;
6869     DataFileGroupInternal dataFileGroup =
6870         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
6871             .setBuildId(buildId)
6872             .build();
6873 
6874     assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue();
6875     fileGroupManager.removeFileGroups(ImmutableList.of(testKey)).get();
6876   }
6877 
6878   /**
6879    * Re-instantiates {@code fileGroupManager} with the injected parameters.
6880    *
6881    * <p>It can be used to work with the mocks for FileGroupsMetadata and/or SharedFileManager.
6882    */
resetFileGroupManager( FileGroupsMetadata fileGroupsMetadata, SharedFileManager sharedFileManager)6883   private void resetFileGroupManager(
6884       FileGroupsMetadata fileGroupsMetadata, SharedFileManager sharedFileManager) throws Exception {
6885     resetFileGroupManager(this.mockLogger, fileGroupsMetadata, sharedFileManager);
6886   }
6887 
resetFileGroupManager( EventLogger eventLogger, FileGroupsMetadata fileGroupsMetadata, SharedFileManager sharedFileManager)6888   private void resetFileGroupManager(
6889       EventLogger eventLogger,
6890       FileGroupsMetadata fileGroupsMetadata,
6891       SharedFileManager sharedFileManager)
6892       throws Exception {
6893     fileGroupManager =
6894         new FileGroupManager(
6895             context,
6896             eventLogger,
6897             mockSilentFeedback,
6898             fileGroupsMetadata,
6899             sharedFileManager,
6900             testClock,
6901             Optional.of(mockAccountSource),
6902             SEQUENTIAL_CONTROL_EXECUTOR,
6903             Optional.absent(),
6904             fileStorage,
6905             downloadStageManager,
6906             flags);
6907   }
6908 
createFileGroupDetails( DataFileGroupInternal fileGroup)6909   private static DataDownloadFileGroupStats.Builder createFileGroupDetails(
6910       DataFileGroupInternal fileGroup) {
6911     return DataDownloadFileGroupStats.newBuilder()
6912         .setOwnerPackage(fileGroup.getOwnerPackage())
6913         .setFileGroupName(fileGroup.getGroupName())
6914         .setFileGroupVersionNumber(fileGroup.getFileGroupVersionNumber())
6915         .setBuildId(fileGroup.getBuildId())
6916         .setVariantId(fileGroup.getVariantId())
6917         .setFileCount(fileGroup.getFileCount());
6918   }
6919 
createMddDownloadLatency( int downloadAttemptCount, long downloadLatencyMs, long totalLatencyMs)6920   private static Void createMddDownloadLatency(
6921       int downloadAttemptCount, long downloadLatencyMs, long totalLatencyMs) {
6922     return null;
6923   }
6924 
createDataFileGroup( String groupName, int fileCount, int downloadAttemptCount, long newFilesReceivedTimestamp)6925   private static DataFileGroupInternal createDataFileGroup(
6926       String groupName, int fileCount, int downloadAttemptCount, long newFilesReceivedTimestamp) {
6927     return MddTestUtil.createDataFileGroupInternal(groupName, fileCount).toBuilder()
6928         .setBookkeeping(
6929             DataFileGroupBookkeeping.newBuilder()
6930                 .setDownloadStartedCount(downloadAttemptCount)
6931                 .setGroupNewFilesReceivedTimestamp(newFilesReceivedTimestamp))
6932         .build();
6933   }
6934 
6935   /** The file download succeeds so the new file status is DOWNLOAD_COMPLETE. */
fileDownloadSucceeds(NewFileKey key, Uri fileUri)6936   private void fileDownloadSucceeds(NewFileKey key, Uri fileUri) {
6937     when(mockDownloader.startDownloading(
6938             any(String.class),
6939             any(GroupKey.class),
6940             anyInt(),
6941             anyLong(),
6942             any(String.class),
6943             eq(fileUri),
6944             any(String.class),
6945             anyInt(),
6946             any(DownloadConditions.class),
6947             isA(DownloaderCallbackImpl.class),
6948             anyInt(),
6949             anyList()))
6950         .then(
6951             new Answer<ListenableFuture<Void>>() {
6952               @Override
6953               public ListenableFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
6954                 SharedFile sharedFile =
6955                     sharedFileManager.getSharedFile(key).get().toBuilder()
6956                         .setFileStatus(FileStatus.DOWNLOAD_COMPLETE)
6957                         .build();
6958                 sharedFilesMetadata.write(key, sharedFile).get();
6959                 return Futures.immediateVoidFuture();
6960               }
6961             });
6962   }
6963 
6964   /**
6965    * The file download fails so the new file status is DOWNLOAD_FAILED. If failureCode is not null,
6966    * the downloader returns a immediateFailedFuture; otherwise it returns an immediateVoidFuture.
6967    */
fileDownloadFails(NewFileKey key, Uri fileUri, DownloadResultCode failureCode)6968   private void fileDownloadFails(NewFileKey key, Uri fileUri, DownloadResultCode failureCode) {
6969     when(mockDownloader.startDownloading(
6970             any(String.class),
6971             any(GroupKey.class),
6972             anyInt(),
6973             anyLong(),
6974             any(String.class),
6975             eq(fileUri),
6976             any(String.class),
6977             anyInt(),
6978             any(DownloadConditions.class),
6979             isA(DownloaderCallbackImpl.class),
6980             anyInt(),
6981             anyList()))
6982         .then(
6983             new Answer<ListenableFuture<Void>>() {
6984               @Override
6985               public ListenableFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
6986                 SharedFile sharedFile =
6987                     sharedFileManager.getSharedFile(key).get().toBuilder()
6988                         .setFileStatus(FileStatus.DOWNLOAD_FAILED)
6989                         .build();
6990                 sharedFilesMetadata.write(key, sharedFile).get();
6991                 if (failureCode == null) {
6992                   return Futures.immediateVoidFuture();
6993                 }
6994                 return Futures.immediateFailedFuture(
6995                     DownloadException.builder().setDownloadResultCode(failureCode).build());
6996               }
6997             });
6998   }
6999 
readPendingFileGroup(GroupKey key)7000   private DataFileGroupInternal readPendingFileGroup(GroupKey key) throws Exception {
7001     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build();
7002     return fileGroupsMetadata.read(duplicateGroupKey).get();
7003   }
7004 
readDownloadedFileGroup(GroupKey key)7005   private DataFileGroupInternal readDownloadedFileGroup(GroupKey key) throws Exception {
7006     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build();
7007     return fileGroupsMetadata.read(duplicateGroupKey).get();
7008   }
7009 
writePendingFileGroup(GroupKey key, DataFileGroupInternal group)7010   private void writePendingFileGroup(GroupKey key, DataFileGroupInternal group) throws Exception {
7011     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build();
7012     fileGroupsMetadata.write(duplicateGroupKey, group).get();
7013   }
7014 
writeDownloadedFileGroup(GroupKey key, DataFileGroupInternal group)7015   private void writeDownloadedFileGroup(GroupKey key, DataFileGroupInternal group)
7016       throws Exception {
7017     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build();
7018     fileGroupsMetadata.write(duplicateGroupKey, group).get();
7019   }
7020 
verifyAddGroupForDownloadWritesMetadata( GroupKey key, DataFileGroupInternal group, long expectedTimestamp)7021   private void verifyAddGroupForDownloadWritesMetadata(
7022       GroupKey key, DataFileGroupInternal group, long expectedTimestamp) throws Exception {
7023     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build();
7024 
7025     DataFileGroupInternal updatedFileGroup =
7026         setReceivedTimeStampWithFeatureOn(group, expectedTimestamp);
7027     assertThat(fileGroupsMetadata.read(duplicateGroupKey).get()).isEqualTo(updatedFileGroup);
7028   }
7029 
getPendingKey(GroupKey key)7030   private static GroupKey getPendingKey(GroupKey key) {
7031     return key.toBuilder().setDownloaded(false).build();
7032   }
7033 
getDownloadedKey(GroupKey key)7034   private static GroupKey getDownloadedKey(GroupKey key) {
7035     return key.toBuilder().setDownloaded(true).build();
7036   }
7037 
setReceivedTimeStampWithFeatureOn( DataFileGroupInternal dataFileGroup, long elapsedTime)7038   private static DataFileGroupInternal setReceivedTimeStampWithFeatureOn(
7039       DataFileGroupInternal dataFileGroup, long elapsedTime) {
7040     DataFileGroupBookkeeping bookkeeping =
7041         dataFileGroup.getBookkeeping().toBuilder()
7042             .setGroupNewFilesReceivedTimestamp(elapsedTime)
7043             .build();
7044     return dataFileGroup.toBuilder().setBookkeeping(bookkeeping).build();
7045   }
7046 
7047   /**
7048    * Simulates the download of the file {@code dataFile} by writing a file with name {@code
7049    * fileName}.
7050    */
simulateDownload(DataFile dataFile, String fileName)7051   private File simulateDownload(DataFile dataFile, String fileName) throws IOException {
7052     File onDeviceFile = new File(publicDirectory, fileName);
7053     byte[] bytes = new byte[dataFile.getByteSize()];
7054     try (FileOutputStream writer = new FileOutputStream(onDeviceFile)) {
7055       writer.write(bytes);
7056     }
7057     return onDeviceFile;
7058   }
7059 
getOnDeviceUrisForFileGroup(DataFileGroupInternal fileGroup)7060   private List<Uri> getOnDeviceUrisForFileGroup(DataFileGroupInternal fileGroup) {
7061     ArrayList<Uri> uriList = new ArrayList<>(fileGroup.getFileCount());
7062     NewFileKey[] newFileKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup);
7063 
7064     for (int i = 0; i < newFileKeys.length; i++) {
7065       NewFileKey newFileKey = newFileKeys[i];
7066       DataFile dataFile = fileGroup.getFile(i);
7067       uriList.add(
7068           DirectoryUtil.getOnDeviceUri(
7069               context,
7070               newFileKey.getAllowedReaders(),
7071               dataFile.getFileId(),
7072               newFileKey.getChecksum(),
7073               mockSilentFeedback,
7074               /* instanceId= */ Optional.absent(),
7075               /* androidShared= */ false));
7076     }
7077     return uriList;
7078   }
7079 
noCustomValidation()7080   private AsyncFunction<DataFileGroupInternal, Boolean> noCustomValidation() {
7081     return unused -> Futures.immediateFuture(true);
7082   }
7083 }
7084