Skip to content

Commit

Permalink
Add option to clear all downloads.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tonihei authored and ojw28 committed May 15, 2019
1 parent 8325e40 commit 0698bd1
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 14 deletions.
4 changes: 4 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release notes #

### 2.10.1 ###

* Offline: Add option to remove all downloads.

### 2.10.0 ###

* Core library:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,19 @@ public void setDownloadingStatesToQueued() throws DatabaseIOException {
}
}

@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 {
ensureInitialized();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,11 @@ default void onRequirementsStateChanged(
private static final int MSG_SET_MIN_RETRY_COUNT = 5;
private static final int MSG_ADD_DOWNLOAD = 6;
private static final int MSG_REMOVE_DOWNLOAD = 7;
private static final int MSG_TASK_STOPPED = 8;
private static final int MSG_CONTENT_LENGTH_CHANGED = 9;
private static final int MSG_UPDATE_PROGRESS = 10;
private static final int MSG_RELEASE = 11;
private static final int MSG_REMOVE_ALL_DOWNLOADS = 8;
private static final int MSG_TASK_STOPPED = 9;
private static final int MSG_CONTENT_LENGTH_CHANGED = 10;
private static final int MSG_UPDATE_PROGRESS = 11;
private static final int MSG_RELEASE = 12;

private static final String TAG = "DownloadManager";

Expand Down Expand Up @@ -446,6 +447,12 @@ public void removeDownload(String id) {
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
* download index. The manager must not be accessed after this method has been called.
Expand Down Expand Up @@ -652,6 +659,9 @@ public void handleMessage(Message message) {
id = (String) message.obj;
removeDownload(id);
break;
case MSG_REMOVE_ALL_DOWNLOADS:
removeAllDownloads();
break;
case MSG_TASK_STOPPED:
Task task = (Task) message.obj;
onTaskStopped(task);
Expand Down Expand Up @@ -797,6 +807,36 @@ private void removeDownload(String id) {
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() {
for (Task task : activeTasks.values()) {
task.cancel(/* released= */ true);
Expand Down Expand Up @@ -1057,16 +1097,7 @@ private Download putDownloadWithState(Download download, @Download.State int sta
// to set STATE_STOPPED either, because it doesn't have a stopReason argument.
Assertions.checkState(
state != STATE_COMPLETED && state != STATE_FAILED && state != STATE_STOPPED);
return putDownload(
new Download(
download.request,
state,
download.startTimeMs,
/* updateTimeMs= */ System.currentTimeMillis(),
download.contentLength,
/* stopReason= */ 0,
FAILURE_REASON_NONE,
download.progress));
return putDownload(copyDownloadWithState(download, state));
}

private Download putDownload(Download download) {
Expand Down Expand Up @@ -1120,6 +1151,18 @@ private int getDownloadIndex(String id) {
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) {
return Util.compareLong(first.startTimeMs, second.startTimeMs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ public abstract class DownloadService extends Service {
public static final String 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:
*
Expand Down Expand Up @@ -296,6 +306,19 @@ public static Intent buildRemoveDownloadIntent(
.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.
*
Expand Down Expand Up @@ -414,6 +437,19 @@ public static void sendRemoveDownload(
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.
*
Expand Down Expand Up @@ -560,6 +596,9 @@ public int onStartCommand(Intent intent, int flags, int startId) {
downloadManager.removeDownload(contentId);
}
break;
case ACTION_REMOVE_ALL_DOWNLOADS:
downloadManager.removeAllDownloads();
break;
case ACTION_RESUME_DOWNLOADS:
downloadManager.resumeDownloads();
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public interface WritableDownloadIndex extends DownloadIndex {
*/
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},
* {@link Download#STATE_FAILED}).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,27 @@ public void secondSameRemoveRequestIgnored() throws Throwable {
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
public void differentDownloadRequestsMerged() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1);
Expand Down Expand Up @@ -605,6 +626,11 @@ private DownloadRunner postRemoveRequest() {
return this;
}

private DownloadRunner postRemoveAllRequest() {
runOnMainThread(() -> downloadManager.removeAllDownloads());
return this;
}

private DownloadRunner postDownloadRequest(StreamKey... keys) {
DownloadRequest downloadRequest =
new DownloadRequest(
Expand Down

0 comments on commit 0698bd1

Please sign in to comment.