Commit 9779d71a by olly Committed by Oliver Woodman

Fix stopping downloads

- Changed startDownloads/stopDownloads back to their previous behavior
  of starting and stopping all downloads at the manager level.
- Made setManualStopReason methods for the new case of setting a manual
  stop reason or some or all downloads.
- Added plumbing to specify an initial manual stop reason when adding a
  new download, without also overwriting the manual stop reasons of all
  other preexisting downloads. Using the value is left as a TODO pending
  a bit of further cleanup that'll make it easier.

PiperOrigin-RevId: 242891688
parent 68993f23
...@@ -46,7 +46,7 @@ public final class Download { ...@@ -46,7 +46,7 @@ public final class Download {
// Important: These constants are persisted into DownloadIndex. Do not change them. // Important: These constants are persisted into DownloadIndex. Do not change them.
/** The download is waiting to be started. */ /** The download is waiting to be started. */
public static final int STATE_QUEUED = 0; public static final int STATE_QUEUED = 0;
/** The download is stopped. */ /** The download is manually stopped for the reason specified by {@link #manualStopReason}. */
public static final int STATE_STOPPED = 1; public static final int STATE_STOPPED = 1;
/** The download is currently started. */ /** The download is currently started. */
public static final int STATE_DOWNLOADING = 2; public static final int STATE_DOWNLOADING = 2;
...@@ -71,8 +71,6 @@ public final class Download { ...@@ -71,8 +71,6 @@ public final class Download {
/** The download isn't manually stopped. */ /** The download isn't manually stopped. */
public static final int MANUAL_STOP_REASON_NONE = 0; public static final int MANUAL_STOP_REASON_NONE = 0;
/** The download is manually stopped but a reason isn't specified. */
public static final int MANUAL_STOP_REASON_UNDEFINED = Integer.MAX_VALUE;
/** Returns the state string for the given state value. */ /** Returns the state string for the given state value. */
public static String getStateString(@State int state) { public static String getStateString(@State int state) {
...@@ -122,7 +120,7 @@ public final class Download { ...@@ -122,7 +120,7 @@ public final class Download {
* #FAILURE_REASON_NONE}. * #FAILURE_REASON_NONE}.
*/ */
@FailureReason public final int failureReason; @FailureReason public final int failureReason;
/** The manual stop reason. */ /** The reason the download is manually stopped, or {@link #MANUAL_STOP_REASON_NONE}. */
public final int manualStopReason; public final int manualStopReason;
/*package*/ CachingCounters counters; /*package*/ CachingCounters counters;
...@@ -232,7 +230,7 @@ public final class Download { ...@@ -232,7 +230,7 @@ public final class Download {
this.counters = counters; this.counters = counters;
} }
private static int getNextState(int currentState, boolean canStart) { private static int getNextState(@State int currentState, boolean canStart) {
if (currentState == STATE_REMOVING || currentState == STATE_RESTARTING) { if (currentState == STATE_REMOVING || currentState == STATE_RESTARTING) {
return STATE_RESTARTING; return STATE_RESTARTING;
} else if (canStart) { } else if (canStart) {
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.offline; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.offline;
import static com.google.android.exoplayer2.offline.Download.FAILURE_REASON_NONE; import static com.google.android.exoplayer2.offline.Download.FAILURE_REASON_NONE;
import static com.google.android.exoplayer2.offline.Download.FAILURE_REASON_UNKNOWN; import static com.google.android.exoplayer2.offline.Download.FAILURE_REASON_UNKNOWN;
import static com.google.android.exoplayer2.offline.Download.MANUAL_STOP_REASON_NONE; import static com.google.android.exoplayer2.offline.Download.MANUAL_STOP_REASON_NONE;
import static com.google.android.exoplayer2.offline.Download.MANUAL_STOP_REASON_UNDEFINED;
import static com.google.android.exoplayer2.offline.Download.STATE_COMPLETED; import static com.google.android.exoplayer2.offline.Download.STATE_COMPLETED;
import static com.google.android.exoplayer2.offline.Download.STATE_DOWNLOADING; import static com.google.android.exoplayer2.offline.Download.STATE_DOWNLOADING;
import static com.google.android.exoplayer2.offline.Download.STATE_FAILED; import static com.google.android.exoplayer2.offline.Download.STATE_FAILED;
...@@ -53,7 +52,11 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -53,7 +52,11 @@ import java.util.concurrent.CopyOnWriteArraySet;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Manages multiple stream download and remove requests. * Manages downloads.
*
* <p>Normally a download manager should be accessed via a {@link DownloadService}. When a download
* manager is used directly instead, downloads will be initially stopped and so must be started by
* calling {@link #startDownloads()}.
* *
* <p>A download manager instance must be accessed only from the thread that created it, unless that * <p>A download manager instance must be accessed only from the thread that created it, unless that
* thread does not have a {@link Looper}. In that case, it must be accessed only from the * thread does not have a {@link Looper}. In that case, it must be accessed only from the
...@@ -122,12 +125,13 @@ public final class DownloadManager { ...@@ -122,12 +125,13 @@ public final class DownloadManager {
// Messages posted to the background handler. // Messages posted to the background handler.
private static final int MSG_INITIALIZE = 0; private static final int MSG_INITIALIZE = 0;
private static final int MSG_ADD_DOWNLOAD = 1; private static final int MSG_SET_DOWNLOADS_STARTED = 1;
private static final int MSG_REMOVE_DOWNLOAD = 2; private static final int MSG_SET_NOT_MET_REQUIREMENTS = 2;
private static final int MSG_SET_MANUAL_STOP_REASON = 3; private static final int MSG_SET_MANUAL_STOP_REASON = 3;
private static final int MSG_SET_NOT_MET_REQUIREMENTS = 4; private static final int MSG_ADD_DOWNLOAD = 4;
private static final int MSG_DOWNLOAD_THREAD_STOPPED = 5; private static final int MSG_REMOVE_DOWNLOAD = 5;
private static final int MSG_RELEASE = 6; private static final int MSG_DOWNLOAD_THREAD_STOPPED = 6;
private static final int MSG_RELEASE = 7;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
...@@ -174,7 +178,7 @@ public final class DownloadManager { ...@@ -174,7 +178,7 @@ public final class DownloadManager {
// Mutable fields that are accessed on the internal thread. // Mutable fields that are accessed on the internal thread.
@Requirements.RequirementFlags private int notMetRequirements; @Requirements.RequirementFlags private int notMetRequirements;
private int manualStopReason; private boolean downloadsStarted;
private int simultaneousDownloads; private int simultaneousDownloads;
/** /**
...@@ -244,7 +248,6 @@ public final class DownloadManager { ...@@ -244,7 +248,6 @@ public final class DownloadManager {
this.maxSimultaneousDownloads = maxSimultaneousDownloads; this.maxSimultaneousDownloads = maxSimultaneousDownloads;
this.minRetryCount = minRetryCount; this.minRetryCount = minRetryCount;
manualStopReason = MANUAL_STOP_REASON_UNDEFINED;
downloadInternals = new ArrayList<>(); downloadInternals = new ArrayList<>();
downloads = new ArrayList<>(); downloads = new ArrayList<>();
activeDownloads = new HashMap<>(); activeDownloads = new HashMap<>();
...@@ -331,73 +334,60 @@ public final class DownloadManager { ...@@ -331,73 +334,60 @@ public final class DownloadManager {
} }
/** /**
* Clears manual stop reason of all downloads. Downloads are started if the requirements are met. * Starts all downloads except those that are manually stopped (i.e. have a non-zero {@link
* Download#manualStopReason}).
*/ */
public void startDownloads() { public void startDownloads() {
postSetManualStopReason(/* id= */ null, MANUAL_STOP_REASON_NONE); pendingMessages++;
internalHandler
.obtainMessage(MSG_SET_DOWNLOADS_STARTED, /* downloadsStarted */ 1, /* unused */ 0)
.sendToTarget();
} }
/** Signals all downloads to stop. Call {@link #startDownloads()} to let them to be started. */ /** Stops all downloads. */
public void stopDownloads() { public void stopDownloads() {
stopDownloads(MANUAL_STOP_REASON_UNDEFINED); pendingMessages++;
} internalHandler
.obtainMessage(MSG_SET_DOWNLOADS_STARTED, /* downloadsStarted */ 0, /* unused */ 0)
/** .sendToTarget();
* Sets a manual stop reason for all downloads.
*
* @param manualStopReason An application defined stop reason. Value {@value
* Download#MANUAL_STOP_REASON_NONE} is not allowed and value {@value
* Download#MANUAL_STOP_REASON_UNDEFINED} is reserved for {@link
* Download#MANUAL_STOP_REASON_UNDEFINED}.
*/
public void stopDownloads(int manualStopReason) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE);
postSetManualStopReason(/* id= */ null, manualStopReason);
}
/**
* Clears manual stop reason of the download with the {@code id}. Download is started if the
* requirements are met.
*
* @param id The unique content id of the download to be started.
*/
public void startDownload(String id) {
postSetManualStopReason(id, MANUAL_STOP_REASON_NONE);
} }
/** /**
* Signals the download with the {@code id} to stop. Call {@link #startDownload(String)} to let it * Sets the manual stop reason for one or all downloads. To clear the manual stop reason, pass
* to be started. * {@link Download#MANUAL_STOP_REASON_NONE}.
* *
* @param id The unique content id of the download to be stopped. * @param id The content id of the download to update, or {@code null} to set the manual stop
* reason for all downloads.
* @param manualStopReason The manual stop reason, or {@link Download#MANUAL_STOP_REASON_NONE}.
*/ */
public void stopDownload(String id) { public void setManualStopReason(@Nullable String id, int manualStopReason) {
stopDownload(id, MANUAL_STOP_REASON_UNDEFINED); pendingMessages++;
internalHandler
.obtainMessage(MSG_SET_MANUAL_STOP_REASON, manualStopReason, /* unused */ 0, id)
.sendToTarget();
} }
/** /**
* Signals the download with the {@code id} to stop. Call {@link #startDownload(String)} to let it * Adds a download defined by the given action.
* to be started.
* *
* @param id The unique content id of the download to be stopped. * @param action The download action.
* @param manualStopReason An application defined stop reason. Value {@value
* Download#MANUAL_STOP_REASON_NONE} is not allowed and value {@value
* Download#MANUAL_STOP_REASON_UNDEFINED} is reserved for {@link
* Download#MANUAL_STOP_REASON_UNDEFINED}.
*/ */
public void stopDownload(String id, int manualStopReason) { public void addDownload(DownloadAction action) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE); addDownload(action, Download.MANUAL_STOP_REASON_NONE);
postSetManualStopReason(id, manualStopReason);
} }
/** /**
* Adds a download defined by the given action. * Adds a download defined by the given action and with the specified manual stop reason.
* *
* @param action The download action. * @param action The download action.
* @param manualStopReason An initial manual stop reason for the download, or {@link
* Download#MANUAL_STOP_REASON_NONE} if the download should be started.
*/ */
public void addDownload(DownloadAction action) { public void addDownload(DownloadAction action, int manualStopReason) {
pendingMessages++; pendingMessages++;
internalHandler.obtainMessage(MSG_ADD_DOWNLOAD, action).sendToTarget(); internalHandler
.obtainMessage(MSG_ADD_DOWNLOAD, manualStopReason, /* unused */ 0, action)
.sendToTarget();
} }
/** /**
...@@ -442,13 +432,6 @@ public final class DownloadManager { ...@@ -442,13 +432,6 @@ public final class DownloadManager {
} }
} }
private void postSetManualStopReason(@Nullable String id, int manualStopReason) {
pendingMessages++;
internalHandler
.obtainMessage(MSG_SET_MANUAL_STOP_REASON, manualStopReason, /* unused */ 0, id)
.sendToTarget();
}
private void onRequirementsStateChanged( private void onRequirementsStateChanged(
RequirementsWatcher requirementsWatcher, RequirementsWatcher requirementsWatcher,
@Requirements.RequirementFlags int notMetRequirements) { @Requirements.RequirementFlags int notMetRequirements) {
...@@ -551,27 +534,32 @@ public final class DownloadManager { ...@@ -551,27 +534,32 @@ public final class DownloadManager {
int notMetRequirements = message.arg1; int notMetRequirements = message.arg1;
initializeInternal(notMetRequirements); initializeInternal(notMetRequirements);
break; break;
case MSG_ADD_DOWNLOAD: case MSG_SET_DOWNLOADS_STARTED:
DownloadAction action = (DownloadAction) message.obj; boolean downloadsStarted = message.arg1 != 0;
addDownloadInternal(action); setDownloadsStartedInternal(downloadsStarted);
break; break;
case MSG_REMOVE_DOWNLOAD: case MSG_SET_NOT_MET_REQUIREMENTS:
String id = (String) message.obj; notMetRequirements = message.arg1;
removeDownloadInternal(id); setNotMetRequirementsInternal(notMetRequirements);
break; break;
case MSG_SET_MANUAL_STOP_REASON: case MSG_SET_MANUAL_STOP_REASON:
id = (String) message.obj; String id = (String) message.obj;
int manualStopReason = message.arg1; int manualStopReason = message.arg1;
setManualStopReasonInternal(id, manualStopReason); setManualStopReasonInternal(id, manualStopReason);
break; break;
case MSG_SET_NOT_MET_REQUIREMENTS: case MSG_ADD_DOWNLOAD:
notMetRequirements = message.arg1; DownloadAction action = (DownloadAction) message.obj;
setNotMetRequirementsInternal(notMetRequirements); manualStopReason = message.arg1;
addDownloadInternal(action, manualStopReason);
break;
case MSG_REMOVE_DOWNLOAD:
id = (String) message.obj;
removeDownloadInternal(id);
break; break;
case MSG_DOWNLOAD_THREAD_STOPPED: case MSG_DOWNLOAD_THREAD_STOPPED:
DownloadThread downloadThread = (DownloadThread) message.obj; DownloadThread downloadThread = (DownloadThread) message.obj;
onDownloadThreadStoppedInternal(downloadThread); onDownloadThreadStoppedInternal(downloadThread);
processedExternalMessage = false; processedExternalMessage = false; // This message is posted internally.
break; break;
case MSG_RELEASE: case MSG_RELEASE:
releaseInternal(); releaseInternal();
...@@ -594,7 +582,6 @@ public final class DownloadManager { ...@@ -594,7 +582,6 @@ public final class DownloadManager {
return; return;
} }
} else { } else {
this.manualStopReason = manualStopReason;
for (int i = 0; i < downloadInternals.size(); i++) { for (int i = 0; i < downloadInternals.size(); i++) {
downloadInternals.get(i).setManualStopReason(manualStopReason); downloadInternals.get(i).setManualStopReason(manualStopReason);
} }
...@@ -610,7 +597,8 @@ public final class DownloadManager { ...@@ -610,7 +597,8 @@ public final class DownloadManager {
} }
} }
private void addDownloadInternal(DownloadAction action) { // TODO: Use manualStopReason.
private void addDownloadInternal(DownloadAction action, int manualStopReason) {
DownloadInternal downloadInternal = getDownload(action.id); DownloadInternal downloadInternal = getDownload(action.id);
if (downloadInternal != null) { if (downloadInternal != null) {
downloadInternal.addAction(action); downloadInternal.addAction(action);
...@@ -621,7 +609,7 @@ public final class DownloadManager { ...@@ -621,7 +609,7 @@ public final class DownloadManager {
download = new Download(action); download = new Download(action);
logd("Download state is created for " + action.id); logd("Download state is created for " + action.id);
} else { } else {
download = download.copyWithMergedAction(action, /* canStart= */ notMetRequirements == 0); download = download.copyWithMergedAction(action, /* canStart= */ canStartDownloads());
logd("Download state is loaded for " + action.id); logd("Download state is loaded for " + action.id);
} }
addDownloadForState(download); addDownloadForState(download);
...@@ -666,6 +654,16 @@ public final class DownloadManager { ...@@ -666,6 +654,16 @@ public final class DownloadManager {
mainHandler.obtainMessage(MSG_DOWNLOAD_REMOVED, download).sendToTarget(); mainHandler.obtainMessage(MSG_DOWNLOAD_REMOVED, download).sendToTarget();
} }
private void setDownloadsStartedInternal(boolean downloadsStarted) {
if (this.downloadsStarted == downloadsStarted) {
return;
}
this.downloadsStarted = downloadsStarted;
for (int i = 0; i < downloadInternals.size(); i++) {
downloadInternals.get(i).updateStopState();
}
}
private void setNotMetRequirementsInternal( private void setNotMetRequirementsInternal(
@Requirements.RequirementFlags int notMetRequirements) { @Requirements.RequirementFlags int notMetRequirements) {
if (this.notMetRequirements == notMetRequirements) { if (this.notMetRequirements == notMetRequirements) {
...@@ -674,7 +672,7 @@ public final class DownloadManager { ...@@ -674,7 +672,7 @@ public final class DownloadManager {
this.notMetRequirements = notMetRequirements; this.notMetRequirements = notMetRequirements;
logdFlags("Not met requirements are changed", notMetRequirements); logdFlags("Not met requirements are changed", notMetRequirements);
for (int i = 0; i < downloadInternals.size(); i++) { for (int i = 0; i < downloadInternals.size(); i++) {
downloadInternals.get(i).setNotMetRequirements(notMetRequirements); downloadInternals.get(i).updateStopState();
} }
} }
...@@ -682,7 +680,7 @@ public final class DownloadManager { ...@@ -682,7 +680,7 @@ public final class DownloadManager {
private DownloadInternal getDownload(String id) { private DownloadInternal getDownload(String id) {
for (int i = 0; i < downloadInternals.size(); i++) { for (int i = 0; i < downloadInternals.size(); i++) {
DownloadInternal downloadInternal = downloadInternals.get(i); DownloadInternal downloadInternal = downloadInternals.get(i);
if (downloadInternal.getId().equals(id)) { if (downloadInternal.download.action.id.equals(id)) {
return downloadInternal; return downloadInternal;
} }
} }
...@@ -723,13 +721,16 @@ public final class DownloadManager { ...@@ -723,13 +721,16 @@ public final class DownloadManager {
} }
private void addDownloadForState(Download download) { private void addDownloadForState(Download download) {
DownloadInternal downloadInternal = DownloadInternal downloadInternal = new DownloadInternal(this, download);
new DownloadInternal(this, download, notMetRequirements, manualStopReason);
downloadInternals.add(downloadInternal); downloadInternals.add(downloadInternal);
logd("Download is added", downloadInternal); logd("Download is added", downloadInternal);
downloadInternal.initialize(); downloadInternal.initialize();
} }
private boolean canStartDownloads() {
return downloadsStarted && notMetRequirements == 0;
}
private static void logd(String message) { private static void logd(String message) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, message); Log.d(TAG, message);
...@@ -814,32 +815,24 @@ public final class DownloadManager { ...@@ -814,32 +815,24 @@ public final class DownloadManager {
private final DownloadManager downloadManager; private final DownloadManager downloadManager;
private Download download; private Download download;
@Download.State private int state;
@MonotonicNonNull @Download.FailureReason private int failureReason; @MonotonicNonNull @Download.FailureReason private int failureReason;
@Requirements.RequirementFlags private int notMetRequirements;
// TODO: Get rid of these and use download directly.
@Download.State private int state;
private int manualStopReason; private int manualStopReason;
private DownloadInternal( private DownloadInternal(DownloadManager downloadManager, Download download) {
DownloadManager downloadManager,
Download download,
@Requirements.RequirementFlags int notMetRequirements,
int manualStopReason) {
this.downloadManager = downloadManager; this.downloadManager = downloadManager;
this.download = download; this.download = download;
this.notMetRequirements = notMetRequirements; manualStopReason = download.manualStopReason;
this.manualStopReason = manualStopReason;
} }
private void initialize() { private void initialize() {
initialize(download.state); initialize(download.state);
} }
public String getId() {
return download.action.id;
}
public void addAction(DownloadAction newAction) { public void addAction(DownloadAction newAction) {
download = download.copyWithMergedAction(newAction, /* canStart= */ notMetRequirements == 0); download = download.copyWithMergedAction(newAction, downloadManager.canStartDownloads());
initialize(); initialize();
} }
...@@ -866,7 +859,7 @@ public final class DownloadManager { ...@@ -866,7 +859,7 @@ public final class DownloadManager {
@Override @Override
public String toString() { public String toString() {
return getId() + ' ' + Download.getStateString(state); return download.action.id + ' ' + Download.getStateString(state);
} }
public void start() { public void start() {
...@@ -877,11 +870,6 @@ public final class DownloadManager { ...@@ -877,11 +870,6 @@ public final class DownloadManager {
} }
} }
public void setNotMetRequirements(@Requirements.RequirementFlags int notMetRequirements) {
this.notMetRequirements = notMetRequirements;
updateStopState();
}
public void setManualStopReason(int manualStopReason) { public void setManualStopReason(int manualStopReason) {
this.manualStopReason = manualStopReason; this.manualStopReason = manualStopReason;
updateStopState(); updateStopState();
...@@ -913,8 +901,8 @@ public final class DownloadManager { ...@@ -913,8 +901,8 @@ public final class DownloadManager {
} }
private void initialize(int initialState) { private void initialize(int initialState) {
// Don't notify listeners with initial state until we make sure we don't switch to // Don't notify listeners with initial state until we make sure we don't switch to another
// another state immediately. // state immediately.
state = initialState; state = initialState;
if (isInRemoveState()) { if (isInRemoveState()) {
downloadManager.startDownloadThread(this); downloadManager.startDownloadThread(this);
...@@ -929,7 +917,7 @@ public final class DownloadManager { ...@@ -929,7 +917,7 @@ public final class DownloadManager {
} }
private boolean canStart() { private boolean canStart() {
return manualStopReason == MANUAL_STOP_REASON_NONE && notMetRequirements == 0; return downloadManager.canStartDownloads() && manualStopReason == MANUAL_STOP_REASON_NONE;
} }
private void startOrQueue() { private void startOrQueue() {
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.offline; package com.google.android.exoplayer2.offline;
import static com.google.android.exoplayer2.offline.Download.MANUAL_STOP_REASON_NONE;
import android.app.Notification; import android.app.Notification;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
...@@ -55,17 +57,18 @@ public abstract class DownloadService extends Service { ...@@ -55,17 +57,18 @@ public abstract class DownloadService extends Service {
* <ul> * <ul>
* <li>{@link #KEY_DOWNLOAD_ACTION} - A {@link DownloadAction} defining the download to be * <li>{@link #KEY_DOWNLOAD_ACTION} - A {@link DownloadAction} defining the download to be
* added. * added.
* <li>{@link #KEY_MANUAL_STOP_REASON} - An initial manual stop reason for the download. If
* omitted {@link Download#MANUAL_STOP_REASON_NONE} is used.
* <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}. * <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}.
* </ul> * </ul>
*/ */
public static final String ACTION_ADD = "com.google.android.exoplayer.downloadService.action.ADD"; public static final String ACTION_ADD = "com.google.android.exoplayer.downloadService.action.ADD";
/** /**
* Starts one or all of the current downloads. Extras: * Starts all downloads except those that are manually stopped (i.e. have a non-zero {@link
* Download#manualStopReason}). Extras:
* *
* <ul> * <ul>
* <li>{@link #KEY_CONTENT_ID} - The content id of a single download to start. If omitted, all
* of the current downloads will be started.
* <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}. * <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}.
* </ul> * </ul>
*/ */
...@@ -73,14 +76,9 @@ public abstract class DownloadService extends Service { ...@@ -73,14 +76,9 @@ public abstract class DownloadService extends Service {
"com.google.android.exoplayer.downloadService.action.START"; "com.google.android.exoplayer.downloadService.action.START";
/** /**
* Stops one or all of the current downloads. Extras: * Stops all downloads. Extras:
* *
* <ul> * <ul>
* <li>{@link #KEY_CONTENT_ID} - The content id of a single download to stop. If omitted, all of
* the current downloads will be stopped.
* <li>{@link #KEY_STOP_REASON} - An application provided reason for stopping the download or
* downloads. Must not be {@link Download#MANUAL_STOP_REASON_NONE}. If omitted, the stop
* reason will be {@link Download#MANUAL_STOP_REASON_UNDEFINED}.
* <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}. * <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}.
* </ul> * </ul>
*/ */
...@@ -88,6 +86,22 @@ public abstract class DownloadService extends Service { ...@@ -88,6 +86,22 @@ public abstract class DownloadService extends Service {
"com.google.android.exoplayer.downloadService.action.STOP"; "com.google.android.exoplayer.downloadService.action.STOP";
/** /**
* Sets the manual stop reason for one or all downloads. To clear the manual stop reason, pass
* {@link Download#MANUAL_STOP_REASON_NONE}. Extras:
*
* <ul>
* <li>{@link #KEY_CONTENT_ID} - The content id of a single download to update with the manual
* stop reason. If omitted, all downloads will be updated.
* <li>{@link #KEY_MANUAL_STOP_REASON} - An application provided reason for stopping the
* download or downloads, or {@link Download#MANUAL_STOP_REASON_NONE} to clear the manual
* stop reason.
* <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}.
* </ul>
*/
public static final String ACTION_SET_MANUAL_STOP_REASON =
"com.google.android.exoplayer.downloadService.action.SET_MANUAL_STOP_REASON";
/**
* Removes an existing download. Extras: * Removes an existing download. Extras:
* *
* <ul> * <ul>
...@@ -110,8 +124,11 @@ public abstract class DownloadService extends Service { ...@@ -110,8 +124,11 @@ public abstract class DownloadService extends Service {
*/ */
public static final String KEY_CONTENT_ID = "content_id"; public static final String KEY_CONTENT_ID = "content_id";
/** Key for the stop reason in {@link #ACTION_STOP} intents. */ /**
public static final String KEY_STOP_REASON = "stop_reason"; * Key for the manual stop reason in {@link #ACTION_SET_MANUAL_STOP_REASON} and {@link
* #ACTION_ADD} intents.
*/
public static final String KEY_MANUAL_STOP_REASON = "manual_stop_reason";
/** /**
* Key for a boolean extra that can be set on any intent to indicate whether the service was * Key for a boolean extra that can be set on any intent to indicate whether the service was
...@@ -228,59 +245,67 @@ public abstract class DownloadService extends Service { ...@@ -228,59 +245,67 @@ public abstract class DownloadService extends Service {
Class<? extends DownloadService> clazz, Class<? extends DownloadService> clazz,
DownloadAction downloadAction, DownloadAction downloadAction,
boolean foreground) { boolean foreground) {
return getIntent(context, clazz, ACTION_ADD) return buildAddActionIntent(
.putExtra(KEY_DOWNLOAD_ACTION, downloadAction) context, clazz, downloadAction, MANUAL_STOP_REASON_NONE, foreground);
.putExtra(KEY_FOREGROUND, foreground);
} }
/** /**
* Builds an {@link Intent} for stopping the download with the {@code id}. If {@code id} is null, * Builds an {@link Intent} for adding an action to be executed by the service.
* stops all downloads.
* *
* @param context A {@link Context}. * @param context A {@link Context}.
* @param clazz The concrete download service being targeted by the intent. * @param clazz The concrete download service being targeted by the intent.
* @param id The content id, or null if all downloads should be stopped. * @param downloadAction The action to be executed.
* @param manualStopReason An application defined stop reason. * @param manualStopReason An initial manual stop reason for the download, or {@link
* Download#MANUAL_STOP_REASON_NONE} if the download should be started.
* @param foreground Whether this intent will be used to start the service in the foreground.
* @return Created Intent. * @return Created Intent.
*/ */
public static Intent buildStopDownloadIntent( public static Intent buildAddActionIntent(
Context context, Context context,
Class<? extends DownloadService> clazz, Class<? extends DownloadService> clazz,
@Nullable String id, DownloadAction downloadAction,
int manualStopReason) { int manualStopReason,
return getIntent(context, clazz, ACTION_STOP) boolean foreground) {
.putExtra(KEY_CONTENT_ID, id) return getIntent(context, clazz, ACTION_ADD)
.putExtra(KEY_STOP_REASON, manualStopReason); .putExtra(KEY_DOWNLOAD_ACTION, downloadAction)
.putExtra(KEY_MANUAL_STOP_REASON, manualStopReason)
.putExtra(KEY_FOREGROUND, foreground);
} }
/** /**
* Builds an {@link Intent} for starting the download with the {@code id}. If {@code id} is null, * Builds an {@link Intent} for removing the download with the {@code id}.
* starts all downloads.
* *
* @param context A {@link Context}. * @param context A {@link Context}.
* @param clazz The concrete download service being targeted by the intent. * @param clazz The concrete download service being targeted by the intent.
* @param id The content id, or null if all downloads should be started. * @param id The content id.
* @param foreground Whether this intent will be used to start the service in the foreground.
* @return Created Intent. * @return Created Intent.
*/ */
public static Intent buildStartDownloadIntent( public static Intent buildRemoveDownloadIntent(
Context context, Class<? extends DownloadService> clazz, @Nullable String id) { Context context, Class<? extends DownloadService> clazz, String id, boolean foreground) {
return getIntent(context, clazz, ACTION_START).putExtra(KEY_CONTENT_ID, id); return getIntent(context, clazz, ACTION_REMOVE)
.putExtra(KEY_CONTENT_ID, id)
.putExtra(KEY_FOREGROUND, foreground);
} }
/** /**
* Builds an {@link Intent} for removing the download with the {@code id}. * Builds an {@link Intent} for setting the manual stop reason for one or all downloads. To clear
* the manual stop reason, pass {@link Download#MANUAL_STOP_REASON_NONE}.
* *
* @param context A {@link Context}. * @param context A {@link Context}.
* @param clazz The concrete download service being targeted by the intent. * @param clazz The concrete download service being targeted by the intent.
* @param id The content id. * @param id The content id, or {@code null} to set the manual stop reason for all downloads.
* @param foreground Whether this intent will be used to start the service in the foreground. * @param manualStopReason An application defined stop reason.
* @return Created Intent. * @return Created Intent.
*/ */
public static Intent buildRemoveDownloadIntent( public static Intent buildSetManualStopReasonIntent(
Context context, Class<? extends DownloadService> clazz, String id, boolean foreground) { Context context,
return getIntent(context, clazz, ACTION_REMOVE) Class<? extends DownloadService> clazz,
@Nullable String id,
int manualStopReason) {
return getIntent(context, clazz, ACTION_STOP)
.putExtra(KEY_CONTENT_ID, id) .putExtra(KEY_CONTENT_ID, id)
.putExtra(KEY_FOREGROUND, foreground); .putExtra(KEY_MANUAL_STOP_REASON, manualStopReason);
} }
/** /**
...@@ -358,9 +383,11 @@ public abstract class DownloadService extends Service { ...@@ -358,9 +383,11 @@ public abstract class DownloadService extends Service {
Class<? extends DownloadService> clazz = getClass(); Class<? extends DownloadService> clazz = getClass();
DownloadManagerHelper downloadManagerHelper = downloadManagerListeners.get(clazz); DownloadManagerHelper downloadManagerHelper = downloadManagerListeners.get(clazz);
if (downloadManagerHelper == null) { if (downloadManagerHelper == null) {
DownloadManager downloadManager = getDownloadManager();
downloadManager.startDownloads();
downloadManagerHelper = downloadManagerHelper =
new DownloadManagerHelper( new DownloadManagerHelper(
getApplicationContext(), getDownloadManager(), getScheduler(), clazz); getApplicationContext(), downloadManager, getScheduler(), clazz);
downloadManagerListeners.put(clazz, downloadManagerHelper); downloadManagerListeners.put(clazz, downloadManagerHelper);
} }
downloadManager = downloadManagerHelper.downloadManager; downloadManager = downloadManagerHelper.downloadManager;
...@@ -390,32 +417,33 @@ public abstract class DownloadService extends Service { ...@@ -390,32 +417,33 @@ public abstract class DownloadService extends Service {
case ACTION_ADD: case ACTION_ADD:
DownloadAction downloadAction = intent.getParcelableExtra(KEY_DOWNLOAD_ACTION); DownloadAction downloadAction = intent.getParcelableExtra(KEY_DOWNLOAD_ACTION);
if (downloadAction == null) { if (downloadAction == null) {
Log.e(TAG, "Ignored ADD action: Missing download_action extra"); Log.e(TAG, "Ignored ADD: Missing download_action extra");
} else { } else {
downloadManager.addDownload(downloadAction); int manualStopReason =
intent.getIntExtra(KEY_MANUAL_STOP_REASON, Download.MANUAL_STOP_REASON_NONE);
downloadManager.addDownload(downloadAction, manualStopReason);
} }
break; break;
case ACTION_START: case ACTION_START:
String contentId = intent.getStringExtra(KEY_CONTENT_ID); downloadManager.startDownloads();
if (contentId == null) {
downloadManager.startDownloads();
} else {
downloadManager.startDownload(contentId);
}
break; break;
case ACTION_STOP: case ACTION_STOP:
contentId = intent.getStringExtra(KEY_CONTENT_ID); downloadManager.stopDownloads();
int stopReason = intent.getIntExtra(KEY_STOP_REASON, Download.MANUAL_STOP_REASON_UNDEFINED); break;
if (contentId == null) { case ACTION_SET_MANUAL_STOP_REASON:
downloadManager.stopDownloads(stopReason); if (!intent.hasExtra(KEY_MANUAL_STOP_REASON)) {
Log.e(TAG, "Ignored SET_MANUAL_STOP_REASON: Missing manual_stop_reason extra");
} else { } else {
downloadManager.stopDownload(contentId, stopReason); String contentId = intent.getStringExtra(KEY_CONTENT_ID);
int manualStopReason =
intent.getIntExtra(KEY_MANUAL_STOP_REASON, Download.MANUAL_STOP_REASON_NONE);
downloadManager.setManualStopReason(contentId, manualStopReason);
} }
break; break;
case ACTION_REMOVE: case ACTION_REMOVE:
contentId = intent.getStringExtra(KEY_CONTENT_ID); String contentId = intent.getStringExtra(KEY_CONTENT_ID);
if (contentId == null) { if (contentId == null) {
Log.e(TAG, "Ignored REMOVE action: Missing content_id extra"); Log.e(TAG, "Ignored REMOVE: Missing content_id extra");
} else { } else {
downloadManager.removeDownload(contentId); downloadManager.removeDownload(contentId);
} }
......
...@@ -47,14 +47,16 @@ import org.robolectric.shadows.ShadowLog; ...@@ -47,14 +47,16 @@ import org.robolectric.shadows.ShadowLog;
@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class}) @Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public class DownloadManagerTest { public class DownloadManagerTest {
/* Used to check if condition becomes true in this time interval. */ /** Used to check if condition becomes true in this time interval. */
private static final int ASSERT_TRUE_TIMEOUT = 10000; private static final int ASSERT_TRUE_TIMEOUT = 10000;
/* Used to check if condition stays false for this time interval. */ /** Used to check if condition stays false for this time interval. */
private static final int ASSERT_FALSE_TIME = 1000; private static final int ASSERT_FALSE_TIME = 1000;
/* Maximum retry delay in DownloadManager. */ /** Maximum retry delay in DownloadManager. */
private static final int MAX_RETRY_DELAY = 5000; private static final int MAX_RETRY_DELAY = 5000;
/* Maximum number of times a downloader can be restarted before doing a released check. */ /** Maximum number of times a downloader can be restarted before doing a released check. */
private static final int MAX_STARTS_BEFORE_RELEASED = 1; private static final int MAX_STARTS_BEFORE_RELEASED = 1;
/** A manual stop reason. */
private static final int APP_STOP_REASON = 1;
/** The minimum number of times a task must be retried before failing. */ /** The minimum number of times a task must be retried before failing. */
private static final int MIN_RETRY_COUNT = 3; private static final int MIN_RETRY_COUNT = 3;
...@@ -388,17 +390,18 @@ public class DownloadManagerTest { ...@@ -388,17 +390,18 @@ public class DownloadManagerTest {
} }
@Test @Test
public void stopAndResumeSingleDownload() throws Throwable { public void manuallyStopAndResumeSingleDownload() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1).postDownloadAction(); DownloadRunner runner = new DownloadRunner(uri1).postDownloadAction();
TaskWrapper task = runner.getTask(); TaskWrapper task = runner.getTask();
task.assertDownloading(); task.assertDownloading();
runOnMainThread(() -> downloadManager.stopDownload(task.taskId)); runOnMainThread(() -> downloadManager.setManualStopReason(task.taskId, APP_STOP_REASON));
task.assertStopped(); task.assertStopped();
runOnMainThread(() -> downloadManager.startDownload(task.taskId)); runOnMainThread(
() -> downloadManager.setManualStopReason(task.taskId, Download.MANUAL_STOP_REASON_NONE));
runner.getDownloader(1).assertStarted().unblock(); runner.getDownloader(1).assertStarted().unblock();
...@@ -406,13 +409,13 @@ public class DownloadManagerTest { ...@@ -406,13 +409,13 @@ public class DownloadManagerTest {
} }
@Test @Test
public void stoppedDownloadCanBeCancelled() throws Throwable { public void manuallyStoppedDownloadCanBeCancelled() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1).postDownloadAction(); DownloadRunner runner = new DownloadRunner(uri1).postDownloadAction();
TaskWrapper task = runner.getTask(); TaskWrapper task = runner.getTask();
task.assertDownloading(); task.assertDownloading();
runOnMainThread(() -> downloadManager.stopDownload(task.taskId)); runOnMainThread(() -> downloadManager.setManualStopReason(task.taskId, APP_STOP_REASON));
task.assertStopped(); task.assertStopped();
...@@ -424,7 +427,7 @@ public class DownloadManagerTest { ...@@ -424,7 +427,7 @@ public class DownloadManagerTest {
} }
@Test @Test
public void stoppedSingleDownload_doesNotAffectOthers() throws Throwable { public void manuallyStoppedSingleDownload_doesNotAffectOthers() throws Throwable {
DownloadRunner runner1 = new DownloadRunner(uri1); DownloadRunner runner1 = new DownloadRunner(uri1);
DownloadRunner runner2 = new DownloadRunner(uri2); DownloadRunner runner2 = new DownloadRunner(uri2);
DownloadRunner runner3 = new DownloadRunner(uri3); DownloadRunner runner3 = new DownloadRunner(uri3);
...@@ -432,7 +435,8 @@ public class DownloadManagerTest { ...@@ -432,7 +435,8 @@ public class DownloadManagerTest {
runner1.postDownloadAction().getTask().assertDownloading(); runner1.postDownloadAction().getTask().assertDownloading();
runner2.postDownloadAction().postRemoveAction().getTask().assertRemoving(); runner2.postDownloadAction().postRemoveAction().getTask().assertRemoving();
runOnMainThread(() -> downloadManager.stopDownload(runner1.getTask().taskId)); runOnMainThread(
() -> downloadManager.setManualStopReason(runner1.getTask().taskId, APP_STOP_REASON));
runner1.getTask().assertStopped(); runner1.getTask().assertStopped();
...@@ -460,9 +464,9 @@ public class DownloadManagerTest { ...@@ -460,9 +464,9 @@ public class DownloadManagerTest {
maxActiveDownloadTasks, maxActiveDownloadTasks,
MIN_RETRY_COUNT, MIN_RETRY_COUNT,
new Requirements(0)); new Requirements(0));
downloadManager.startDownloads();
downloadManagerListener = downloadManagerListener =
new TestDownloadManagerListener(downloadManager, dummyMainThread); new TestDownloadManagerListener(downloadManager, dummyMainThread);
downloadManager.startDownloads();
}); });
downloadManagerListener.waitUntilInitialized(); downloadManagerListener.waitUntilInitialized();
} catch (Throwable throwable) { } catch (Throwable throwable) {
......
...@@ -156,7 +156,7 @@ public class DownloadTest { ...@@ -156,7 +156,7 @@ public class DownloadTest {
DownloadBuilder downloadBuilder = DownloadBuilder downloadBuilder =
new DownloadBuilder(downloadAction) new DownloadBuilder(downloadAction)
.setState(Download.STATE_STOPPED) .setState(Download.STATE_STOPPED)
.setManualStopReason(Download.MANUAL_STOP_REASON_UNDEFINED); .setManualStopReason(/* manualStopReason= */ 1);
Download download = downloadBuilder.build(); Download download = downloadBuilder.build();
Download mergedDownload = download.copyWithMergedAction(downloadAction, /* canStart= */ true); Download mergedDownload = download.copyWithMergedAction(downloadAction, /* canStart= */ true);
...@@ -170,7 +170,7 @@ public class DownloadTest { ...@@ -170,7 +170,7 @@ public class DownloadTest {
DownloadBuilder downloadBuilder = DownloadBuilder downloadBuilder =
new DownloadBuilder(downloadAction) new DownloadBuilder(downloadAction)
.setState(Download.STATE_COMPLETED) .setState(Download.STATE_COMPLETED)
.setManualStopReason(Download.MANUAL_STOP_REASON_UNDEFINED); .setManualStopReason(/* manualStopReason= */ 1);
Download download = downloadBuilder.build(); Download download = downloadBuilder.build();
Download mergedDownload = download.copyWithMergedAction(downloadAction, /* canStart= */ true); Download mergedDownload = download.copyWithMergedAction(downloadAction, /* canStart= */ true);
......
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