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