mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Fix most remaining issues with DownloadManager threading
PiperOrigin-RevId: 242439330
This commit is contained in:
parent
c17c722158
commit
925dd5aa12
1 changed files with 197 additions and 119 deletions
|
|
@ -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;
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
* @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 {
|
||||||
* @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 {
|
||||||
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 {
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
* @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 {
|
||||||
* @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 {
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
* @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 {
|
||||||
* @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,29 +411,90 @@ public final class DownloadManager {
|
||||||
* called.
|
* called.
|
||||||
*/
|
*/
|
||||||
public void release() {
|
public void release() {
|
||||||
if (released) {
|
synchronized (releaseLock) {
|
||||||
return;
|
if (released) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internalHandler.sendEmptyMessage(MSG_RELEASE);
|
||||||
|
boolean wasInterrupted = false;
|
||||||
|
while (!released) {
|
||||||
|
try {
|
||||||
|
releaseLock.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
wasInterrupted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wasInterrupted) {
|
||||||
|
// Restore the interrupted status.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
mainHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||||
|
// Reset state.
|
||||||
|
pendingMessages = 0;
|
||||||
|
activeDownloadCount = 0;
|
||||||
|
initialized = false;
|
||||||
|
downloadStates.clear();
|
||||||
}
|
}
|
||||||
released = true;
|
|
||||||
if (requirementsWatcher != null) {
|
|
||||||
requirementsWatcher.stop();
|
|
||||||
}
|
|
||||||
ConditionVariable fileIOFinishedCondition = new ConditionVariable();
|
|
||||||
internalHandler.post(
|
|
||||||
() -> {
|
|
||||||
releaseInternal();
|
|
||||||
fileIOFinishedCondition.open();
|
|
||||||
});
|
|
||||||
fileIOFinishedCondition.block();
|
|
||||||
logd("Released");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runOnInternalThread(Runnable runnable) {
|
private void postSetManualStopReason(@Nullable String id, int manualStopReason) {
|
||||||
Assertions.checkState(!released);
|
pendingMessages++;
|
||||||
internalHandler.post(runnable);
|
internalHandler
|
||||||
|
.obtainMessage(MSG_SET_MANUAL_STOP_REASON, manualStopReason, /* unused */ 0, id)
|
||||||
|
.sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyListenersDownloadStateChange(DownloadState downloadState) {
|
private void onRequirementsStateChanged(
|
||||||
|
RequirementsWatcher requirementsWatcher,
|
||||||
|
@Requirements.RequirementFlags int notMetRequirements) {
|
||||||
|
Requirements requirements = requirementsWatcher.getRequirements();
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.onRequirementsStateChanged(this, requirements, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
for (int i = 0; i < downloadStates.size(); i++) {
|
||||||
|
DownloadState downloadState = downloadStates.get(i);
|
||||||
|
this.downloadStates.put(downloadState.id, downloadState);
|
||||||
|
}
|
||||||
|
for (Listener listener : listeners) {
|
||||||
|
listener.onInitialized(DownloadManager.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadStateChanged(DownloadState downloadState) {
|
||||||
if (isFinished(downloadState.state)) {
|
if (isFinished(downloadState.state)) {
|
||||||
downloadStates.remove(downloadState.id);
|
downloadStates.remove(downloadState.id);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -436,41 +505,60 @@ public final class DownloadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Requirements.RequirementFlags
|
private void onMessageProcessed(int processedMessageCount, int activeDownloadCount) {
|
||||||
private int watchRequirements(Requirements requirements) {
|
this.pendingMessages -= processedMessageCount;
|
||||||
RequirementsWatcher.Listener listener =
|
this.activeDownloadCount = activeDownloadCount;
|
||||||
(requirementsWatcher, notMetRequirements) -> onRequirementsStateChanged(notMetRequirements);
|
if (isIdle()) {
|
||||||
requirementsWatcher = new RequirementsWatcher(context, listener, requirements);
|
|
||||||
return requirementsWatcher.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRequirementsStateChanged(@Requirements.RequirementFlags int notMetRequirements) {
|
|
||||||
Requirements requirements = requirementsWatcher.getRequirements();
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onRequirementsStateChanged(this, requirements, notMetRequirements);
|
|
||||||
}
|
|
||||||
internalHandler.post(() -> setNotMetRequirements(notMetRequirements));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onInitialized() {
|
|
||||||
initialized = true;
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onInitialized(DownloadManager.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onIdleStateChange(boolean idle) {
|
|
||||||
if (!this.idle && idle) {
|
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue