blob: 416a63a93fbaedceab4f8d2d3a46bc5c92d2f94b [file] [log] [blame]
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.libraries.mobiledatadownload.internal;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.libraries.mobiledatadownload.DownloadException;
import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
import com.google.android.libraries.mobiledatadownload.FileSource;
import com.google.android.libraries.mobiledatadownload.SilentFeedback;
import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri;
import com.google.android.libraries.mobiledatadownload.internal.FileGroupManager.GroupDownloadStatus;
import com.google.android.libraries.mobiledatadownload.internal.Migrations.FileKeyVersion;
import com.google.android.libraries.mobiledatadownload.internal.experimentation.DownloadStageManager;
import com.google.android.libraries.mobiledatadownload.internal.experimentation.NoOpDownloadStageManager;
import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger;
import com.google.android.libraries.mobiledatadownload.internal.logging.FileGroupStatsLogger;
import com.google.android.libraries.mobiledatadownload.internal.logging.LoggingStateStore;
import com.google.android.libraries.mobiledatadownload.internal.logging.NetworkLogger;
import com.google.android.libraries.mobiledatadownload.internal.logging.NoOpLoggingState;
import com.google.android.libraries.mobiledatadownload.internal.logging.StorageLogger;
import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil;
import com.google.android.libraries.mobiledatadownload.internal.util.SharedPreferencesUtil;
import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.labs.concurrent.LabsFutures;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.mobiledatadownload.TransformProto.CompressTransform;
import com.google.mobiledatadownload.TransformProto.Transform;
import com.google.mobiledatadownload.TransformProto.Transforms;
import com.google.mobiledatadownload.TransformProto.ZipTransform;
import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
import com.google.mobiledatadownload.internal.MetadataProto.DataFile.ChecksumType;
import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions;
import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy;
import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceStoragePolicy;
import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.LooperMode;
// The LooperMode Mode.PAUSED fixes buggy behavior in the legacy looper implementation that can lead
// to deadlock in some cases. See documentation at:
// http://robolectric.org/javadoc/4.3/org/robolectric/annotation/LooperMode.Mode.html for more
// information.
@RunWith(RobolectricTestRunner.class)
@LooperMode(LooperMode.Mode.PAUSED)
public class MobileDataDownloadManagerTest {
private static final String TEST_GROUP = "test-group";
private static final GroupKey TEST_KEY =
FileGroupUtil.createGroupKey(TEST_GROUP, "com.google.android.gms");
private static final Executor CONTROL_EXECUTOR = Executors.newCachedThreadPool();
private static final int DEFAULT_DAYS_SINCE_LAST_LOG = 1;
// Note: We can't make those android uris static variable since the Uri.parse will fail
// with initialization.
private final Uri fileUri1 = Uri.parse(MddTestUtil.FILE_URI + "1");
private final Uri fileUri2 = Uri.parse(MddTestUtil.FILE_URI + "2");
private static final String HOST_APP_LOG_SOURCE = "HOST_APP_LOG_SOURCE";
private static final String HOST_APP_PRIMES_LOG_SOURCE = "HOST_APP_PRIMES_LOG_SOURCE";
private Context context;
private MobileDataDownloadManager mddManager;
private final TestFlags flags = new TestFlags();
@Rule public final TemporaryUri tmpUri = new TemporaryUri();
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
@Mock EventLogger mockLogger;
@Mock SharedFileManager mockSharedFileManager;
@Mock SharedFilesMetadata mockSharedFilesMetadata;
@Mock FileGroupManager mockFileGroupManager;
@Mock FileGroupsMetadata mockFileGroupsMetadata;
@Mock ExpirationHandler mockExpirationHandler;
@Mock SilentFeedback mockSilentFeedback;
@Mock StorageLogger mockStorageLogger;
@Mock FileGroupStatsLogger mockFileGroupStatsLogger;
@Mock NetworkLogger mockNetworkLogger;
private LoggingStateStore loggingStateStore;
private DownloadStageManager downloadStageManager;
private FakeTimeSource testClock;
@Captor ArgumentCaptor<List<GroupKey>> groupKeyListCaptor;
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
this.testClock = new FakeTimeSource();
testClock.advance(1, DAYS);
loggingStateStore = new NoOpLoggingState();
loggingStateStore.getAndResetDaysSinceLastMaintenance().get();
testClock.advance(1, DAYS); // The next call into logging state store will return 1
downloadStageManager = new NoOpDownloadStageManager();
mddManager =
new MobileDataDownloadManager(
context,
mockLogger,
mockSharedFileManager,
mockSharedFilesMetadata,
mockFileGroupManager,
mockFileGroupsMetadata,
mockExpirationHandler,
mockSilentFeedback,
mockStorageLogger,
mockFileGroupStatsLogger,
mockNetworkLogger,
Optional.absent(),
CONTROL_EXECUTOR,
flags,
loggingStateStore,
downloadStageManager);
// Enable migrations so that init doesn't run all migrations before each test.
setMigrationState(MobileDataDownloadManager.MDD_MIGRATED_TO_OFFROAD, true);
when(mockSharedFileManager.init()).thenReturn(Futures.immediateFuture(true));
when(mockSharedFileManager.clear()).thenReturn(Futures.immediateFuture(null));
when(mockSharedFileManager.cancelDownload(any())).thenReturn(Futures.immediateFuture(null));
when(mockSharedFileManager.cancelDownloadAndClear()).thenReturn(Futures.immediateFuture(null));
when(mockSharedFilesMetadata.init()).thenReturn(Futures.immediateFuture(true));
when(mockFileGroupsMetadata.init()).thenReturn(Futures.immediateFuture(null));
when(mockFileGroupsMetadata.clear()).thenReturn(Futures.immediateFuture(null));
when(mockSharedFilesMetadata.clear()).thenReturn(Futures.immediateFuture(null));
}
@After
public void tearDown() throws Exception {
mddManager.clear().get();
}
@Test
public void init_offroadDownloaderMigration() throws Exception {
setMigrationState(MobileDataDownloadManager.MDD_MIGRATED_TO_OFFROAD, false);
mddManager.init().get();
verify(mockSharedFileManager).clear();
}
@Test
public void init_offroadDownloaderMigration_onlyOnce() throws Exception {
setMigrationState(MobileDataDownloadManager.MDD_MIGRATED_TO_OFFROAD, false);
mddManager.init().get();
mddManager.init().get();
verify(mockSharedFileManager, times(1)).clear();
}
@Test
public void initDoesNotClearsIfInternalInitSucceeds() throws Exception {
when(mockSharedFileManager.init()).thenReturn(Futures.immediateFuture(true));
mddManager.init().get();
verify(mockSharedFileManager, times(0)).clear();
}
@Test
public void initClearsIfInternalInitFails() throws Exception {
when(mockSharedFileManager.init()).thenReturn(Futures.immediateFuture(false));
mddManager.init().get();
verify(mockSharedFileManager).clear();
}
@Test
public void testAddGroupForDownload() throws Exception {
// This tests that the default value of {allowed_readers, allowed_readers_enum} is to allow
// access to all 1p google apps.
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenReturn(Futures.immediateFuture(true));
when(mockFileGroupManager.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
verify(mockFileGroupManager)
.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any());
verifyNoInteractions(mockLogger);
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
}
@Test
public void testAddGroupForDownload_compressedFile() throws Exception {
Migrations.setCurrentVersion(context, FileKeyVersion.USE_CHECKSUM_ONLY);
DataFileGroupInternal.Builder fileGroupBuilder =
MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
DataFileGroupInternal dataFileGroup =
fileGroupBuilder
.setFile(
0,
fileGroupBuilder.getFile(0).toBuilder()
.setDownloadedFileChecksum("downloadchecksum")
.setDownloadTransforms(
Transforms.newBuilder()
.addTransform(
Transform.newBuilder()
.setCompress(CompressTransform.getDefaultInstance()))))
.build();
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenReturn(Futures.immediateFuture(true));
when(mockFileGroupManager.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
verify(mockFileGroupManager)
.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any());
verifyNoInteractions(mockLogger);
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
}
@Test
public void testAddGroupForDownload_deltaFile() throws Exception {
Migrations.setCurrentVersion(context, FileKeyVersion.USE_CHECKSUM_ONLY);
DataFileGroupInternal dataFileGroup =
MddTestUtil.createFileGroupInternalWithDeltaFile(TEST_GROUP);
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenReturn(Futures.immediateFuture(true));
when(mockFileGroupManager.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
verify(mockFileGroupManager)
.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any());
verifyNoInteractions(mockLogger);
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
}
@Test
public void testAddGroupForDownload_downloadImmediate() throws Exception {
// This tests that the default value of {allowed_readers, allowed_readers_enum} is to allow
// access to all 1p google apps.
DataFileGroupInternal dataFileGroup =
MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
.setVariantId("testVariant")
.setBuildId(10)
.build();
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenReturn(Futures.immediateFuture(true));
when(mockFileGroupManager.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.DOWNLOADED));
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
verify(mockFileGroupManager)
.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any());
verify(mockLogger)
.logEventSampled(
0,
TEST_GROUP,
/* fileGroupVersionNumber= */ 0,
/* buildId= */ dataFileGroup.getBuildId(),
/* variantId= */ dataFileGroup.getVariantId());
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
}
@Test
public void testAddGroupForDownload_throwsIOException() throws Exception {
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenThrow(new IOException());
// assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
ExecutionException exception =
assertThrows(
ExecutionException.class,
() -> mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get());
assertThat(exception).hasCauseThat().isInstanceOf(IOException.class);
verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
verify(mockSilentFeedback).send(isA(IOException.class), isA(String.class));
verifyNoInteractions(mockLogger);
}
@Test
public void testAddGroupForDownload_throwsUninstalledAppException() throws Exception {
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenThrow(new UninstalledAppException());
// assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
ExecutionException exception =
assertThrows(
ExecutionException.class,
() -> mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get());
assertThat(exception).hasCauseThat().isInstanceOf(UninstalledAppException.class);
verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
verifyNoInteractions(mockLogger);
}
@Test
public void testAddGroupForDownload_throwsExpiredFileGroupException() throws Exception {
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenThrow(new ExpiredFileGroupException());
// assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
ExecutionException exception =
assertThrows(
ExecutionException.class,
() -> mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get());
assertThat(exception).hasCauseThat().isInstanceOf(ExpiredFileGroupException.class);
verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
verifyNoInteractions(mockLogger);
}
@Test
public void testAddGroupForDownload_multipleCallsSameGroup() throws Exception {
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
.thenReturn(Futures.immediateFuture(true), Futures.immediateFuture(false));
when(mockFileGroupManager.verifyPendingGroupDownloaded(
eq(TEST_KEY), any(DataFileGroupInternal.class), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
verify(mockFileGroupManager, times(2)).addGroupForDownload(TEST_KEY, dataFileGroup);
verify(mockFileGroupManager, times(1))
.verifyPendingGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), any());
verifyNoInteractions(mockExpirationHandler);
verifyNoInteractions(mockLogger);
}
@Test
public void testAddGroupForDownload_isValidGroup() throws Exception {
DataFileGroupInternal dataFileGroup =
MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
.setGroupName("")
.setVariantId("testVariant")
.setBuildId(10)
.build();
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
verifyNoInteractions(mockFileGroupManager);
verify(mockLogger)
.logEventSampled(
0,
"",
/* fileGroupVersionNumber= */ 0,
/* buildId= */ dataFileGroup.getBuildId(),
/* variantId= */ dataFileGroup.getVariantId());
}
@Test
public void testAddGroupForDownload_noChecksum() throws Exception {
DataFileGroupInternal.Builder fileGroupBuilder =
MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
DataFileGroupInternal dataFileGroup =
fileGroupBuilder
.setFile(
0,
fileGroupBuilder.getFile(0).toBuilder()
.setChecksumType(ChecksumType.NONE)
.setChecksum(""))
.build();
ArgumentCaptor<DataFileGroupInternal> dataFileGroupCaptor =
ArgumentCaptor.forClass(DataFileGroupInternal.class);
when(mockFileGroupManager.addGroupForDownload(eq(TEST_KEY), dataFileGroupCaptor.capture()))
.thenReturn(Futures.immediateFuture(true));
when(mockFileGroupManager.verifyPendingGroupDownloaded(
eq(TEST_KEY), any(DataFileGroupInternal.class), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
verifyNoInteractions(mockLogger);
DataFileGroupInternal capturedDataFileGroup = dataFileGroupCaptor.getValue();
assertThat(capturedDataFileGroup.getFileCount()).isEqualTo(1);
DataFile dataFile = capturedDataFileGroup.getFile(0);
// Checksum of the Url.
assertThat(dataFile.getChecksum()).isEqualTo("0d79849a839d83fbc53e3bfe794ec38a305b7220");
}
@Test
public void testAddGroupForDownload_noChecksumWithZipTransform() throws Exception {
DataFileGroupInternal.Builder fileGroupBuilder =
MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
DataFileGroupInternal dataFileGroup =
fileGroupBuilder
.setFile(
0,
fileGroupBuilder.getFile(0).toBuilder()
.setChecksumType(ChecksumType.NONE)
.setChecksum("")
.setDownloadedFileChecksum("")
.setDownloadTransforms(
Transforms.newBuilder()
.addTransform(
Transform.newBuilder()
.setZip(ZipTransform.newBuilder().setTarget("*")))))
.build();
ArgumentCaptor<DataFileGroupInternal> dataFileGroupCaptor =
ArgumentCaptor.forClass(DataFileGroupInternal.class);
when(mockFileGroupManager.addGroupForDownload(eq(TEST_KEY), dataFileGroupCaptor.capture()))
.thenReturn(Futures.immediateFuture(true));
when(mockFileGroupManager.verifyPendingGroupDownloaded(
eq(TEST_KEY), any(DataFileGroupInternal.class), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
verifyNoInteractions(mockLogger);
DataFileGroupInternal capturedDataFileGroup = dataFileGroupCaptor.getValue();
assertThat(capturedDataFileGroup.getFileCount()).isEqualTo(1);
DataFile dataFile = capturedDataFileGroup.getFile(0);
// Checksum of url is propagated to downloaded file checksum if data file has zip transform.
assertThat(dataFile.getChecksum()).isEmpty();
assertThat(dataFile.getDownloadedFileChecksum())
.isEqualTo("0d79849a839d83fbc53e3bfe794ec38a305b7220");
}
@Test
public void testAddGroupForDownload_noChecksumAndNotSetChecksumType() throws Exception {
DataFileGroupInternal.Builder fileGroupBuilder =
MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
// Not setting ChecksumType.NONE
DataFileGroupInternal dataFileGroup =
fileGroupBuilder
.setFile(0, fileGroupBuilder.getFile(0).toBuilder().setChecksum(""))
.build();
assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
verify(mockLogger)
.logEventSampled(
0, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ "");
verifyNoInteractions(mockFileGroupManager);
}
@Test
public void testAddGroupForDownload_sideloadedFile_onlyWhenSideloadingIsEnabled()
throws Exception {
// Create sideloaded group
DataFileGroupInternal sideloadedGroup =
DataFileGroupInternal.newBuilder()
.setGroupName(TEST_GROUP)
.addFile(
DataFile.newBuilder()
.setFileId("sideloaded_file")
.setUrlToDownload("file:/test")
.setChecksumType(DataFile.ChecksumType.NONE)
.build())
.build();
when(mockFileGroupManager.addGroupForDownload(eq(TEST_KEY), any()))
.thenReturn(Futures.immediateFuture(true));
when(mockFileGroupManager.verifyPendingGroupDownloaded(eq(TEST_KEY), any(), any()))
.thenReturn(Futures.immediateFuture(GroupDownloadStatus.DOWNLOADED));
{
// Force sideloading off
flags.enableSideloading = Optional.of(false);
assertThat(mddManager.addGroupForDownload(TEST_KEY, sideloadedGroup).get()).isFalse();
}
{
// Force sideloading on
flags.enableSideloading = Optional.of(true);
assertThat(mddManager.addGroupForDownload(TEST_KEY, sideloadedGroup).get()).isTrue();
}
}
@Test
public void testRemoveFileGroup() throws Exception {
GroupKey groupKey =
GroupKey.newBuilder()
.setGroupName(TEST_GROUP)
.setOwnerPackage(context.getPackageName())
.build();
when(mockFileGroupManager.removeFileGroup(eq(groupKey), eq(false)))
.thenReturn(Futures.immediateFuture(null /* Void */));
mddManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get();
verify(mockFileGroupManager).removeFileGroup(groupKey, /* pendingOnly= */ false);
verifyNoMoreInteractions(mockFileGroupManager);
verifyNoInteractions(mockLogger);
}
@Test
public void testRemoveFileGroup_onFailure() throws Exception {
GroupKey groupKey =
GroupKey.newBuilder()
.setGroupName(TEST_GROUP)
.setOwnerPackage(context.getPackageName())
.build();
doThrow(new IOException())
.when(mockFileGroupManager)
.removeFileGroup(groupKey, /* pendingOnly= */ false);
ExecutionException ex =
assertThrows(
ExecutionException.class,
mddManager.removeFileGroup(groupKey, /* pendingOnly= */ false)::get);
assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);
verify(mockFileGroupManager).removeFileGroup(groupKey, /* pendingOnly= */ false);
verifyNoMoreInteractions(mockFileGroupManager);
verifyNoInteractions(mockLogger);
}
@Test
public void testRemoveFileGroups() throws Exception {
GroupKey groupKey1 =
GroupKey.newBuilder()
.setGroupName(TEST_GROUP)
.setOwnerPackage(context.getPackageName())
.build();
GroupKey groupKey2 =
GroupKey.newBuilder()
.setGroupName(TEST_GROUP + "_2")
.setOwnerPackage(context.getPackageName())
.build();
when(mockFileGroupManager.removeFileGroups(groupKeyListCaptor.capture()))
.thenReturn(Futures.immediateVoidFuture());
mddManager.removeFileGroups(ImmutableList.of(groupKey1, groupKey2)).get();
verify(mockFileGroupManager).removeFileGroups(anyList());
List<GroupKey> groupKeyListCapture = groupKeyListCaptor.getValue();
assertThat(groupKeyListCapture).hasSize(2);
assertThat(groupKeyListCapture).contains(groupKey1);
assertThat(groupKeyListCapture).contains(groupKey2);
}
@Test
public void testRemoveFileGroups_onFailure() throws Exception {
GroupKey groupKey1 =
GroupKey.newBuilder()
.setGroupName(TEST_GROUP)
.setOwnerPackage(context.getPackageName())
.build();
GroupKey groupKey2 =
GroupKey.newBuilder()
.setGroupName(TEST_GROUP + "_2")
.setOwnerPackage(context.getPackageName())
.build();
when(mockFileGroupManager.removeFileGroups(groupKeyListCaptor.capture()))
.thenReturn(Futures.immediateFailedFuture(new Exception("Test failure")));
ExecutionException ex =
assertThrows(
ExecutionException.class,
() -> mddManager.removeFileGroups(ImmutableList.of(groupKey1, groupKey2)).get());
assertThat(ex).hasMessageThat().contains("Test failure");
verify(mockFileGroupManager).removeFileGroups(anyList());
List<GroupKey> groupKeyListCapture = groupKeyListCaptor.getValue();
assertThat(groupKeyListCapture).hasSize(2);
assertThat(groupKeyListCapture).contains(groupKey1);
assertThat(groupKeyListCapture).contains(groupKey2);
}
@Test
public void testGetDownloadedGroup() throws Exception {
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
when(mockFileGroupManager.getFileGroup(TEST_KEY, true))
.thenReturn(Futures.immediateFuture(dataFileGroup));
DataFileGroupInternal completedDataFileGroup = mddManager.getFileGroup(TEST_KEY, true).get();
MddTestUtil.assertMessageEquals(dataFileGroup, completedDataFileGroup);
verifyNoInteractions(mockLogger);
}
@Test
public void testGetDataFileUri() throws Exception {
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
when(mockFileGroupManager.getOnDeviceUri(dataFileGroup.getFile(0), dataFileGroup))
.thenReturn(Futures.immediateFuture(fileUri1));
when(mockFileGroupManager.getOnDeviceUri(dataFileGroup.getFile(1), dataFileGroup))
.thenReturn(Futures.immediateFuture(fileUri2));
assertThat(mddManager.getDataFileUri(dataFileGroup.getFile(0), dataFileGroup).get())
.isEqualTo(fileUri1);
assertThat(mddManager.getDataFileUri(dataFileGroup.getFile(1), dataFileGroup).get())
.isEqualTo(fileUri2);
}
@Test
public void testGetDataFileUri_readTransform() throws Exception {
DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
Transforms compressTransform =
Transforms.newBuilder()
.addTransform(
Transform.newBuilder().setCompress(CompressTransform.getDefaultInstance()))
.build();
dataFileGroup =
dataFileGroup.toBuilder()
.setFile(0, dataFileGroup.getFile(0).toBuilder().setReadTransforms(compressTransform))
.build();
when(mockFileGroupManager.getOnDeviceUri(dataFileGroup.getFile(0), dataFileGroup))
.thenReturn(Futures.immediateFuture(fileUri1));
when(mockFileGroupManager.getOnDeviceUri(dataFileGroup.getFile(1), dataFileGroup))
.thenReturn(Futures.immediateFuture(fileUri2));
assertThat(mddManager.getDataFileUri(dataFileGroup.getFile(0), dataFileGroup).get())
.isEqualTo(fileUri1.buildUpon().encodedFragment("transform=compress").build());
assertThat(mddManager.getDataFileUri(dataFileGroup.getFile(1), dataFileGroup).get())
.isEqualTo(fileUri2);
}
@Test
public void testGetDataFileUri_relativeFilePaths() throws Exception {
DataFile relativePathFile = MddTestUtil.createRelativePathDataFile("file", 1, "test");
DataFileGroupInternal testFileGroup =
DataFileGroupInternal.newBuilder()
.setGroupName(TEST_GROUP)
.setPreserveFilenamesAndIsolateFiles(true)
.addFile(relativePathFile)
.build();
Uri symlinkedUri =
FileGroupUtil.getIsolatedFileUri(
context, Optional.absent(), relativePathFile, testFileGroup);
when(mockFileGroupManager.getOnDeviceUri(testFileGroup.getFile(0), testFileGroup))
.thenReturn(Futures.immediateFuture(fileUri1));
when(mockFileGroupManager.getAndVerifyIsolatedFileUri(
fileUri1, relativePathFile, testFileGroup))
.thenReturn(symlinkedUri);
assertThat(mddManager.getDataFileUri(relativePathFile, testFileGroup).get())
.isEqualTo(symlinkedUri);
}
@Test
public void testGetDataFileUri_whenSymlinkRequiredButNotPresent_returnsNull() throws Exception {
DataFile relativePathFile = MddTestUtil.createRelativePathDataFile("file", 1, "test");
DataFileGroupInternal testFileGroup =
DataFileGroupInternal.newBuilder()
.setGroupName(TEST_GROUP)
.setPreserveFilenamesAndIsolateFiles(true)
.addFile(relativePathFile)
.build();
when(mockFileGroupManager.getOnDeviceUri(testFileGroup.getFile(0), testFileGroup))
.thenReturn(Futures.immediateFuture(fileUri1));
when(mockFileGroupManager.getAndVerifyIsolatedFileUri(
fileUri1, relativePathFile, testFileGroup))
.thenThrow(new IOException("test failure"));
assertThat(mddManager.getDataFileUri(relativePathFile, testFileGroup).get()).isNull();
}
@Test
public void testImportFiles_failed() throws Exception {
ImmutableList<DataFile> updatedDataFileList =
ImmutableList.of(
MddTestUtil.createDataFile("inline-file", 0).toBuilder()
.setUrlToDownload("inlinefile:sha1:abcdef")
.build());
ImmutableMap<String, FileSource> inlineFileMap =
ImmutableMap.of(
"inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
when(mockFileGroupManager.importFilesIntoFileGroup(
eq(TEST_KEY), anyLong(), any(), any(), any(), any(), any()))
.thenReturn(Futures.immediateFailedFuture(new Exception("Test failure")));
ExecutionException ex =
assertThrows(
ExecutionException.class,
() ->
mddManager
.importFiles(
TEST_KEY,
1,
"testvariant",
updatedDataFileList,
inlineFileMap,
Optional.absent(),
noCustomValidation())
.get());
assertThat(ex).hasMessageThat().contains("Test failure");
verify(mockFileGroupManager)
.importFilesIntoFileGroup(
eq(TEST_KEY),
anyLong(),
any(),
eq(updatedDataFileList),
eq(inlineFileMap),
any(),
any());
}
@Test
public void testImportFiles_succeeds() throws Exception {
ImmutableList<DataFile> updatedDataFileList =
ImmutableList.of(
MddTestUtil.createDataFile("inline-file", 0).toBuilder()
.setUrlToDownload("inlinefile:sha1:abcdef")
.build());
ImmutableMap<String, FileSource> inlineFileMap =
ImmutableMap.of(
"inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
when(mockFileGroupManager.importFilesIntoFileGroup(
eq(TEST_KEY), anyLong(), any(), any(), any(), any(), any()))
.thenReturn(immediateVoidFuture());
mddManager
.importFiles(
TEST_KEY,
1,
"testvariant",
updatedDataFileList,
inlineFileMap,
Optional.absent(),
noCustomValidation())
.get();
verify(mockFileGroupManager)
.importFilesIntoFileGroup(
eq(TEST_KEY),
anyLong(),
any(),
eq(updatedDataFileList),
eq(inlineFileMap),
any(),
any());
}
@Test
public void testDownloadPendingGroup_failed() {
when(mockFileGroupManager.downloadFileGroup(eq(TEST_KEY), isNull(), any()))
.thenReturn(
Futures.immediateFailedFuture(
DownloadException.builder()
.setDownloadResultCode(DownloadResultCode.UNKNOWN_ERROR)
.setMessage("Fail")
.build()));
ListenableFuture<DataFileGroupInternal> downloadFuture =
mddManager.downloadFileGroup(TEST_KEY, Optional.absent(), noCustomValidation());
assertThrows(ExecutionException.class, downloadFuture::get);
DownloadException unused =
LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
verify(mockFileGroupManager).downloadFileGroup(eq(TEST_KEY), isNull(), any());
}
@Test
public void testDownloadPendingGroup_downloadCondition_absent() throws Exception {
DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
when(mockFileGroupManager.downloadFileGroup(eq(TEST_KEY), isNull(), any()))
.thenReturn(Futures.immediateFuture(pendingGroup));
assertThat(
mddManager.downloadFileGroup(TEST_KEY, Optional.absent(), noCustomValidation()).get())
.isEqualTo(pendingGroup);
verify(mockFileGroupManager).downloadFileGroup(eq(TEST_KEY), isNull(), any());
}
@Test
public void testDownloadPendingGroup_downloadCondition_present() throws Exception {
DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
Optional<DownloadConditions> downloadConditionsOptional =
Optional.of(
DownloadConditions.newBuilder()
.setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK)
.setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE)
.build());
when(mockFileGroupManager.downloadFileGroup(
eq(TEST_KEY), eq(downloadConditionsOptional.get()), any()))
.thenReturn(Futures.immediateFuture(pendingGroup));
assertThat(
mddManager
.downloadFileGroup(TEST_KEY, downloadConditionsOptional, noCustomValidation())
.get())
.isEqualTo(pendingGroup);
verify(mockFileGroupManager)
.downloadFileGroup(eq(TEST_KEY), eq(downloadConditionsOptional.get()), any());
}
@Test
public void testDownloadAllPendingGroups() throws Exception {
when(mockFileGroupManager.scheduleAllPendingGroupsForDownload(eq(true), any()))
.thenReturn(Futures.immediateFuture(null));
mddManager.downloadAllPendingGroups(true, noCustomValidation()).get();
verify(mockLogger).logEventSampled(0);
verify(mockFileGroupManager).scheduleAllPendingGroupsForDownload(eq(true), any());
verifyNoMoreInteractions(mockLogger);
}
@Test
public void testVerifyPendingGroups() throws Exception {
when(mockFileGroupManager.verifyAllPendingGroupsDownloaded(any()))
.thenReturn(Futures.immediateFuture(null));
mddManager.verifyAllPendingGroups(noCustomValidation()).get();
verify(mockFileGroupManager).verifyAllPendingGroupsDownloaded(any());
verify(mockLogger).logEventSampled(0);
verifyNoMoreInteractions(mockLogger);
}
@Test
public void testMaintenance_mddFileExpiration() throws Exception {
setupMaintenanceTasks();
mddManager.maintenance().get();
verify(mockFileGroupManager).deleteUninstalledAppGroups();
verify(mockExpirationHandler).updateExpiration();
verify(mockFileGroupStatsLogger).log(anyInt());
verify(mockLogger).logEventSampled(0);
}
@Test
public void testMaintenance_logStorage() throws Exception {
setupMaintenanceTasks();
mddManager.maintenance().get();
verify(mockFileGroupStatsLogger).log(anyInt());
}
@Test
public void testMaintenance_logNetwork() throws Exception {
setupMaintenanceTasks();
mddManager.maintenance().get();
verify(mockNetworkLogger).log();
}
@Test
public void maintenance_triggerSync_absentSpe() throws Exception {
mddManager =
new MobileDataDownloadManager(
context,
mockLogger,
mockSharedFileManager,
mockSharedFilesMetadata,
mockFileGroupManager,
mockFileGroupsMetadata,
mockExpirationHandler,
mockSilentFeedback,
mockStorageLogger,
mockFileGroupStatsLogger,
mockNetworkLogger,
Optional.absent(),
CONTROL_EXECUTOR,
flags,
loggingStateStore,
downloadStageManager);
setupMaintenanceTasks();
mddManager.maintenance().get();
// With absent SPE, no triggerSync was called.
verify(mockFileGroupManager, never()).triggerSyncAllPendingGroups();
}
@Test
public void testMaintenance_deleteRemovedAccountGroups() throws Exception {
setupMaintenanceTasks();
flags.mddDeleteGroupsRemovedAccounts = Optional.of(true);
mddManager.maintenance().get();
verify(mockFileGroupManager).deleteRemovedAccountGroups();
}
void setupMaintenanceTasks() {
flags.enableDaysSinceLastMaintenanceTracking = Optional.of(true);
when(mockStorageLogger.logStorageStats(anyInt())).thenReturn(Futures.immediateVoidFuture());
when(mockExpirationHandler.updateExpiration()).thenReturn(Futures.immediateVoidFuture());
when(mockFileGroupStatsLogger.log(anyInt())).thenReturn(Futures.immediateVoidFuture());
when(mockNetworkLogger.log()).thenReturn(Futures.immediateVoidFuture());
when(mockFileGroupManager.logAndDeleteForMissingSharedFiles())
.thenReturn(Futures.immediateVoidFuture());
when(mockFileGroupManager.deleteUninstalledAppGroups())
.thenReturn(Futures.immediateVoidFuture());
when(mockFileGroupManager.deleteRemovedAccountGroups())
.thenReturn(Futures.immediateVoidFuture());
when(mockFileGroupManager.triggerSyncAllPendingGroups()).thenReturn(immediateVoidFuture());
when(mockFileGroupManager.verifyAndAttemptToRepairIsolatedFiles())
.thenReturn(immediateVoidFuture());
}
@Test
public void testClear() throws Exception {
mddManager.clear().get();
verify(mockSharedFileManager).cancelDownloadAndClear();
verifyNoInteractions(mockLogger);
}
@Test
public void testCheckResetTrigger_resetTrigger_noIncrement() throws Exception {
setSavedResetValue(1);
flags.mddResetTrigger = Optional.of(1);
mddManager.checkResetTrigger().get();
verify(mockSharedFileManager, never()).clear();
verifyNoInteractions(mockLogger);
// saved reset value should not have changed
checkSavedResetValue(1);
verifyNoInteractions(mockLogger);
}
@Test
public void testCheckResetTrigger_resetTrigger_singleIncrement() throws Exception {
setSavedResetValue(1);
flags.mddResetTrigger = Optional.of(2);
mddManager.checkResetTrigger().get();
verify(mockSharedFileManager).cancelDownloadAndClear();
verify(mockLogger).logEventSampled(0);
// saved reset value should be set to 2
checkSavedResetValue(2);
verifyNoMoreInteractions(mockLogger);
}
@Test
public void testCheckResetTrigger_resetTrigger_singleIncrementMultipleChecks() throws Exception {
setSavedResetValue(1);
flags.mddResetTrigger = Optional.of(2);
mddManager.checkResetTrigger().get();
// The second check should have no effect - clear should only be called once.
mddManager.checkResetTrigger().get();
verify(mockSharedFileManager).cancelDownloadAndClear();
verify(mockLogger).logEventSampled(0);
// saved reset value should be set to 2
checkSavedResetValue(2);
verifyNoMoreInteractions(mockLogger);
}
@Test
public void testCheckResetTrigger_resetTrigger_multipleIncrementMultipleChecks()
throws Exception {
setSavedResetValue(1);
flags.mddResetTrigger = Optional.of(2);
mddManager.checkResetTrigger().get();
flags.mddResetTrigger = Optional.of(3);
mddManager.checkResetTrigger().get();
verify(mockSharedFileManager, times(2)).cancelDownloadAndClear();
verify(mockLogger, times(2)).logEventSampled(0);
// saved reset value should be set to 2
checkSavedResetValue(3);
verifyNoMoreInteractions(mockLogger);
}
private void setMigrationState(String key, boolean value) {
SharedPreferences sharedPreferences =
SharedPreferencesUtil.getSharedPreferences(
context, MobileDataDownloadManager.MDD_MANAGER_METADATA, Optional.absent());
sharedPreferences.edit().putBoolean(key, value).commit();
}
private void setSavedResetValue(int value) {
SharedPreferences prefs =
SharedPreferencesUtil.getSharedPreferences(
context, MobileDataDownloadManager.MDD_MANAGER_METADATA, Optional.absent());
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(MobileDataDownloadManager.RESET_TRIGGER, value);
editor.commit();
}
private void checkSavedResetValue(int expected) {
SharedPreferences prefs =
SharedPreferencesUtil.getSharedPreferences(
context, MobileDataDownloadManager.MDD_MANAGER_METADATA, Optional.absent());
assertThat(prefs.getInt(MobileDataDownloadManager.RESET_TRIGGER, expected - 1))
.isEqualTo(expected);
}
private AsyncFunction<DataFileGroupInternal, Boolean> noCustomValidation() {
return unused -> Futures.immediateFuture(true);
}
}