Commit b3ae1d3f by tonihei Committed by Oliver Woodman

Add option to clear all downloads.

Adding an explicit option to clear all downloads prevents repeated database
access in a loop when trying to delete all downloads.

However, we still create an arbitrary number of parallel Task threads for this
and seperate callbacks for each download.

PiperOrigin-RevId: 247234181
parent 7d555888
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
### dev-v2 (not yet released) ### ### dev-v2 (not yet released) ###
* Offline: Add option to remove all downloads.
* Decoders: * Decoders:
* Prefer codecs that advertise format support over ones that do not, even if * Prefer codecs that advertise format support over ones that do not, even if
they are listed lower in the `MediaCodecList`. they are listed lower in the `MediaCodecList`.
......
...@@ -234,6 +234,19 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex { ...@@ -234,6 +234,19 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
} }
@Override @Override
public void setStatesToRemoving() throws DatabaseIOException {
ensureInitialized();
try {
ContentValues values = new ContentValues();
values.put(COLUMN_STATE, Download.STATE_REMOVING);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.update(tableName, values, /* whereClause= */ null, /* whereArgs= */ null);
} catch (SQLException e) {
throw new DatabaseIOException(e);
}
}
@Override
public void setStopReason(int stopReason) throws DatabaseIOException { public void setStopReason(int stopReason) throws DatabaseIOException {
ensureInitialized(); ensureInitialized();
try { try {
......
...@@ -133,10 +133,11 @@ public final class DownloadManager { ...@@ -133,10 +133,11 @@ public final class DownloadManager {
private static final int MSG_SET_MIN_RETRY_COUNT = 5; private static final int MSG_SET_MIN_RETRY_COUNT = 5;
private static final int MSG_ADD_DOWNLOAD = 6; private static final int MSG_ADD_DOWNLOAD = 6;
private static final int MSG_REMOVE_DOWNLOAD = 7; private static final int MSG_REMOVE_DOWNLOAD = 7;
private static final int MSG_TASK_STOPPED = 8; private static final int MSG_REMOVE_ALL_DOWNLOADS = 8;
private static final int MSG_CONTENT_LENGTH_CHANGED = 9; private static final int MSG_TASK_STOPPED = 9;
private static final int MSG_UPDATE_PROGRESS = 10; private static final int MSG_CONTENT_LENGTH_CHANGED = 10;
private static final int MSG_RELEASE = 11; private static final int MSG_UPDATE_PROGRESS = 11;
private static final int MSG_RELEASE = 12;
private static final String TAG = "DownloadManager"; private static final String TAG = "DownloadManager";
...@@ -446,6 +447,12 @@ public final class DownloadManager { ...@@ -446,6 +447,12 @@ public final class DownloadManager {
internalHandler.obtainMessage(MSG_REMOVE_DOWNLOAD, id).sendToTarget(); internalHandler.obtainMessage(MSG_REMOVE_DOWNLOAD, id).sendToTarget();
} }
/** Cancels all pending downloads and removes all downloaded data. */
public void removeAllDownloads() {
pendingMessages++;
internalHandler.obtainMessage(MSG_REMOVE_ALL_DOWNLOADS).sendToTarget();
}
/** /**
* Stops the downloads and releases resources. Waits until the downloads are persisted to the * Stops the downloads and releases resources. Waits until the downloads are persisted to the
* download index. The manager must not be accessed after this method has been called. * download index. The manager must not be accessed after this method has been called.
...@@ -652,6 +659,9 @@ public final class DownloadManager { ...@@ -652,6 +659,9 @@ public final class DownloadManager {
id = (String) message.obj; id = (String) message.obj;
removeDownload(id); removeDownload(id);
break; break;
case MSG_REMOVE_ALL_DOWNLOADS:
removeAllDownloads();
break;
case MSG_TASK_STOPPED: case MSG_TASK_STOPPED:
Task task = (Task) message.obj; Task task = (Task) message.obj;
onTaskStopped(task); onTaskStopped(task);
...@@ -797,6 +807,36 @@ public final class DownloadManager { ...@@ -797,6 +807,36 @@ public final class DownloadManager {
syncTasks(); syncTasks();
} }
private void removeAllDownloads() {
List<Download> terminalDownloads = new ArrayList<>();
try (DownloadCursor cursor = downloadIndex.getDownloads(STATE_COMPLETED, STATE_FAILED)) {
while (cursor.moveToNext()) {
terminalDownloads.add(cursor.getDownload());
}
} catch (IOException e) {
Log.e(TAG, "Failed to load downloads.");
}
for (int i = 0; i < downloads.size(); i++) {
downloads.set(i, copyDownloadWithState(downloads.get(i), STATE_REMOVING));
}
for (int i = 0; i < terminalDownloads.size(); i++) {
downloads.add(copyDownloadWithState(terminalDownloads.get(i), STATE_REMOVING));
}
Collections.sort(downloads, InternalHandler::compareStartTimes);
try {
downloadIndex.setStatesToRemoving();
} catch (IOException e) {
Log.e(TAG, "Failed to update index.", e);
}
ArrayList<Download> updateList = new ArrayList<>(downloads);
for (int i = 0; i < downloads.size(); i++) {
DownloadUpdate update =
new DownloadUpdate(downloads.get(i), /* isRemove= */ false, updateList);
mainHandler.obtainMessage(MSG_DOWNLOAD_UPDATE, update).sendToTarget();
}
syncTasks();
}
private void release() { private void release() {
for (Task task : activeTasks.values()) { for (Task task : activeTasks.values()) {
task.cancel(/* released= */ true); task.cancel(/* released= */ true);
...@@ -1057,16 +1097,7 @@ public final class DownloadManager { ...@@ -1057,16 +1097,7 @@ public final class DownloadManager {
// to set STATE_STOPPED either, because it doesn't have a stopReason argument. // to set STATE_STOPPED either, because it doesn't have a stopReason argument.
Assertions.checkState( Assertions.checkState(
state != STATE_COMPLETED && state != STATE_FAILED && state != STATE_STOPPED); state != STATE_COMPLETED && state != STATE_FAILED && state != STATE_STOPPED);
return putDownload( return putDownload(copyDownloadWithState(download, state));
new Download(
download.request,
state,
download.startTimeMs,
/* updateTimeMs= */ System.currentTimeMillis(),
download.contentLength,
/* stopReason= */ 0,
FAILURE_REASON_NONE,
download.progress));
} }
private Download putDownload(Download download) { private Download putDownload(Download download) {
...@@ -1120,6 +1151,18 @@ public final class DownloadManager { ...@@ -1120,6 +1151,18 @@ public final class DownloadManager {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
private static Download copyDownloadWithState(Download download, @Download.State int state) {
return new Download(
download.request,
state,
download.startTimeMs,
/* updateTimeMs= */ System.currentTimeMillis(),
download.contentLength,
/* stopReason= */ 0,
FAILURE_REASON_NONE,
download.progress);
}
private static int compareStartTimes(Download first, Download second) { private static int compareStartTimes(Download first, Download second) {
return Util.compareLong(first.startTimeMs, second.startTimeMs); return Util.compareLong(first.startTimeMs, second.startTimeMs);
} }
......
...@@ -78,6 +78,16 @@ public abstract class DownloadService extends Service { ...@@ -78,6 +78,16 @@ public abstract class DownloadService extends Service {
"com.google.android.exoplayer.downloadService.action.REMOVE_DOWNLOAD"; "com.google.android.exoplayer.downloadService.action.REMOVE_DOWNLOAD";
/** /**
* Removes all downloads. Extras:
*
* <ul>
* <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}.
* </ul>
*/
public static final String ACTION_REMOVE_ALL_DOWNLOADS =
"com.google.android.exoplayer.downloadService.action.REMOVE_ALL_DOWNLOADS";
/**
* Resumes all downloads except those that have a non-zero {@link Download#stopReason}. Extras: * Resumes all downloads except those that have a non-zero {@link Download#stopReason}. Extras:
* *
* <ul> * <ul>
...@@ -297,6 +307,19 @@ public abstract class DownloadService extends Service { ...@@ -297,6 +307,19 @@ public abstract class DownloadService extends Service {
} }
/** /**
* Builds an {@link Intent} for removing all downloads.
*
* @param context A {@link Context}.
* @param clazz The concrete download service being targeted by the intent.
* @param foreground Whether this intent will be used to start the service in the foreground.
* @return The created intent.
*/
public static Intent buildRemoveAllDownloadsIntent(
Context context, Class<? extends DownloadService> clazz, boolean foreground) {
return getIntent(context, clazz, ACTION_REMOVE_ALL_DOWNLOADS, foreground);
}
/**
* Builds an {@link Intent} for resuming all downloads. * Builds an {@link Intent} for resuming all downloads.
* *
* @param context A {@link Context}. * @param context A {@link Context}.
...@@ -415,6 +438,19 @@ public abstract class DownloadService extends Service { ...@@ -415,6 +438,19 @@ public abstract class DownloadService extends Service {
} }
/** /**
* Starts the service if not started already and removes all downloads.
*
* @param context A {@link Context}.
* @param clazz The concrete download service to be started.
* @param foreground Whether the service is started in the foreground.
*/
public static void sendRemoveAllDownloads(
Context context, Class<? extends DownloadService> clazz, boolean foreground) {
Intent intent = buildRemoveAllDownloadsIntent(context, clazz, foreground);
startService(context, intent, foreground);
}
/**
* Starts the service if not started already and resumes all downloads. * Starts the service if not started already and resumes all downloads.
* *
* @param context A {@link Context}. * @param context A {@link Context}.
...@@ -560,6 +596,9 @@ public abstract class DownloadService extends Service { ...@@ -560,6 +596,9 @@ public abstract class DownloadService extends Service {
downloadManager.removeDownload(contentId); downloadManager.removeDownload(contentId);
} }
break; break;
case ACTION_REMOVE_ALL_DOWNLOADS:
downloadManager.removeAllDownloads();
break;
case ACTION_RESUME_DOWNLOADS: case ACTION_RESUME_DOWNLOADS:
downloadManager.resumeDownloads(); downloadManager.resumeDownloads();
break; break;
......
...@@ -45,6 +45,13 @@ public interface WritableDownloadIndex extends DownloadIndex { ...@@ -45,6 +45,13 @@ public interface WritableDownloadIndex extends DownloadIndex {
void setDownloadingStatesToQueued() throws IOException; void setDownloadingStatesToQueued() throws IOException;
/** /**
* Sets all states to {@link Download#STATE_REMOVING}.
*
* @throws IOException If an error occurs updating the state.
*/
void setStatesToRemoving() throws IOException;
/**
* Sets the stop reason of the downloads in a terminal state ({@link Download#STATE_COMPLETED}, * Sets the stop reason of the downloads in a terminal state ({@link Download#STATE_COMPLETED},
* {@link Download#STATE_FAILED}). * {@link Download#STATE_FAILED}).
* *
......
...@@ -244,6 +244,27 @@ public class DownloadManagerTest { ...@@ -244,6 +244,27 @@ public class DownloadManagerTest {
} }
@Test @Test
public void removeAllDownloads_removesAllDownloads() throws Throwable {
// Finish one download and keep one running.
DownloadRunner runner1 = new DownloadRunner(uri1);
DownloadRunner runner2 = new DownloadRunner(uri2);
runner1.postDownloadRequest();
runner1.getDownloader(0).unblock();
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
runner2.postDownloadRequest();
runner1.postRemoveAllRequest();
runner1.getDownloader(1).unblock();
runner2.getDownloader(1).unblock();
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
runner1.getTask().assertRemoved();
runner2.getTask().assertRemoved();
assertThat(downloadManager.getCurrentDownloads()).isEmpty();
assertThat(downloadIndex.getDownloads().getCount()).isEqualTo(0);
}
@Test
public void differentDownloadRequestsMerged() throws Throwable { public void differentDownloadRequestsMerged() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1); DownloadRunner runner = new DownloadRunner(uri1);
FakeDownloader downloader1 = runner.getDownloader(0); FakeDownloader downloader1 = runner.getDownloader(0);
...@@ -605,6 +626,11 @@ public class DownloadManagerTest { ...@@ -605,6 +626,11 @@ public class DownloadManagerTest {
return this; return this;
} }
private DownloadRunner postRemoveAllRequest() {
runOnMainThread(() -> downloadManager.removeAllDownloads());
return this;
}
private DownloadRunner postDownloadRequest(StreamKey... keys) { private DownloadRunner postDownloadRequest(StreamKey... keys) {
DownloadRequest downloadRequest = DownloadRequest downloadRequest =
new DownloadRequest( new DownloadRequest(
......
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