Commit 3919f384 by eguven Committed by Oliver Woodman

Make DownloadManager use DownloadIndex

PiperOrigin-RevId: 240320220
parent e4b49477
......@@ -114,21 +114,14 @@ public class DemoApplication extends Application {
private synchronized void initDownloadManager() {
if (downloadManager == null) {
DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(new ExoDatabaseProvider(this));
File actionFile = new File(getDownloadDirectory(), DOWNLOAD_TRACKER_ACTION_FILE);
if (actionFile.exists()) {
try {
DownloadIndexUtil.upgradeActionFile(new ActionFile(actionFile), downloadIndex, null);
} catch (IOException e) {
Log.e(TAG, "Upgrading action file failed", e);
}
actionFile.delete();
}
upgradeActionFile(DOWNLOAD_TRACKER_ACTION_FILE, downloadIndex);
upgradeActionFile(DOWNLOAD_ACTION_FILE, downloadIndex);
DownloaderConstructorHelper downloaderConstructorHelper =
new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory());
downloadManager =
new DownloadManager(
this,
new File(getDownloadDirectory(), DOWNLOAD_ACTION_FILE),
downloadIndex,
new DefaultDownloaderFactory(downloaderConstructorHelper),
MAX_SIMULTANEOUS_DOWNLOADS,
DownloadManager.DEFAULT_MIN_RETRY_COUNT,
......@@ -139,6 +132,18 @@ public class DemoApplication extends Application {
}
}
private void upgradeActionFile(String file, DefaultDownloadIndex downloadIndex) {
ActionFile actionFile = new ActionFile(new File(getDownloadDirectory(), file));
if (actionFile.exists()) {
try {
DownloadIndexUtil.upgradeActionFile(actionFile, downloadIndex, null);
} catch (IOException e) {
Log.e(TAG, "Upgrading action file failed", e);
}
actionFile.delete();
}
}
private File getDownloadDirectory() {
if (downloadDirectory == null) {
downloadDirectory = getExternalFilesDir(null);
......
......@@ -18,8 +18,6 @@ package com.google.android.exoplayer2.demo;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import android.widget.Toast;
......@@ -66,9 +64,8 @@ public class DownloadTracker implements DownloadManager.Listener {
private final Context context;
private final DataSource.Factory dataSourceFactory;
private final CopyOnWriteArraySet<Listener> listeners;
private final HashMap<Uri, DownloadState> trackedDownloadStates;
private final HashMap<Uri, DownloadState> downloadStates;
private final DefaultDownloadIndex downloadIndex;
private final Handler indexHandler;
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
......@@ -78,11 +75,8 @@ public class DownloadTracker implements DownloadManager.Listener {
this.dataSourceFactory = dataSourceFactory;
this.downloadIndex = downloadIndex;
listeners = new CopyOnWriteArraySet<>();
trackedDownloadStates = new HashMap<>();
HandlerThread indexThread = new HandlerThread("DownloadTracker");
indexThread.start();
indexHandler = new Handler(indexThread.getLooper());
loadTrackedActions();
downloadStates = new HashMap<>();
loadDownloads();
}
public void addListener(Listener listener) {
......@@ -94,15 +88,16 @@ public class DownloadTracker implements DownloadManager.Listener {
}
public boolean isDownloaded(Uri uri) {
return trackedDownloadStates.containsKey(uri);
DownloadState downloadState = downloadStates.get(uri);
return downloadState != null && downloadState.state != DownloadState.STATE_FAILED;
}
@SuppressWarnings("unchecked")
public List<StreamKey> getOfflineStreamKeys(Uri uri) {
if (!trackedDownloadStates.containsKey(uri)) {
return Collections.emptyList();
}
return Arrays.asList(trackedDownloadStates.get(uri).streamKeys);
DownloadState downloadState = downloadStates.get(uri);
return downloadState != null && downloadState.state != DownloadState.STATE_FAILED
? Arrays.asList(downloadState.streamKeys)
: Collections.emptyList();
}
public void toggleDownload(
......@@ -129,59 +124,34 @@ public class DownloadTracker implements DownloadManager.Listener {
@Override
public void onDownloadStateChanged(DownloadManager downloadManager, DownloadState downloadState) {
if (downloadState.state == DownloadState.STATE_REMOVED
|| downloadState.state == DownloadState.STATE_FAILED) {
// A download has been removed, or has failed. Stop tracking it.
if (trackedDownloadStates.remove(downloadState.uri) != null) {
handleTrackedDownloadStateChanged(downloadState);
boolean downloaded = isDownloaded(downloadState.uri);
if (downloadState.state == DownloadState.STATE_REMOVED) {
downloadStates.remove(downloadState.uri);
} else {
downloadStates.put(downloadState.uri, downloadState);
}
if (downloaded != isDownloaded(downloadState.uri)) {
for (Listener listener : listeners) {
listener.onDownloadsChanged();
}
}
}
// Internal methods
private void loadTrackedActions() {
private void loadDownloads() {
try {
DownloadStateCursor downloadStates = downloadIndex.getDownloadStates();
while (downloadStates.moveToNext()) {
DownloadState downloadState = downloadStates.getDownloadState();
trackedDownloadStates.put(downloadState.uri, downloadState);
DownloadStateCursor loadedDownloadStates = downloadIndex.getDownloadStates();
while (loadedDownloadStates.moveToNext()) {
DownloadState downloadState = loadedDownloadStates.getDownloadState();
downloadStates.put(downloadState.uri, downloadState);
}
downloadStates.close();
loadedDownloadStates.close();
} catch (IOException e) {
Log.w(TAG, "Failed to query download states", e);
}
}
private void handleTrackedDownloadStateChanged(DownloadState downloadState) {
for (Listener listener : listeners) {
listener.onDownloadsChanged();
}
indexHandler.post(
() -> {
try {
if (downloadState.state == DownloadState.STATE_REMOVED) {
downloadIndex.removeDownloadState(downloadState.id);
} else {
downloadIndex.putDownloadState(downloadState);
}
} catch (IOException e) {
// TODO: This whole method is going away in cr/232854678.
}
});
}
private void startDownload(DownloadAction action) {
if (trackedDownloadStates.containsKey(action.uri)) {
// This content is already being downloaded. Do nothing.
return;
}
DownloadState downloadState = new DownloadState(action);
trackedDownloadStates.put(downloadState.uri, downloadState);
handleTrackedDownloadStateChanged(downloadState);
startServiceWithAction(action);
}
private void startServiceWithAction(DownloadAction action) {
DownloadService.startWithAction(context, DemoDownloadService.class, action, false);
}
......@@ -238,7 +208,7 @@ public class DownloadTracker implements DownloadManager.Listener {
if (helper.getPeriodCount() == 0) {
Log.d(TAG, "No periods found. Downloading entire stream.");
DownloadAction downloadAction = downloadHelper.getDownloadAction(Util.getUtf8Bytes(name));
startDownload(downloadAction);
startServiceWithAction(downloadAction);
downloadHelper.release();
return;
}
......@@ -280,7 +250,7 @@ public class DownloadTracker implements DownloadManager.Listener {
}
}
DownloadAction downloadAction = downloadHelper.getDownloadAction(Util.getUtf8Bytes(name));
startDownload(downloadAction);
startServiceWithAction(downloadAction);
}
// DialogInterface.OnDismissListener implementation.
......
......@@ -25,10 +25,10 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadState.State;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.DummyMainThread.TestRunnable;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TestDownloadManagerListener;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -64,7 +64,7 @@ public class DownloadManagerTest {
private Uri uri2;
private Uri uri3;
private DummyMainThread dummyMainThread;
private File actionFile;
private DefaultDownloadIndex downloadIndex;
private TestDownloadManagerListener downloadManagerListener;
private FakeDownloaderFactory downloaderFactory;
private DownloadManager downloadManager;
......@@ -77,7 +77,7 @@ public class DownloadManagerTest {
uri2 = Uri.parse("http://abc.com/media2");
uri3 = Uri.parse("http://abc.com/media3");
dummyMainThread = new DummyMainThread();
actionFile = Util.createTempFile(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
downloadIndex = new DefaultDownloadIndex(TestUtil.getTestDatabaseProvider());
downloaderFactory = new FakeDownloaderFactory();
setUpDownloadManager(100);
}
......@@ -85,7 +85,6 @@ public class DownloadManagerTest {
@After
public void tearDown() throws Exception {
releaseDownloadManager();
actionFile.delete();
dummyMainThread.release();
}
......@@ -359,6 +358,7 @@ public class DownloadManagerTest {
TaskWrapper task2 = new DownloadRunner(uri2).postDownloadAction().getTask();
TaskWrapper task3 = new DownloadRunner(uri3).postRemoveAction().getTask();
task3.assertRemoving();
DownloadState[] states = downloadManager.getAllDownloadStates();
assertThat(states).hasLength(3);
......@@ -471,7 +471,7 @@ public class DownloadManagerTest {
downloadManager =
new DownloadManager(
ApplicationProvider.getApplicationContext(),
actionFile,
downloadIndex,
downloaderFactory,
maxActiveDownloadTasks,
MIN_RETRY_COUNT,
......@@ -494,8 +494,8 @@ public class DownloadManagerTest {
}
}
private void runOnMainThread(final Runnable r) {
dummyMainThread.runOnMainThread(r);
private void runOnMainThread(final TestRunnable r) {
dummyMainThread.runTestOnMainThread(r);
}
private final class DownloadRunner {
......
......@@ -27,6 +27,7 @@ import android.os.ConditionVariable;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadManager;
......@@ -69,7 +70,7 @@ public class DownloadManagerDashTest {
private StreamKey fakeStreamKey1;
private StreamKey fakeStreamKey2;
private TestDownloadManagerListener downloadManagerListener;
private File actionFile;
private DefaultDownloadIndex downloadIndex;
private DummyMainThread dummyMainThread;
@Before
......@@ -95,7 +96,7 @@ public class DownloadManagerDashTest {
fakeStreamKey1 = new StreamKey(0, 0, 0);
fakeStreamKey2 = new StreamKey(0, 1, 0);
actionFile = new File(tempFolder, "actionFile");
downloadIndex = new DefaultDownloadIndex(TestUtil.getTestDatabaseProvider());
createDownloadManager();
}
......@@ -136,8 +137,6 @@ public class DownloadManagerDashTest {
downloadManager.release();
});
assertThat(actionFile.exists()).isTrue();
assertThat(actionFile.length()).isGreaterThan(0L);
assertCacheEmpty(cache);
// Revert fakeDataSet to normal.
......@@ -239,13 +238,13 @@ public class DownloadManagerDashTest {
}
private void createDownloadManager() {
dummyMainThread.runOnMainThread(
dummyMainThread.runTestOnMainThread(
() -> {
Factory fakeDataSourceFactory = new FakeDataSource.Factory().setFakeDataSet(fakeDataSet);
downloadManager =
new DownloadManager(
ApplicationProvider.getApplicationContext(),
actionFile,
downloadIndex,
new DefaultDownloaderFactory(
new DownloaderConstructorHelper(cache, fakeDataSourceFactory)),
/* maxSimultaneousDownloads= */ 1,
......
......@@ -26,6 +26,7 @@ import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.offline.DefaultDownloadIndex;
import com.google.android.exoplayer2.offline.DefaultDownloaderFactory;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloadManager;
......@@ -107,19 +108,14 @@ public class DownloadServiceDashTest {
fakeStreamKey1 = new StreamKey(0, 0, 0);
fakeStreamKey2 = new StreamKey(0, 1, 0);
dummyMainThread.runOnMainThread(
dummyMainThread.runTestOnMainThread(
() -> {
File actionFile;
try {
actionFile = Util.createTempFile(context, "ExoPlayerTest");
} catch (IOException e) {
throw new RuntimeException(e);
}
actionFile.delete();
DefaultDownloadIndex downloadIndex =
new DefaultDownloadIndex(TestUtil.getTestDatabaseProvider());
final DownloadManager dashDownloadManager =
new DownloadManager(
ApplicationProvider.getApplicationContext(),
actionFile,
downloadIndex,
new DefaultDownloaderFactory(
new DownloaderConstructorHelper(cache, fakeDataSourceFactory)),
/* maxSimultaneousDownloads= */ 1,
......
......@@ -27,6 +27,11 @@ import java.util.concurrent.atomic.AtomicReference;
/** Helper class to simulate main/UI thread in tests. */
public final class DummyMainThread {
/** {@link Runnable} variant which can throw a checked exception. */
public interface TestRunnable {
void run() throws Exception;
}
/** Default timeout value used for {@link #runOnMainThread(Runnable)}. */
public static final int TIMEOUT_MS = 10000;
......@@ -57,8 +62,33 @@ public final class DummyMainThread {
* @param runnable The {@link Runnable} to run.
*/
public void runOnMainThread(int timeoutMs, final Runnable runnable) {
runTestOnMainThread(timeoutMs, runnable::run);
}
/**
* Runs the provided {@link TestRunnable} on the main thread, blocking until execution completes
* or until {@link #TIMEOUT_MS} milliseconds have passed.
*
* @param runnable The {@link TestRunnable} to run.
*/
public void runTestOnMainThread(final TestRunnable runnable) {
runTestOnMainThread(TIMEOUT_MS, runnable);
}
/**
* Runs the provided {@link TestRunnable} on the main thread, blocking until execution completes
* or until timeout milliseconds have passed.
*
* @param timeoutMs The maximum time to wait in milliseconds.
* @param runnable The {@link TestRunnable} to run.
*/
public void runTestOnMainThread(int timeoutMs, final TestRunnable runnable) {
if (Looper.myLooper() == handler.getLooper()) {
runnable.run();
try {
runnable.run();
} catch (Exception e) {
Util.sneakyThrow(e);
}
} else {
ConditionVariable finishedCondition = new ConditionVariable();
AtomicReference<Throwable> thrown = new AtomicReference<>();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment