blob: d9f439b1deafca5e0ce1db68b52e2e9c34f08f40 [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.
*/
// TODO
package com.google.android.libraries.mobiledatadownload.downloader.offroad;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertThrows;
import android.content.Context;
import android.net.Uri;
import android.util.Pair;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.downloader.ConnectivityHandler;
import com.google.android.downloader.DownloadConstraints;
import com.google.android.downloader.DownloadConstraints.NetworkType;
import com.google.android.downloader.DownloadMetadata;
import com.google.android.downloader.Downloader;
import com.google.android.downloader.FloggerDownloaderLogger;
import com.google.android.downloader.OAuthTokenProvider;
import com.google.android.downloader.PlatformUrlEngine;
import com.google.android.downloader.testing.TestUrlEngine;
import com.google.android.downloader.testing.TestUrlEngine.TestUrlRequest;
import com.google.android.libraries.mobiledatadownload.DownloadException;
import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
import com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend;
import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri;
import com.google.android.libraries.mobiledatadownload.file.integration.downloader.DownloadMetadataStore;
import com.google.android.libraries.mobiledatadownload.file.openers.ReadStreamOpener;
import com.google.android.libraries.mobiledatadownload.file.openers.WriteStreamOpener;
import com.google.android.libraries.mobiledatadownload.testing.TestHttpServer;
import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import com.google.common.net.HttpHeaders;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.runtime.RunfilesPaths;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link
* com.google.android.libraries.mobiledatadownload.downloader.offroad.Offroad2FileDownloader}.
*/
@RunWith(RobolectricTestRunner.class)
public class Offroad2FileDownloaderTest {
private static final int TRAFFIC_TAG = 1000;
private static final ScheduledExecutorService DOWNLOAD_EXECUTOR =
Executors.newScheduledThreadPool(2);
private static final ListeningExecutorService CONTROL_EXECUTOR =
MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
private static final long MAX_CONNECTION_WAIT_SECS = 10L;
private static final int MAX_PLATFORM_ENGINE_TIMEOUT_MILLIS = 1000;
/** Endpoint that can be registered to TestHttpServer to serve a file that can be downloaded. */
private static final String TEST_DATA_ENDPOINT = "/testfile";
/** Path to the underlying test data that is the source of what TestHttpServer will serve. */
private static final String TEST_DATA_PATH =
RunfilesPaths.resolve(
"third_party/java_src/android_libs/mobiledatadownload/javatests/com/google/android/libraries/mobiledatadownload/testdata/full_file.txt")
.toString();
private static final String PARTIAL_TEST_DATA_PATH =
RunfilesPaths.resolve(
"third_party/java_src/android_libs/mobiledatadownload/javatests/com/google/android/libraries/mobiledatadownload/testdata/partial_file.txt")
.toString();
private Context context;
private Uri.Builder testUrlPrefix;
private TestHttpServer testHttpServer;
private SynchronousFileStorage fileStorage;
private FakeConnectivityHandler fakeConnectivityHandler;
private FakeDownloadMetadataStore fakeDownloadMetadataStore;
private FakeOAuthTokenProvider fakeOAuthTokenProvider;
private FakeTrafficStatsTagger fakeTrafficStatsTagger;
private TestUrlEngine testUrlEngine;
private Downloader downloader;
private Offroad2FileDownloader fileDownloader;
@Rule public TemporaryUri tmpUri = new TemporaryUri();
@Before
public void setUp() throws Exception {
context = ApplicationProvider.getApplicationContext();
fileStorage =
new SynchronousFileStorage(
/* backends = */ ImmutableList.of(
AndroidFileBackend.builder(context).build(), new JavaFileBackend()),
/* transforms = */ ImmutableList.of(),
/* monitors = */ ImmutableList.of());
fakeDownloadMetadataStore = new FakeDownloadMetadataStore();
fakeTrafficStatsTagger = new FakeTrafficStatsTagger();
PlatformUrlEngine urlEngine =
new PlatformUrlEngine(
CONTROL_EXECUTOR,
MAX_PLATFORM_ENGINE_TIMEOUT_MILLIS,
MAX_PLATFORM_ENGINE_TIMEOUT_MILLIS,
fakeTrafficStatsTagger);
testUrlEngine = new TestUrlEngine(urlEngine);
fakeConnectivityHandler = new FakeConnectivityHandler();
downloader =
new Downloader.Builder()
.withIOExecutor(CONTROL_EXECUTOR)
.withConnectivityHandler(fakeConnectivityHandler)
.withMaxConcurrentDownloads(2)
.withLogger(new FloggerDownloaderLogger())
.addUrlEngine(ImmutableList.of("http", "https"), testUrlEngine)
.build();
fakeOAuthTokenProvider = new FakeOAuthTokenProvider();
fileDownloader =
new Offroad2FileDownloader(
downloader,
fileStorage,
DOWNLOAD_EXECUTOR,
fakeOAuthTokenProvider,
fakeDownloadMetadataStore,
ExceptionHandler.withDefaultHandling(),
Optional.absent());
testHttpServer = new TestHttpServer();
testUrlPrefix = testHttpServer.startServer();
}
@After
public void tearDown() {
testHttpServer.stopServer();
fakeConnectivityHandler.reset();
fakeDownloadMetadataStore.reset();
fakeOAuthTokenProvider.reset();
fakeTrafficStatsTagger.reset();
}
@Test
public void testStartDownloading_downloadConditionsNull_usesWifiOnly() throws Exception {
Uri fileUri = tmpUri.newUri();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
// Setup custom handler to ensure expected constraints.
fakeConnectivityHandler.customHandler =
constraints -> {
assertThat(constraints.requireUnmeteredNetwork()).isTrue();
assertThat(constraints.requiredNetworkTypes())
.containsExactly(NetworkType.WIFI, NetworkType.ETHERNET, NetworkType.BLUETOOTH);
return immediateVoidFuture();
};
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NONE)
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
assertThat(fakeConnectivityHandler.invokedCustomHandler).isTrue();
// Check DownloadMetadataStore calls
assertThat(fakeDownloadMetadataStore.upsertCalls).containsKey(fileUri);
assertThat(fakeDownloadMetadataStore.deleteCalls).contains(fileUri);
assertThat(fakeDownloadMetadataStore.read(fileUri).get(MAX_CONNECTION_WAIT_SECS, SECONDS))
.isAbsent();
}
@Test
public void testStartDownloading_wifi() throws Exception {
Uri fileUri = tmpUri.newUri();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
// Setup custom handler to ensure expected constraints.
fakeConnectivityHandler.customHandler =
constraints -> {
assertThat(constraints.requireUnmeteredNetwork()).isTrue();
assertThat(constraints.requiredNetworkTypes())
.containsExactly(NetworkType.WIFI, NetworkType.ETHERNET, NetworkType.BLUETOOTH);
return immediateVoidFuture();
};
// Setup custom handler to add authorization token
fakeOAuthTokenProvider.customHandler = unused -> immediateFuture("TEST_TOKEN");
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_UNMETERED)
.setTrafficTag(TRAFFIC_TAG)
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
assertThat(testUrlEngine.storedRequests()).hasSize(1);
TestUrlRequest request = testUrlEngine.storedRequests().get(0);
assertThat(request.trafficTag()).isEqualTo(TRAFFIC_TAG);
assertThat(request.headers()).containsKey(HttpHeaders.AUTHORIZATION);
assertThat(request.headers())
.valuesForKey(HttpHeaders.AUTHORIZATION)
.contains("Bearer TEST_TOKEN");
assertThat(fakeConnectivityHandler.invokedCustomHandler).isTrue();
assertThat(fakeOAuthTokenProvider.invokedCustomHandler).isTrue();
assertThat(fakeTrafficStatsTagger.storedTrafficTags).contains(TRAFFIC_TAG);
}
@Test
public void testStartDownloading_wifi_notSettingTrafficTag() throws Exception {
Uri fileUri = tmpUri.newUri();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_UNMETERED)
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
assertThat(testUrlEngine.storedRequests()).hasSize(1);
TestUrlRequest request = testUrlEngine.storedRequests().get(0);
assertThat(request.trafficTag()).isEqualTo(0);
assertThat(fakeTrafficStatsTagger.storedTrafficTags).doesNotContain(TRAFFIC_TAG);
}
@Test
public void testStartDownloading_extraHttpHeaders() throws Exception {
Uri fileUri = tmpUri.newUri();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_UNMETERED)
.setTrafficTag(TRAFFIC_TAG)
.setExtraHttpHeaders(
ImmutableList.of(
Pair.create("user-agent", "mdd-downloader"),
Pair.create("other-header", "header-value")))
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
assertThat(testUrlEngine.storedRequests()).hasSize(1);
TestUrlRequest request = testUrlEngine.storedRequests().get(0);
assertThat(request.headers().keySet()).containsExactly("user-agent", "other-header");
assertThat(request.headers()).valuesForKey("user-agent").contains("mdd-downloader");
assertThat(request.headers()).valuesForKey("other-header").contains("header-value");
}
@Test
public void testStartDownloading_cellular() throws Exception {
Uri fileUri = tmpUri.newUri();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
// Setup custom handler to ensure expected constraints.
fakeConnectivityHandler.customHandler =
constraints -> {
assertThat(constraints).isEqualTo(DownloadConstraints.NETWORK_CONNECTED);
return immediateVoidFuture();
};
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_CONNECTED)
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
assertThat(fakeConnectivityHandler.invokedCustomHandler).isTrue();
}
@Test
public void testStartDownloading_failed() throws Exception {
Uri fileUri = tmpUri.newUri();
// Simulate failure due to bad url;
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload("https://BADURL")
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_UNMETERED)
.build());
ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get);
assertThat(exception).hasCauseThat().isInstanceOf(DownloadException.class);
DownloadException dex = (DownloadException) exception.getCause();
assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.UNKNOWN_ERROR);
}
@Test
public void testStartDownloading_whenPartialFile_whenMetadataNotPresent_getsFullFile()
throws Exception {
Uri fileUri = tmpUri.newUri();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
// Write partial content to file but do _not_ write partial metadata.
try (InputStream inStream =
fileStorage.open(
Uri.parse("file://" + PARTIAL_TEST_DATA_PATH), ReadStreamOpener.create());
OutputStream outStream = fileStorage.open(fileUri, WriteStreamOpener.create())) {
ByteStreams.copy(inStream, outStream);
}
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NONE)
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
// Check that full file is requested (no HTTP range headers)
assertThat(testUrlEngine.storedRequests().get(0).headers())
.doesNotContainKey(HttpHeaders.RANGE);
assertThat(testUrlEngine.storedRequests().get(0).headers())
.doesNotContainKey(HttpHeaders.IF_RANGE);
// Check DownloadMetadataStore calls
assertThat(fakeDownloadMetadataStore.readCalls).contains(fileUri);
assertThat(fakeDownloadMetadataStore.upsertCalls).containsKey(fileUri);
assertThat(fakeDownloadMetadataStore.deleteCalls).contains(fileUri);
assertThat(fakeDownloadMetadataStore.read(fileUri).get(MAX_CONNECTION_WAIT_SECS, SECONDS))
.isAbsent();
}
@Test
public void testStartDownloading_whenPartialFile_whenMetadataPresent_reusesPartialFile()
throws Exception {
Uri fileUri = tmpUri.newUri();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
// Write partial content to file.
try (InputStream inStream =
fileStorage.open(
Uri.parse("file://" + PARTIAL_TEST_DATA_PATH), ReadStreamOpener.create());
OutputStream outStream = fileStorage.open(fileUri, WriteStreamOpener.create())) {
ByteStreams.copy(inStream, outStream);
}
// Write existing metadata to file.
fakeDownloadMetadataStore
.upsert(fileUri, DownloadMetadata.create("test", 0))
.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NONE)
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
// Check that full file is requested (no HTTP range headers)
assertThat(testUrlEngine.storedRequests().get(0).headers()).containsKey(HttpHeaders.RANGE);
assertThat(testUrlEngine.storedRequests().get(0).headers()).containsKey(HttpHeaders.IF_RANGE);
// Check DownloadMetadataStore calls
assertThat(fakeDownloadMetadataStore.readCalls).contains(fileUri);
assertThat(fakeDownloadMetadataStore.upsertCalls).containsKey(fileUri);
assertThat(fakeDownloadMetadataStore.deleteCalls).contains(fileUri);
assertThat(fakeDownloadMetadataStore.read(fileUri).get(MAX_CONNECTION_WAIT_SECS, SECONDS))
.isAbsent();
}
@Test
public void testCancelDownload_notFinishedFuture() throws Exception {
// Build a file uri so it's not created by TemporaryUri -- we can then make assertions on the
// existence of the file.
Uri fileUri = tmpUri.newUriBuilder().appendPath("unique").build();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
// Block download using connectivity check
CountDownLatch blockingLatch = new CountDownLatch(1);
fakeConnectivityHandler.customHandler =
unused ->
PropagatedFutures.submitAsync(
() -> {
blockingLatch.await();
return immediateVoidFuture();
},
CONTROL_EXECUTOR);
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_UNMETERED)
.build());
assertThat(downloadFuture.isDone()).isFalse();
assertThat(fileStorage.exists(fileUri)).isFalse();
downloadFuture.cancel(true);
assertThat(downloadFuture.isCancelled()).isTrue();
assertThat(fileStorage.exists(fileUri)).isFalse();
// count down latch to clean up test.
blockingLatch.countDown();
}
@Test
public void testCancelDownload_onAlreadySucceededFuture() throws Exception {
// Build a file uri so it's not created by TemporaryUri -- we can then make assertions on the
// existence of the file.
Uri fileUri = tmpUri.getRootUriBuilder().appendPath("unique").build();
String urlToDownload = testUrlPrefix.path(TEST_DATA_ENDPOINT).toString();
testHttpServer.registerTextFile(TEST_DATA_ENDPOINT, TEST_DATA_PATH);
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload(urlToDownload)
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_UNMETERED)
.build());
downloadFuture.get(MAX_CONNECTION_WAIT_SECS, SECONDS);
// Assert that on device file is created and remains even after cancel.
assertThat(fileStorage.exists(fileUri)).isTrue();
downloadFuture.cancel(true);
assertThat(fileStorage.exists(fileUri)).isTrue();
}
@Test
public void testCancelDownload_onAlreadyFailedFuture() throws Exception {
// Build a file uri so it's not created by TemporaryUri -- we can then make assertions on the
// existence of the file.
Uri fileUri = tmpUri.getRootUriBuilder().appendPath("unique").build();
// Simulate failure due to bad url;
ListenableFuture<Void> downloadFuture =
fileDownloader.startDownloading(
DownloadRequest.newBuilder()
.setFileUri(fileUri)
.setUrlToDownload("https://BADURL")
.setDownloadConstraints(
com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints
.NETWORK_UNMETERED)
.build());
Exception unused = assertThrows(ExecutionException.class, downloadFuture::get);
// Assert that on device file is not created and doesn't get created after cancel.
assertThat(fileStorage.exists(fileUri)).isFalse();
downloadFuture.cancel(true);
assertThat(fileStorage.exists(fileUri)).isFalse();
}
/** Custom {@link ConnectivityHandler} that allows custom logic to be used for each test. */
static final class FakeConnectivityHandler implements ConnectivityHandler {
private static final AsyncFunction<DownloadConstraints, Void> DEFAULT_HANDLER =
unused -> immediateVoidFuture();
private AsyncFunction<DownloadConstraints, Void> customHandler = DEFAULT_HANDLER;
private boolean invokedCustomHandler = false;
@Override
public ListenableFuture<Void> checkConnectivity(DownloadConstraints constraints) {
ListenableFuture<Void> returnFuture;
try {
returnFuture = customHandler.apply(constraints);
} catch (Exception e) {
returnFuture = immediateFailedFuture(e);
}
invokedCustomHandler = true;
return returnFuture;
}
public boolean invokedCustomHandler() {
return invokedCustomHandler;
}
/**
* Reset inner state to initial values.
*
* <p>This prevents failures caused by cross test pollution.
*/
public void reset() {
customHandler = DEFAULT_HANDLER;
invokedCustomHandler = false;
}
}
/** Custom {@link OAuthTokenProvider} that allows custom logic for each test. */
static final class FakeOAuthTokenProvider implements OAuthTokenProvider {
private static final AsyncFunction<URI, String> DEFAULT_HANDLER =
unused -> immediateFuture(null);
private AsyncFunction<URI, String> customHandler = DEFAULT_HANDLER;
private boolean invokedCustomHandler = false;
@Override
public ListenableFuture<String> provideOAuthToken(URI uri) {
ListenableFuture<String> returnFuture;
try {
returnFuture = customHandler.apply(uri);
} catch (Exception e) {
returnFuture = immediateFailedFuture(e);
}
invokedCustomHandler = true;
return returnFuture;
}
/**
* Reset inner state to initial values.
*
* <p>This prevents failures caused by cross test pollution.
*/
public void reset() {
customHandler = DEFAULT_HANDLER;
invokedCustomHandler = false;
}
}
private static final class FakeTrafficStatsTagger
implements PlatformUrlEngine.TrafficStatsTagger {
private final List<Integer> storedTrafficTags = new ArrayList<>();
@Override
public int getAndSetThreadStatsTag(int tag) {
int prevTag = storedTrafficTags.isEmpty() ? 0 : Iterables.getLast(storedTrafficTags);
storedTrafficTags.add(tag);
return prevTag;
}
@Override
public void restoreThreadStatsTag(int tag) {
storedTrafficTags.add(tag);
}
public void reset() {
storedTrafficTags.clear();
}
}
private static final class FakeDownloadMetadataStore implements DownloadMetadataStore {
// Backing storage structure for metadata.
private final Map<Uri, DownloadMetadata> storedMetadata = new HashMap<>();
// Tracking of what calls are made on this fake.
final List<Uri> readCalls = new ArrayList<>();
final Map<Uri, List<DownloadMetadata>> upsertCalls = new HashMap<>();
final List<Uri> deleteCalls = new ArrayList<>();
@Override
public ListenableFuture<Optional<DownloadMetadata>> read(Uri uri) {
readCalls.add(uri);
return immediateFuture(Optional.fromNullable(storedMetadata.get(uri)));
}
@Override
public ListenableFuture<Void> upsert(Uri uri, DownloadMetadata downloadMetadata) {
upsertCalls.putIfAbsent(uri, new ArrayList<>());
upsertCalls.get(uri).add(downloadMetadata);
storedMetadata.put(uri, downloadMetadata);
return immediateVoidFuture();
}
@Override
public ListenableFuture<Void> delete(Uri uri) {
deleteCalls.add(uri);
storedMetadata.remove(uri);
return immediateVoidFuture();
}
public void reset() {
storedMetadata.clear();
readCalls.clear();
upsertCalls.clear();
deleteCalls.clear();
}
}
}