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.
......
......@@ -36,11 +36,12 @@ import android.os.Looper;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.database.DatabaseIOException;
import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.scheduler.Requirements;
import com.google.android.exoplayer2.scheduler.RequirementsWatcher;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -48,7 +49,6 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
......@@ -130,7 +130,7 @@ public final class DownloadManager {
private final int maxSimultaneousDownloads;
private final int minRetryCount;
private final Context context;
private final ActionFile actionFile;
private final DefaultDownloadIndex downloadIndex;
private final DownloaderFactory downloaderFactory;
private final ArrayList<Download> downloads;
private final HashMap<Download, DownloadThread> activeDownloads;
......@@ -146,18 +146,20 @@ public final class DownloadManager {
private int manualStopReason;
private RequirementsWatcher requirementsWatcher;
private int simultaneousDownloads;
private boolean loadingDownload;
/**
* Constructs a {@link DownloadManager}.
*
* @param context Any context.
* @param actionFile The file in which active actions are saved.
* @param databaseProvider Used to create a {@link DownloadIndex} which holds download states.
* @param downloaderFactory A factory for creating {@link Downloader}s.
*/
public DownloadManager(Context context, File actionFile, DownloaderFactory downloaderFactory) {
public DownloadManager(
Context context, DatabaseProvider databaseProvider, DownloaderFactory downloaderFactory) {
this(
context,
actionFile,
databaseProvider,
downloaderFactory,
DEFAULT_MAX_SIMULTANEOUS_DOWNLOADS,
DEFAULT_MIN_RETRY_COUNT,
......@@ -168,7 +170,33 @@ public final class DownloadManager {
* Constructs a {@link DownloadManager}.
*
* @param context Any context.
* @param actionFile The file in which active actions are saved.
* @param databaseProvider Used to create a {@link DownloadIndex} which holds download states.
* @param downloaderFactory A factory for creating {@link Downloader}s.
* @param maxSimultaneousDownloads The maximum number of simultaneous downloads.
* @param minRetryCount The minimum number of times a download must be retried before failing.
* @param requirements The requirements needed to be met to start downloads.
*/
public DownloadManager(
Context context,
DatabaseProvider databaseProvider,
DownloaderFactory downloaderFactory,
int maxSimultaneousDownloads,
int minRetryCount,
Requirements requirements) {
this(
context,
new DefaultDownloadIndex(databaseProvider),
downloaderFactory,
maxSimultaneousDownloads,
minRetryCount,
requirements);
}
/**
* Constructs a {@link DownloadManager}.
*
* @param context Any context.
* @param downloadIndex The {@link DefaultDownloadIndex} which holds download states.
* @param downloaderFactory A factory for creating {@link Downloader}s.
* @param maxSimultaneousDownloads The maximum number of simultaneous downloads.
* @param minRetryCount The minimum number of times a download must be retried before failing.
......@@ -176,13 +204,13 @@ public final class DownloadManager {
*/
public DownloadManager(
Context context,
File actionFile,
DefaultDownloadIndex downloadIndex,
DownloaderFactory downloaderFactory,
int maxSimultaneousDownloads,
int minRetryCount,
Requirements requirements) {
this.context = context.getApplicationContext();
this.actionFile = new ActionFile(actionFile);
this.downloadIndex = downloadIndex;
this.downloaderFactory = downloaderFactory;
this.maxSimultaneousDownloads = maxSimultaneousDownloads;
this.minRetryCount = minRetryCount;
......@@ -204,11 +232,16 @@ public final class DownloadManager {
listeners = new CopyOnWriteArraySet<>();
actionQueue = new ArrayDeque<>();
notMetRequirements = watchRequirements(requirements);
loadActions();
setNotMetRequirements(watchRequirements(requirements));
loadDownloads();
logd("Created");
}
/** Returns the used {@link DownloadIndex}. */
public DownloadIndex getDownloadIndex() {
return downloadIndex;
}
/**
* Sets the requirements needed to be met to start downloads.
*
......@@ -251,10 +284,7 @@ public final class DownloadManager {
*/
public void startDownloads() {
logd("manual stop is cancelled");
manualStopReason = MANUAL_STOP_REASON_NONE;
for (int i = 0; i < downloads.size(); i++) {
downloads.get(i).setManualStopReason(MANUAL_STOP_REASON_NONE);
}
setManualStopReason(/* id= */ null, MANUAL_STOP_REASON_NONE);
}
/** Signals all downloads to stop. Call {@link #startDownloads()} to let them to be started. */
......@@ -273,10 +303,7 @@ public final class DownloadManager {
public void stopDownloads(int manualStopReason) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE);
logd("downloads are stopped manually");
this.manualStopReason = manualStopReason;
for (int i = 0; i < downloads.size(); i++) {
downloads.get(i).setManualStopReason(this.manualStopReason);
}
setManualStopReason(/* id= */ null, manualStopReason);
}
/**
......@@ -286,11 +313,7 @@ public final class DownloadManager {
* @param id The unique content id of the download to be started.
*/
public void startDownload(String id) {
Download download = getDownload(id);
if (download != null) {
logd("manual stop is cancelled", download);
download.setManualStopReason(MANUAL_STOP_REASON_NONE);
}
setManualStopReason(id, MANUAL_STOP_REASON_NONE);
}
/**
......@@ -315,11 +338,7 @@ public final class DownloadManager {
*/
public void stopDownload(String id, int manualStopReason) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE);
Download download = getDownload(id);
if (download != null) {
logd("download is stopped manually", download);
download.setManualStopReason(manualStopReason);
}
setManualStopReason(id, manualStopReason);
}
/**
......@@ -329,11 +348,9 @@ public final class DownloadManager {
*/
public void handleAction(DownloadAction action) {
Assertions.checkState(!released);
actionQueue.add(action);
if (initialized) {
addDownloadForAction(action);
saveActions();
} else {
actionQueue.add(action);
processActionQueue();
}
}
......@@ -343,25 +360,12 @@ public final class DownloadManager {
return downloads.size();
}
/**
* Returns {@link DownloadState} for the given content id, or null if no such download exists.
*
* @param id The unique content id.
* @return DownloadState for the given content id, or null if no such download exists.
*/
@Nullable
public DownloadState getDownloadState(String id) {
Assertions.checkState(!released);
Download download = getDownload(id);
return download != null ? download.getDownloadState() : null;
}
/** Returns the states of all current downloads. */
public DownloadState[] getAllDownloadStates() {
Assertions.checkState(!released);
DownloadState[] states = new DownloadState[downloads.size()];
for (int i = 0; i < states.length; i++) {
states[i] = downloads.get(i).getDownloadState();
states[i] = downloads.get(i).getUpdatedDownloadState();
}
return states;
}
......@@ -375,7 +379,7 @@ public final class DownloadManager {
/** Returns whether there are no active downloads. */
public boolean isIdle() {
Assertions.checkState(!released);
return initialized && activeDownloads.isEmpty();
return initialized && activeDownloads.isEmpty() && actionQueue.isEmpty() && !loadingDownload;
}
/**
......@@ -399,17 +403,32 @@ public final class DownloadManager {
logd("Released");
}
private void addDownloadForAction(DownloadAction action) {
for (int i = 0; i < downloads.size(); i++) {
Download download = downloads.get(i);
if (download.addAction(action)) {
logd("Action is added to existing download", download);
private void setManualStopReason(@Nullable String id, int manualStopReason) {
if (id != null) {
Download download = getDownload(id);
if (download != null) {
logd("download manual stop reason is set to : " + manualStopReason, download);
download.setManualStopReason(manualStopReason);
return;
}
} else {
this.manualStopReason = manualStopReason;
for (int i = 0; i < downloads.size(); i++) {
downloads.get(i).setManualStopReason(manualStopReason);
}
}
Download download = new Download(this, action, notMetRequirements, manualStopReason);
downloads.add(download);
logd("Download is added", download);
fileIOHandler.post(
() -> {
try {
if (id != null) {
downloadIndex.setManualStopReason(id, manualStopReason);
} else {
downloadIndex.setManualStopReason(manualStopReason);
}
} catch (DatabaseIOException e) {
Log.e(TAG, "setManualStopReason failed", e);
}
});
}
private void maybeNotifyListenersIdle() {
......@@ -422,33 +441,44 @@ public final class DownloadManager {
}
}
private void onDownloadStateChange(Download download) {
private void onDownloadStateChange(Download download, DownloadState downloadState) {
if (released) {
return;
}
logd("Download state is changed", download);
DownloadState downloadState = download.getDownloadState();
updateDownloadIndex(downloadState);
for (Listener listener : listeners) {
listener.onDownloadStateChanged(this, downloadState);
}
if (download.isFinished()) {
downloads.remove(download);
saveActions();
}
}
private void onRequirementsStateChanged(@Requirements.RequirementFlags int notMetRequirements) {
this.notMetRequirements = notMetRequirements;
logdFlags("Not met requirements are changed", notMetRequirements);
Requirements requirements = requirementsWatcher.getRequirements();
for (Listener listener : listeners) {
listener.onRequirementsStateChanged(DownloadManager.this, requirements, notMetRequirements);
}
setNotMetRequirements(notMetRequirements);
}
private void setNotMetRequirements(@Requirements.RequirementFlags int notMetRequirements) {
this.notMetRequirements = notMetRequirements;
logdFlags("Not met requirements are changed", notMetRequirements);
for (int i = 0; i < downloads.size(); i++) {
downloads.get(i).setNotMetRequirements(notMetRequirements);
}
}
@Requirements.RequirementFlags
private int watchRequirements(Requirements requirements) {
RequirementsWatcher.Listener listener =
(requirementsWatcher, notMetRequirements) -> onRequirementsStateChanged(notMetRequirements);
requirementsWatcher = new RequirementsWatcher(context, listener, requirements);
return requirementsWatcher.start();
}
@Nullable
private Download getDownload(String id) {
for (int i = 0; i < downloads.size(); i++) {
......@@ -460,34 +490,85 @@ public final class DownloadManager {
return null;
}
private void loadActions() {
private void processActionQueue() {
if (loadingDownload || actionQueue.isEmpty()) {
return;
}
DownloadAction action = actionQueue.remove();
Download download = getDownload(action.id);
if (download != null) {
download.addAction(action);
logd("Action is added to existing download", download);
return;
}
loadDownload(action);
}
private void loadDownload(DownloadAction action) {
loadingDownload = true;
fileIOHandler.post(
() -> {
DownloadAction[] loadedActions;
DownloadState downloadState = null;
try {
loadedActions = actionFile.load();
logd("Action file is loaded.");
} catch (Throwable e) {
Log.e(TAG, "Action file loading failed.", e);
loadedActions = new DownloadAction[0];
downloadState = downloadIndex.getDownloadState(action.id);
} catch (DatabaseIOException e) {
Log.e(TAG, "loadDownload failed", e);
}
final DownloadAction[] actions = loadedActions;
DownloadState finalDownloadState = downloadState;
handler.post(
() -> {
loadingDownload = false;
if (released) {
return;
}
for (DownloadAction action : actions) {
addDownloadForAction(action);
DownloadState state;
if (finalDownloadState == null) {
state = new DownloadState(action);
logd("Download state is created for " + action.id);
} else {
state = finalDownloadState.mergeAction(action);
logd("Download state is loaded for " + action.id);
}
addDownloadForState(state);
processActionQueue();
});
});
}
private void loadDownloads() {
fileIOHandler.post(
() -> {
DownloadState[] loadedStates;
try {
DownloadStateCursor cursor =
downloadIndex.getDownloadStates(
STATE_QUEUED,
STATE_STOPPED,
STATE_DOWNLOADING,
STATE_REMOVING,
STATE_RESTARTING);
loadedStates = new DownloadState[cursor.getCount()];
for (int i = 0, length = loadedStates.length; i < length; i++) {
cursor.moveToNext();
loadedStates[i] = cursor.getDownloadState();
}
logd("Download states are loaded.");
} catch (Throwable e) {
Log.e(TAG, "Download state loading failed.", e);
loadedStates = new DownloadState[0];
}
final DownloadState[] states = loadedStates;
handler.post(
() -> {
if (released) {
return;
}
if (!actionQueue.isEmpty()) {
while (!actionQueue.isEmpty()) {
addDownloadForAction(actionQueue.remove());
}
saveActions();
for (DownloadState downloadState : states) {
addDownloadForState(downloadState);
}
logd("Downloads are created.");
initialized = true;
processActionQueue();
for (Listener listener : listeners) {
listener.onInitialized(DownloadManager.this);
}
......@@ -498,22 +579,26 @@ public final class DownloadManager {
});
}
private void saveActions() {
if (released) {
return;
}
ArrayList<DownloadAction> actions = new ArrayList<>(downloads.size());
for (int i = 0; i < downloads.size(); i++) {
downloads.get(i).addActions(actions);
}
final DownloadAction[] actionsArray = actions.toArray(new DownloadAction[0]);
private void addDownloadForState(DownloadState downloadState) {
Download download = new Download(this, downloadState, notMetRequirements, manualStopReason);
downloads.add(download);
logd("Download is added", download);
}
private void updateDownloadIndex(DownloadState downloadState) {
fileIOHandler.post(
() -> {
if (released) {
return;
}
try {
actionFile.store(actionsArray);
logd("Actions persisted.");
} catch (IOException e) {
Log.e(TAG, "Persisting actions failed.", e);
if (downloadState.state == DownloadState.STATE_REMOVED) {
downloadIndex.removeDownloadState(downloadState.id);
} else {
downloadIndex.putDownloadState(downloadState);
}
} catch (DatabaseIOException e) {
Log.e(TAG, "updateDownloadIndex failed", e);
}
});
}
......@@ -536,20 +621,6 @@ public final class DownloadManager {
}
}
@Requirements.RequirementFlags
private int watchRequirements(Requirements requirements) {
RequirementsWatcher.Listener listener =
(requirementsWatcher, notMetRequirements) -> onRequirementsStateChanged(notMetRequirements);
requirementsWatcher = new RequirementsWatcher(context, listener, requirements);
@Requirements.RequirementFlags int notMetRequirements = requirementsWatcher.start();
if (notMetRequirements == 0) {
startDownloads();
} else {
stopDownloads();
}
return notMetRequirements;
}
@StartThreadResults
private int startDownloadThread(Download download, DownloadAction action) {
if (!initialized || released) {
......@@ -634,13 +705,13 @@ public final class DownloadManager {
private Download(
DownloadManager downloadManager,
DownloadAction action,
DownloadState downloadState,
@Requirements.RequirementFlags int notMetRequirements,
int manualStopReason) {
this.downloadManager = downloadManager;
this.downloadState = downloadState;
this.notMetRequirements = notMetRequirements;
this.manualStopReason = manualStopReason;
downloadState = new DownloadState(action);
initialize(downloadState.state);
}
......@@ -649,17 +720,17 @@ public final class DownloadManager {
return downloadState.id;
}
public boolean addAction(DownloadAction newAction) {
if (!getId().equals(newAction.id)) {
return false;
public void addAction(DownloadAction newAction) {
Assertions.checkArgument(getId().equals(newAction.id));
if (!downloadState.type.equals(newAction.type)) {
String format = "Action type (%s) doesn't match existing download type (%s)";
Log.e(TAG, String.format(format, newAction.type, downloadState.type));
}
Assertions.checkState(downloadState.type.equals(newAction.type));
downloadState = downloadState.mergeAction(newAction);
initialize(downloadState.state);
return true;
}
public DownloadState getDownloadState() {
public DownloadState getUpdatedDownloadState() {
float downloadPercentage = C.PERCENTAGE_UNSET;
long downloadedBytes = 0;
long totalBytes = C.LENGTH_UNSET;
......@@ -721,6 +792,7 @@ public final class DownloadManager {
}
private void updateStopState() {
DownloadState oldDownloadState = downloadState;
if (canStart()) {
if (state == STATE_STOPPED) {
startOrQueue();
......@@ -731,6 +803,9 @@ public final class DownloadManager {
setState(STATE_STOPPED);
}
}
if (oldDownloadState == downloadState) {
downloadManager.onDownloadStateChange(this, getUpdatedDownloadState());
}
}
private void initialize(int initialState) {
......@@ -745,7 +820,7 @@ public final class DownloadManager {
setState(STATE_STOPPED);
}
if (state == initialState) {
downloadManager.onDownloadStateChange(this);
downloadManager.onDownloadStateChange(this, getUpdatedDownloadState());
}
}
......@@ -770,20 +845,18 @@ public final class DownloadManager {
return DownloadAction.createRemoveAction(
downloadState.type, downloadState.uri, downloadState.cacheKey);
}
return getDownloadAction(downloadState);
}
private void addActions(List<DownloadAction> actions) {
actions.add(getAction());
if (state == STATE_RESTARTING) {
actions.add(getDownloadAction(downloadState));
}
return DownloadAction.createDownloadAction(
downloadState.type,
downloadState.uri,
Arrays.asList(downloadState.streamKeys),
downloadState.cacheKey,
downloadState.customMetadata);
}
private void setState(@DownloadState.State int newState) {
if (state != newState) {
state = newState;
downloadManager.onDownloadStateChange(this);
downloadManager.onDownloadStateChange(this, getUpdatedDownloadState());
}
}
......@@ -808,14 +881,6 @@ public final class DownloadManager {
}
}
private static DownloadAction getDownloadAction(DownloadState downloadState) {
return DownloadAction.createDownloadAction(
downloadState.type,
downloadState.uri,
Arrays.asList(downloadState.streamKeys),
downloadState.cacheKey,
downloadState.customMetadata);
}
}
private class DownloadThread extends Thread {
......
......@@ -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