Commit 925dd5aa by olly Committed by Oliver Woodman

Fix most remaining issues with DownloadManager threading

PiperOrigin-RevId: 242439330
parent c17c7221
...@@ -29,10 +29,10 @@ import static com.google.android.exoplayer2.offline.DownloadState.STATE_RESTARTI ...@@ -29,10 +29,10 @@ import static com.google.android.exoplayer2.offline.DownloadState.STATE_RESTARTI
import static com.google.android.exoplayer2.offline.DownloadState.STATE_STOPPED; import static com.google.android.exoplayer2.offline.DownloadState.STATE_STOPPED;
import android.content.Context; import android.content.Context;
import android.os.ConditionVariable;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -43,12 +43,14 @@ import com.google.android.exoplayer2.scheduler.RequirementsWatcher; ...@@ -43,12 +43,14 @@ import com.google.android.exoplayer2.scheduler.RequirementsWatcher;
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters; import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...@@ -108,6 +110,20 @@ public final class DownloadManager { ...@@ -108,6 +110,20 @@ public final class DownloadManager {
public static final Requirements DEFAULT_REQUIREMENTS = public static final Requirements DEFAULT_REQUIREMENTS =
new Requirements(Requirements.NETWORK_TYPE_ANY, false, false); new Requirements(Requirements.NETWORK_TYPE_ANY, false, false);
// Messages posted to the main handler.
private static final int MSG_INITIALIZED = 0;
private static final int MSG_PROCESSED = 1;
private static final int MSG_DOWNLOAD_STATE_CHANGED = 2;
// Messages posted to the background handler.
private static final int MSG_INITIALIZE = 0;
private static final int MSG_ADD_DOWNLOAD = 1;
private static final int MSG_REMOVE_DOWNLOAD = 2;
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_DOWNLOAD_THREAD_STOPPED = 5;
private static final int MSG_RELEASE = 6;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({ @IntDef({
START_THREAD_SUCCEEDED, START_THREAD_SUCCEEDED,
...@@ -133,6 +149,8 @@ public final class DownloadManager { ...@@ -133,6 +149,8 @@ public final class DownloadManager {
private final Handler mainHandler; private final Handler mainHandler;
private final HandlerThread internalThread; private final HandlerThread internalThread;
private final Handler internalHandler; private final Handler internalHandler;
private final RequirementsWatcher.Listener requirementsListener;
private final Object releaseLock;
// Collections that are accessed on the main thread. // Collections that are accessed on the main thread.
private final CopyOnWriteArraySet<Listener> listeners; private final CopyOnWriteArraySet<Listener> listeners;
...@@ -143,7 +161,8 @@ public final class DownloadManager { ...@@ -143,7 +161,8 @@ public final class DownloadManager {
private final HashMap<Download, DownloadThread> activeDownloads; private final HashMap<Download, DownloadThread> activeDownloads;
// Mutable fields that are accessed on the main thread. // Mutable fields that are accessed on the main thread.
private boolean idle; private int pendingMessages;
private int activeDownloadCount;
private boolean initialized; private boolean initialized;
private boolean released; private boolean released;
private RequirementsWatcher requirementsWatcher; private RequirementsWatcher requirementsWatcher;
...@@ -224,61 +243,52 @@ public final class DownloadManager { ...@@ -224,61 +243,52 @@ public final class DownloadManager {
downloads = new ArrayList<>(); downloads = new ArrayList<>();
downloadStates = new HashMap<>(); downloadStates = new HashMap<>();
activeDownloads = new HashMap<>(); activeDownloads = new HashMap<>();
listeners = new CopyOnWriteArraySet<>();
releaseLock = new Object();
Looper looper = Looper.myLooper(); requirementsListener = this::onRequirementsStateChanged;
if (looper == null) {
looper = Looper.getMainLooper();
}
mainHandler = new Handler(looper);
mainHandler = new Handler(Util.getLooper(), this::handleMainMessage);
internalThread = new HandlerThread("DownloadManager file i/o"); internalThread = new HandlerThread("DownloadManager file i/o");
internalThread.start(); internalThread.start();
internalHandler = new Handler(internalThread.getLooper()); internalHandler = new Handler(internalThread.getLooper(), this::handleInternalMessage);
listeners = new CopyOnWriteArraySet<>(); requirementsWatcher = new RequirementsWatcher(context, requirementsListener, requirements);
int notMetRequirements = requirementsWatcher.start();
int notMetRequirements = watchRequirements(requirements); pendingMessages = 1;
runOnInternalThread( internalHandler
() -> { .obtainMessage(MSG_INITIALIZE, notMetRequirements, /* unused */ 0)
setNotMetRequirements(notMetRequirements); .sendToTarget();
loadDownloads();
});
logd("Created");
} }
/** Returns whether the manager has completed initialization. */ /** Returns whether the manager has completed initialization. */
public boolean isInitialized() { public boolean isInitialized() {
Assertions.checkState(!released);
return initialized; return initialized;
} }
/** Returns whether there are no active downloads. */ /** Returns whether there are no active downloads. */
public boolean isIdle() { public boolean isIdle() {
Assertions.checkState(!released); return activeDownloadCount == 0 && pendingMessages == 0;
return idle;
} }
/** Returns the used {@link DownloadIndex}. */ /** Returns the used {@link DownloadIndex}. */
public DownloadIndex getDownloadIndex() { public DownloadIndex getDownloadIndex() {
Assertions.checkState(!released);
return downloadIndex; return downloadIndex;
} }
/** Returns the number of downloads. */ /** Returns the number of downloads. */
public int getDownloadCount() { public int getDownloadCount() {
Assertions.checkState(!released);
return downloadStates.size(); return downloadStates.size();
} }
/** Returns the states of all current downloads. */ /** Returns the states of all current downloads. */
public DownloadState[] getAllDownloadStates() { public DownloadState[] getAllDownloadStates() {
Assertions.checkState(!released);
return downloadStates.values().toArray(new DownloadState[0]); return downloadStates.values().toArray(new DownloadState[0]);
} }
/** Returns the requirements needed to be met to start downloads. */ /** Returns the requirements needed to be met to start downloads. */
public Requirements getRequirements() { public Requirements getRequirements() {
Assertions.checkState(!released);
return requirementsWatcher.getRequirements(); return requirementsWatcher.getRequirements();
} }
...@@ -288,7 +298,6 @@ public final class DownloadManager { ...@@ -288,7 +298,6 @@ public final class DownloadManager {
* @param listener The listener to be added. * @param listener The listener to be added.
*/ */
public void addListener(Listener listener) { public void addListener(Listener listener) {
Assertions.checkState(!released);
listeners.add(listener); listeners.add(listener);
} }
...@@ -298,7 +307,6 @@ public final class DownloadManager { ...@@ -298,7 +307,6 @@ public final class DownloadManager {
* @param listener The listener to be removed. * @param listener The listener to be removed.
*/ */
public void removeListener(Listener listener) { public void removeListener(Listener listener) {
Assertions.checkState(!released);
listeners.remove(listener); listeners.remove(listener);
} }
...@@ -312,25 +320,25 @@ public final class DownloadManager { ...@@ -312,25 +320,25 @@ public final class DownloadManager {
return; return;
} }
requirementsWatcher.stop(); requirementsWatcher.stop();
int notMetRequirements = watchRequirements(requirements); requirementsWatcher = new RequirementsWatcher(context, requirementsListener, requirements);
onRequirementsStateChanged(notMetRequirements); int notMetRequirements = requirementsWatcher.start();
onRequirementsStateChanged(requirementsWatcher, notMetRequirements);
} }
/** /**
* Clears manual stop reason of all downloads. Downloads are started if the requirements are met. * Clears manual stop reason of all downloads. Downloads are started if the requirements are met.
*/ */
public void startDownloads() { public void startDownloads() {
logd("manual stop is cancelled"); postSetManualStopReason(/* id= */ null, MANUAL_STOP_REASON_NONE);
runOnInternalThread(() -> setManualStopReason(/* id= */ null, MANUAL_STOP_REASON_NONE));
} }
/** Signals all downloads to stop. Call {@link #startDownloads()} to let them to be started. */ /** Signals all downloads to stop. Call {@link #startDownloads()} to let them to be started. */
public void stopDownloads() { public void stopDownloads() {
stopDownloads(/* manualStopReason= */ MANUAL_STOP_REASON_UNDEFINED); stopDownloads(MANUAL_STOP_REASON_UNDEFINED);
} }
/** /**
* Signals all downloads to stop. Call {@link #startDownloads()} to let them to be started. * Sets a manual stop reason for all downloads.
* *
* @param manualStopReason An application defined stop reason. Value {@value * @param manualStopReason An application defined stop reason. Value {@value
* DownloadState#MANUAL_STOP_REASON_NONE} is not allowed and value {@value * DownloadState#MANUAL_STOP_REASON_NONE} is not allowed and value {@value
...@@ -339,8 +347,7 @@ public final class DownloadManager { ...@@ -339,8 +347,7 @@ public final class DownloadManager {
*/ */
public void stopDownloads(int manualStopReason) { public void stopDownloads(int manualStopReason) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE); Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE);
logd("downloads are stopped manually"); postSetManualStopReason(/* id= */ null, manualStopReason);
runOnInternalThread(() -> setManualStopReason(/* id= */ null, manualStopReason));
} }
/** /**
...@@ -350,7 +357,7 @@ public final class DownloadManager { ...@@ -350,7 +357,7 @@ public final class DownloadManager {
* @param id The unique content id of the download to be started. * @param id The unique content id of the download to be started.
*/ */
public void startDownload(String id) { public void startDownload(String id) {
runOnInternalThread(() -> setManualStopReason(id, MANUAL_STOP_REASON_NONE)); postSetManualStopReason(id, MANUAL_STOP_REASON_NONE);
} }
/** /**
...@@ -360,8 +367,7 @@ public final class DownloadManager { ...@@ -360,8 +367,7 @@ public final class DownloadManager {
* @param id The unique content id of the download to be stopped. * @param id The unique content id of the download to be stopped.
*/ */
public void stopDownload(String id) { public void stopDownload(String id) {
runOnInternalThread( stopDownload(id, MANUAL_STOP_REASON_UNDEFINED);
() -> stopDownload(id, /* manualStopReason= */ MANUAL_STOP_REASON_UNDEFINED));
} }
/** /**
...@@ -376,7 +382,7 @@ public final class DownloadManager { ...@@ -376,7 +382,7 @@ public final class DownloadManager {
*/ */
public void stopDownload(String id, int manualStopReason) { public void stopDownload(String id, int manualStopReason) {
Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE); Assertions.checkArgument(manualStopReason != MANUAL_STOP_REASON_NONE);
runOnInternalThread(() -> setManualStopReason(id, manualStopReason)); postSetManualStopReason(id, manualStopReason);
} }
/** /**
...@@ -385,7 +391,8 @@ public final class DownloadManager { ...@@ -385,7 +391,8 @@ public final class DownloadManager {
* @param action The download action. * @param action The download action.
*/ */
public void addDownload(DownloadAction action) { public void addDownload(DownloadAction action) {
runOnInternalThread(() -> addDownloadInternal(action)); pendingMessages++;
internalHandler.obtainMessage(MSG_ADD_DOWNLOAD, action).sendToTarget();
} }
/** /**
...@@ -394,7 +401,8 @@ public final class DownloadManager { ...@@ -394,7 +401,8 @@ public final class DownloadManager {
* @param id The unique content id of the download to be started. * @param id The unique content id of the download to be started.
*/ */
public void removeDownload(String id) { public void removeDownload(String id) {
runOnInternalThread(() -> removeDownloadInternal(id)); pendingMessages++;
internalHandler.obtainMessage(MSG_REMOVE_DOWNLOAD, id).sendToTarget();
} }
/** /**
...@@ -403,74 +411,154 @@ public final class DownloadManager { ...@@ -403,74 +411,154 @@ public final class DownloadManager {
* called. * called.
*/ */
public void release() { public void release() {
synchronized (releaseLock) {
if (released) { if (released) {
return; return;
} }
released = true; internalHandler.sendEmptyMessage(MSG_RELEASE);
if (requirementsWatcher != null) { boolean wasInterrupted = false;
requirementsWatcher.stop(); while (!released) {
try {
releaseLock.wait();
} catch (InterruptedException e) {
wasInterrupted = true;
} }
ConditionVariable fileIOFinishedCondition = new ConditionVariable();
internalHandler.post(
() -> {
releaseInternal();
fileIOFinishedCondition.open();
});
fileIOFinishedCondition.block();
logd("Released");
} }
if (wasInterrupted) {
private void runOnInternalThread(Runnable runnable) { // Restore the interrupted status.
Assertions.checkState(!released); Thread.currentThread().interrupt();
internalHandler.post(runnable);
} }
mainHandler.removeCallbacksAndMessages(/* token= */ null);
private void notifyListenersDownloadStateChange(DownloadState downloadState) { // Reset state.
if (isFinished(downloadState.state)) { pendingMessages = 0;
downloadStates.remove(downloadState.id); activeDownloadCount = 0;
} else { initialized = false;
downloadStates.put(downloadState.id, downloadState); downloadStates.clear();
}
for (Listener listener : listeners) {
listener.onDownloadStateChanged(this, downloadState);
} }
} }
@Requirements.RequirementFlags private void postSetManualStopReason(@Nullable String id, int manualStopReason) {
private int watchRequirements(Requirements requirements) { pendingMessages++;
RequirementsWatcher.Listener listener = internalHandler
(requirementsWatcher, notMetRequirements) -> onRequirementsStateChanged(notMetRequirements); .obtainMessage(MSG_SET_MANUAL_STOP_REASON, manualStopReason, /* unused */ 0, id)
requirementsWatcher = new RequirementsWatcher(context, listener, requirements); .sendToTarget();
return requirementsWatcher.start();
} }
private void onRequirementsStateChanged(@Requirements.RequirementFlags int notMetRequirements) { private void onRequirementsStateChanged(
RequirementsWatcher requirementsWatcher,
@Requirements.RequirementFlags int notMetRequirements) {
Requirements requirements = requirementsWatcher.getRequirements(); Requirements requirements = requirementsWatcher.getRequirements();
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.onRequirementsStateChanged(this, requirements, notMetRequirements); listener.onRequirementsStateChanged(this, requirements, notMetRequirements);
} }
internalHandler.post(() -> setNotMetRequirements(notMetRequirements)); pendingMessages++;
internalHandler
.obtainMessage(MSG_SET_NOT_MET_REQUIREMENTS, notMetRequirements, /* unused */ 0)
.sendToTarget();
}
// Main thread message handling.
@SuppressWarnings("unchecked")
private boolean handleMainMessage(Message message) {
switch (message.what) {
case MSG_INITIALIZED:
List<DownloadState> downloadStates = (List<DownloadState>) message.obj;
onInitialized(downloadStates);
break;
case MSG_DOWNLOAD_STATE_CHANGED:
DownloadState state = (DownloadState) message.obj;
onDownloadStateChanged(state);
break;
case MSG_PROCESSED:
int processedMessageCount = message.arg1;
int activeDownloadCount = message.arg2;
onMessageProcessed(processedMessageCount, activeDownloadCount);
break;
default:
throw new IllegalStateException();
}
return true;
} }
private void onInitialized() { // TODO: Merge these three events into a single MSG_STATE_CHANGE that can carry all updates. This
// allows updating idle at the same point as the downloads that can be queried changes.
private void onInitialized(List<DownloadState> downloadStates) {
initialized = true; initialized = true;
for (int i = 0; i < downloadStates.size(); i++) {
DownloadState downloadState = downloadStates.get(i);
this.downloadStates.put(downloadState.id, downloadState);
}
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.onInitialized(DownloadManager.this); listener.onInitialized(DownloadManager.this);
} }
} }
private void onIdleStateChange(boolean idle) { private void onDownloadStateChanged(DownloadState downloadState) {
if (!this.idle && idle) { if (isFinished(downloadState.state)) {
downloadStates.remove(downloadState.id);
} else {
downloadStates.put(downloadState.id, downloadState);
}
for (Listener listener : listeners) {
listener.onDownloadStateChanged(this, downloadState);
}
}
private void onMessageProcessed(int processedMessageCount, int activeDownloadCount) {
this.pendingMessages -= processedMessageCount;
this.activeDownloadCount = activeDownloadCount;
if (isIdle()) {
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.onIdle(this); listener.onIdle(this);
} }
} }
this.idle = idle;
} }
// Methods that run on internal thread. // Internal thread message handling.
private void setManualStopReason(@Nullable String id, int manualStopReason) { private boolean handleInternalMessage(Message message) {
boolean processedExternalMessage = true;
switch (message.what) {
case MSG_INITIALIZE:
int notMetRequirements = message.arg1;
initializeInternal(notMetRequirements);
break;
case MSG_ADD_DOWNLOAD:
DownloadAction action = (DownloadAction) message.obj;
addDownloadInternal(action);
break;
case MSG_REMOVE_DOWNLOAD:
String id = (String) message.obj;
removeDownloadInternal(id);
break;
case MSG_SET_MANUAL_STOP_REASON:
id = (String) message.obj;
int manualStopReason = message.arg1;
setManualStopReasonInternal(id, manualStopReason);
break;
case MSG_SET_NOT_MET_REQUIREMENTS:
notMetRequirements = message.arg1;
setNotMetRequirementsInternal(notMetRequirements);
break;
case MSG_DOWNLOAD_THREAD_STOPPED:
DownloadThread downloadThread = (DownloadThread) message.obj;
onDownloadThreadStoppedInternal(downloadThread);
processedExternalMessage = false;
break;
case MSG_RELEASE:
releaseInternal();
return true; // Don't post back to mainHandler on release.
default:
throw new IllegalStateException();
}
mainHandler
.obtainMessage(MSG_PROCESSED, processedExternalMessage ? 1 : 0, activeDownloads.size())
.sendToTarget();
return true;
}
private void setManualStopReasonInternal(@Nullable String id, int manualStopReason) {
if (id != null) { if (id != null) {
Download download = getDownload(id); Download download = getDownload(id);
if (download != null) { if (download != null) {
...@@ -530,14 +618,17 @@ public final class DownloadManager { ...@@ -530,14 +618,17 @@ public final class DownloadManager {
private void onDownloadStateChange(Download download, DownloadState downloadState) { private void onDownloadStateChange(Download download, DownloadState downloadState) {
logd("Download state is changed", download); logd("Download state is changed", download);
updateDownloadIndex(downloadState); updateDownloadIndex(downloadState);
mainHandler.post(() -> notifyListenersDownloadStateChange(downloadState));
int index = downloads.indexOf(download);
if (isFinished(download.state)) { if (isFinished(download.state)) {
downloads.remove(index); downloads.remove(download);
} }
mainHandler.obtainMessage(MSG_DOWNLOAD_STATE_CHANGED, downloadState).sendToTarget();
} }
private void setNotMetRequirements(@Requirements.RequirementFlags int notMetRequirements) { private void setNotMetRequirementsInternal(
@Requirements.RequirementFlags int notMetRequirements) {
if (this.notMetRequirements == notMetRequirements) {
return;
}
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 < downloads.size(); i++) { for (int i = 0; i < downloads.size(); i++) {
...@@ -565,35 +656,28 @@ public final class DownloadManager { ...@@ -565,35 +656,28 @@ public final class DownloadManager {
return null; return null;
} }
private void loadDownloads() { private void initializeInternal(int notMetRequirements) {
DownloadState[] loadedStates; this.notMetRequirements = notMetRequirements;
ArrayList<DownloadState> loadedStates = new ArrayList<>();
try (DownloadStateCursor cursor = try (DownloadStateCursor cursor =
downloadIndex.getDownloadStates( downloadIndex.getDownloadStates(
STATE_QUEUED, STATE_STOPPED, STATE_DOWNLOADING, STATE_REMOVING, STATE_RESTARTING)) { STATE_QUEUED, STATE_STOPPED, STATE_DOWNLOADING, STATE_REMOVING, STATE_RESTARTING)) {
loadedStates = new DownloadState[cursor.getCount()]; while (cursor.moveToNext()) {
for (int i = 0, length = loadedStates.length; i < length; i++) { loadedStates.add(cursor.getDownloadState());
cursor.moveToNext();
loadedStates[i] = cursor.getDownloadState();
} }
logd("Download states are loaded."); logd("Download states are loaded.");
} catch (Throwable e) { } catch (Throwable e) {
Log.e(TAG, "Download state loading failed.", e); Log.e(TAG, "Download state loading failed.", e);
loadedStates = new DownloadState[0]; loadedStates.clear();
} }
for (DownloadState downloadState : loadedStates) { for (DownloadState downloadState : loadedStates) {
addDownloadForState(downloadState); addDownloadForState(downloadState);
} }
logd("Downloads are created."); logd("Downloads are created.");
mainHandler.post(this::onInitialized); mainHandler.obtainMessage(MSG_INITIALIZED, loadedStates).sendToTarget();
for (int i = 0; i < downloads.size(); i++) { for (int i = 0; i < downloads.size(); i++) {
downloads.get(i).start(); downloads.get(i).start();
} }
checkIfIdle();
}
private void checkIfIdle() {
boolean idle = activeDownloads.isEmpty();
mainHandler.post(() -> onIdleStateChange(idle));
} }
private void addDownloadForState(DownloadState downloadState) { private void addDownloadForState(DownloadState downloadState) {
...@@ -650,7 +734,6 @@ public final class DownloadManager { ...@@ -650,7 +734,6 @@ public final class DownloadManager {
DownloadThread downloadThread = new DownloadThread(download); DownloadThread downloadThread = new DownloadThread(download);
activeDownloads.put(download, downloadThread); activeDownloads.put(download, downloadThread);
download.setCounters(downloadThread.downloader.getCounters()); download.setCounters(downloadThread.downloader.getCounters());
checkIfIdle();
logd("Download is started", download); logd("Download is started", download);
return START_THREAD_SUCCEEDED; return START_THREAD_SUCCEEDED;
} }
...@@ -670,20 +753,23 @@ public final class DownloadManager { ...@@ -670,20 +753,23 @@ public final class DownloadManager {
stopDownloadThread(download); stopDownloadThread(download);
} }
internalThread.quit(); internalThread.quit();
synchronized (releaseLock) {
released = true;
releaseLock.notifyAll();
}
} }
private void onDownloadThreadStopped(DownloadThread downloadThread, Throwable finalError) { private void onDownloadThreadStoppedInternal(DownloadThread downloadThread) {
Download download = downloadThread.download; Download download = downloadThread.download;
logd("Download is stopped", download); logd("Download is stopped", download);
activeDownloads.remove(download); activeDownloads.remove(download);
checkIfIdle();
boolean tryToStartDownloads = false; boolean tryToStartDownloads = false;
if (!downloadThread.isRemoveThread) { if (!downloadThread.isRemoveThread) {
// If maxSimultaneousDownloads was hit, there might be a download waiting for a slot. // If maxSimultaneousDownloads was hit, there might be a download waiting for a slot.
tryToStartDownloads = simultaneousDownloads == maxSimultaneousDownloads; tryToStartDownloads = simultaneousDownloads == maxSimultaneousDownloads;
simultaneousDownloads--; simultaneousDownloads--;
} }
download.onDownloadThreadStopped(downloadThread.isCanceled, finalError); download.onDownloadThreadStopped(downloadThread.isCanceled, downloadThread.finalError);
if (tryToStartDownloads) { if (tryToStartDownloads) {
for (int i = 0; for (int i = 0;
simultaneousDownloads < maxSimultaneousDownloads && i < downloads.size(); simultaneousDownloads < maxSimultaneousDownloads && i < downloads.size();
...@@ -726,11 +812,6 @@ public final class DownloadManager { ...@@ -726,11 +812,6 @@ public final class DownloadManager {
} }
public void addAction(DownloadAction newAction) { 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));
}
downloadState = downloadState.mergeAction(newAction); downloadState = downloadState.mergeAction(newAction);
initialize(); initialize();
} }
...@@ -885,7 +966,9 @@ public final class DownloadManager { ...@@ -885,7 +966,9 @@ public final class DownloadManager {
private final Download download; private final Download download;
private final Downloader downloader; private final Downloader downloader;
private final boolean isRemoveThread; private final boolean isRemoveThread;
private volatile boolean isCanceled; private volatile boolean isCanceled;
private Throwable finalError;
private DownloadThread(Download download) { private DownloadThread(Download download) {
this.download = download; this.download = download;
...@@ -905,7 +988,6 @@ public final class DownloadManager { ...@@ -905,7 +988,6 @@ public final class DownloadManager {
@Override @Override
public void run() { public void run() {
logd("Download started", download); logd("Download started", download);
Throwable error = null;
try { try {
if (isRemoveThread) { if (isRemoveThread) {
downloader.remove(); downloader.remove();
...@@ -934,13 +1016,9 @@ public final class DownloadManager { ...@@ -934,13 +1016,9 @@ public final class DownloadManager {
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
error = e; finalError = e;
} }
final Throwable finalError = error; internalHandler.obtainMessage(MSG_DOWNLOAD_THREAD_STOPPED, this).sendToTarget();
internalHandler.post(
() -> {
onDownloadThreadStopped(this, finalError);
});
} }
private int getRetryDelayMillis(int errorCount) { private int getRetryDelayMillis(int errorCount) {
......
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