mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
8325e40018
commit
0698bd1dbb
6 changed files with 146 additions and 14 deletions
|
|
@ -1,5 +1,9 @@
|
||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### 2.10.1 ###
|
||||||
|
|
||||||
|
* Offline: Add option to remove all downloads.
|
||||||
|
|
||||||
### 2.10.0 ###
|
### 2.10.0 ###
|
||||||
|
|
||||||
* Core library:
|
* Core library:
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,19 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public void setStopReason(int stopReason) throws DatabaseIOException {
|
public void setStopReason(int stopReason) throws DatabaseIOException {
|
||||||
ensureInitialized();
|
ensureInitialized();
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
// 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 {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,16 @@ public abstract class DownloadService extends Service {
|
||||||
public static final String ACTION_REMOVE_DOWNLOAD =
|
public static final String ACTION_REMOVE_DOWNLOAD =
|
||||||
"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:
|
||||||
*
|
*
|
||||||
|
|
@ -296,6 +306,19 @@ public abstract class DownloadService extends Service {
|
||||||
.putExtra(KEY_CONTENT_ID, id);
|
.putExtra(KEY_CONTENT_ID, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
|
|
@ -414,6 +437,19 @@ public abstract class DownloadService extends Service {
|
||||||
startService(context, intent, foreground);
|
startService(context, intent, foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,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}).
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,27 @@ public class DownloadManagerTest {
|
||||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void differentDownloadRequestsMerged() throws Throwable {
|
public void differentDownloadRequestsMerged() throws Throwable {
|
||||||
DownloadRunner runner = new DownloadRunner(uri1);
|
DownloadRunner runner = new DownloadRunner(uri1);
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue