Commit 56520b7c by olly Committed by Oliver Woodman

Move DownloadManager internal logic into isolated inner class

There are no logic changes here. It's just moving code around and removing
the "internal" part of names where no longer required.

PiperOrigin-RevId: 245407238
parent d187d9ec
...@@ -160,38 +160,21 @@ public final class DownloadManager { ...@@ -160,38 +160,21 @@ public final class DownloadManager {
private final Context context; private final Context context;
private final WritableDownloadIndex downloadIndex; private final WritableDownloadIndex downloadIndex;
private final DownloaderFactory downloaderFactory;
private final Handler mainHandler; private final Handler mainHandler;
private final HandlerThread internalThread; private final InternalHandler internalHandler;
private final Handler internalHandler;
private final RequirementsWatcher.Listener requirementsListener; private final RequirementsWatcher.Listener requirementsListener;
private final Object releaseLock;
// Collections that are accessed on the main thread.
private final CopyOnWriteArraySet<Listener> listeners; private final CopyOnWriteArraySet<Listener> listeners;
private final ArrayList<Download> downloads; private final ArrayList<Download> downloads;
// Collections that are accessed on the internal thread.
private final ArrayList<DownloadInternal> downloadInternals;
private final HashMap<String, DownloadThread> downloadThreads;
// Mutable fields that are accessed on the main thread.
private int pendingMessages; private int pendingMessages;
private int activeDownloadCount; private int activeDownloadCount;
private boolean initialized; private boolean initialized;
private boolean released;
private boolean downloadsPaused; private boolean downloadsPaused;
private int maxParallelDownloads; private int maxParallelDownloads;
private int minRetryCount; private int minRetryCount;
private RequirementsWatcher requirementsWatcher; private RequirementsWatcher requirementsWatcher;
// Mutable fields that are accessed on the internal thread.
@Requirements.RequirementFlags private int notMetRequirements;
private boolean downloadsPausedInternal;
private int maxParallelDownloadsInternal;
private int minRetryCountInternal;
private int parallelDownloads;
/** /**
* Constructs a {@link DownloadManager}. * Constructs a {@link DownloadManager}.
* *
...@@ -221,31 +204,29 @@ public final class DownloadManager { ...@@ -221,31 +204,29 @@ public final class DownloadManager {
Context context, WritableDownloadIndex downloadIndex, DownloaderFactory downloaderFactory) { Context context, WritableDownloadIndex downloadIndex, DownloaderFactory downloaderFactory) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.downloadIndex = downloadIndex; this.downloadIndex = downloadIndex;
this.downloaderFactory = downloaderFactory;
maxParallelDownloads = DEFAULT_MAX_PARALLEL_DOWNLOADS; maxParallelDownloads = DEFAULT_MAX_PARALLEL_DOWNLOADS;
maxParallelDownloadsInternal = DEFAULT_MAX_PARALLEL_DOWNLOADS;
minRetryCount = DEFAULT_MIN_RETRY_COUNT; minRetryCount = DEFAULT_MIN_RETRY_COUNT;
minRetryCountInternal = DEFAULT_MIN_RETRY_COUNT;
downloadsPaused = true; downloadsPaused = true;
downloadsPausedInternal = true;
downloadInternals = new ArrayList<>();
downloads = new ArrayList<>(); downloads = new ArrayList<>();
downloadThreads = new HashMap<>();
listeners = new CopyOnWriteArraySet<>(); listeners = new CopyOnWriteArraySet<>();
releaseLock = new Object();
requirementsListener = this::onRequirementsStateChanged; requirementsListener = this::onRequirementsStateChanged;
mainHandler = new Handler(Util.getLooper(), this::handleMainMessage);
internalThread = new HandlerThread("DownloadManager file i/o");
internalThread.start();
internalHandler = new Handler(internalThread.getLooper(), this::handleInternalMessage);
requirementsWatcher = requirementsWatcher =
new RequirementsWatcher(context, requirementsListener, DEFAULT_REQUIREMENTS); new RequirementsWatcher(context, requirementsListener, DEFAULT_REQUIREMENTS);
int notMetRequirements = requirementsWatcher.start(); int notMetRequirements = requirementsWatcher.start();
mainHandler = new Handler(Util.getLooper(), this::handleMainMessage);
HandlerThread internalThread = new HandlerThread("DownloadManager file i/o");
internalThread.start();
internalHandler =
new InternalHandler(
internalThread,
downloadIndex,
downloaderFactory,
mainHandler,
maxParallelDownloads,
minRetryCount,
downloadsPaused);
pendingMessages = 1; pendingMessages = 1;
internalHandler internalHandler
.obtainMessage(MSG_INITIALIZE, notMetRequirements, /* unused */ 0) .obtainMessage(MSG_INITIALIZE, notMetRequirements, /* unused */ 0)
...@@ -464,15 +445,15 @@ public final class DownloadManager { ...@@ -464,15 +445,15 @@ public final class DownloadManager {
* download index. The manager must not be accessed after this method has been called. * download index. The manager must not be accessed after this method has been called.
*/ */
public void release() { public void release() {
synchronized (releaseLock) { synchronized (internalHandler) {
if (released) { if (internalHandler.released) {
return; return;
} }
internalHandler.sendEmptyMessage(MSG_RELEASE); internalHandler.sendEmptyMessage(MSG_RELEASE);
boolean wasInterrupted = false; boolean wasInterrupted = false;
while (!released) { while (!internalHandler.released) {
try { try {
releaseLock.wait(); internalHandler.wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
wasInterrupted = true; wasInterrupted = true;
} }
...@@ -581,383 +562,411 @@ public final class DownloadManager { ...@@ -581,383 +562,411 @@ public final class DownloadManager {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
// Internal thread message handling. /* package */ static Download mergeRequest(
Download download, DownloadRequest request, int stopReason) {
private boolean handleInternalMessage(Message message) { @Download.State int state = download.state;
boolean processedExternalMessage = true; if (state == STATE_REMOVING || state == STATE_RESTARTING) {
switch (message.what) { state = STATE_RESTARTING;
case MSG_INITIALIZE: } else if (stopReason != STOP_REASON_NONE) {
int notMetRequirements = message.arg1; state = STATE_STOPPED;
initializeInternal(notMetRequirements); } else {
break; state = STATE_QUEUED;
case MSG_SET_DOWNLOADS_PAUSED:
boolean downloadsPaused = message.arg1 != 0;
setDownloadsPausedInternal(downloadsPaused);
break;
case MSG_SET_NOT_MET_REQUIREMENTS:
notMetRequirements = message.arg1;
setNotMetRequirementsInternal(notMetRequirements);
break;
case MSG_SET_STOP_REASON:
String id = (String) message.obj;
int stopReason = message.arg1;
setStopReasonInternal(id, stopReason);
break;
case MSG_SET_MAX_PARALLEL_DOWNLOADS:
int maxParallelDownloads = message.arg1;
setMaxParallelDownloadsInternal(maxParallelDownloads);
break;
case MSG_SET_MIN_RETRY_COUNT:
int minRetryCount = message.arg1;
setMinRetryCountInternal(minRetryCount);
break;
case MSG_ADD_DOWNLOAD:
DownloadRequest request = (DownloadRequest) message.obj;
stopReason = message.arg1;
addDownloadInternal(request, stopReason);
break;
case MSG_REMOVE_DOWNLOAD:
id = (String) message.obj;
removeDownloadInternal(id);
break;
case MSG_DOWNLOAD_THREAD_STOPPED:
DownloadThread downloadThread = (DownloadThread) message.obj;
onDownloadThreadStoppedInternal(downloadThread);
processedExternalMessage = false; // This message is posted internally.
break;
case MSG_CONTENT_LENGTH_CHANGED:
downloadThread = (DownloadThread) message.obj;
onDownloadThreadContentLengthChangedInternal(downloadThread);
processedExternalMessage = false; // This message is posted internally.
break;
case MSG_RELEASE:
releaseInternal();
return true; // Don't post back to mainHandler on release.
default:
throw new IllegalStateException();
} }
mainHandler long nowMs = System.currentTimeMillis();
.obtainMessage(MSG_PROCESSED, processedExternalMessage ? 1 : 0, downloadThreads.size()) long startTimeMs = download.isTerminalState() ? nowMs : download.startTimeMs;
.sendToTarget(); return new Download(
return true; download.request.copyWithMergedRequest(request),
state,
startTimeMs,
/* updateTimeMs= */ nowMs,
/* contentLength= */ C.LENGTH_UNSET,
stopReason,
FAILURE_REASON_NONE);
} }
private void initializeInternal(int notMetRequirements) { private static Download copyWithState(Download download, @Download.State int state) {
this.notMetRequirements = notMetRequirements; return new Download(
ArrayList<Download> loadedStates = new ArrayList<>(); download.request,
try (DownloadCursor cursor = state,
downloadIndex.getDownloads( download.startTimeMs,
STATE_QUEUED, STATE_STOPPED, STATE_DOWNLOADING, STATE_REMOVING, STATE_RESTARTING)) { /* updateTimeMs= */ System.currentTimeMillis(),
while (cursor.moveToNext()) { download.contentLength,
loadedStates.add(cursor.getDownload()); download.stopReason,
} FAILURE_REASON_NONE,
logd("Downloads are loaded."); download.progress);
} catch (Throwable e) { }
Log.e(TAG, "Download state loading failed.", e);
loadedStates.clear(); private static void logd(String message) {
} if (DEBUG) {
for (Download download : loadedStates) { Log.d(TAG, message);
addDownloadForState(download);
}
logd("Downloads are created.");
mainHandler.obtainMessage(MSG_INITIALIZED, loadedStates).sendToTarget();
for (int i = 0; i < downloadInternals.size(); i++) {
downloadInternals.get(i).start();
} }
} }
private void setDownloadsPausedInternal(boolean downloadsPaused) { private static void logd(String message, DownloadInternal downloadInternal) {
if (this.downloadsPausedInternal == downloadsPaused) { logd(message, downloadInternal.download.request);
return; }
private static void logd(String message, DownloadRequest request) {
if (DEBUG) {
logd(message + ": " + request);
} }
this.downloadsPausedInternal = downloadsPaused; }
for (int i = 0; i < downloadInternals.size(); i++) {
downloadInternals.get(i).updateStopState(); private static void logdFlags(String message, int flags) {
if (DEBUG) {
logd(message + ": " + Integer.toBinaryString(flags));
} }
} }
private void setNotMetRequirementsInternal( private static final class InternalHandler extends Handler {
@Requirements.RequirementFlags int notMetRequirements) {
if (this.notMetRequirements == notMetRequirements) { public boolean released;
return;
private final HandlerThread thread;
private final WritableDownloadIndex downloadIndex;
private final DownloaderFactory downloaderFactory;
private final Handler mainHandler;
private final ArrayList<DownloadInternal> downloadInternals;
private final HashMap<String, DownloadThread> downloadThreads;
// Mutable fields that are accessed on the internal thread.
@Requirements.RequirementFlags private int notMetRequirements;
private boolean downloadsPaused;
private int maxParallelDownloads;
private int minRetryCount;
private int parallelDownloads;
public InternalHandler(
HandlerThread thread,
WritableDownloadIndex downloadIndex,
DownloaderFactory downloaderFactory,
Handler mainHandler,
int maxParallelDownloads,
int minRetryCount,
boolean downloadsPaused) {
super(thread.getLooper());
this.thread = thread;
this.downloadIndex = downloadIndex;
this.downloaderFactory = downloaderFactory;
this.mainHandler = mainHandler;
this.maxParallelDownloads = maxParallelDownloads;
this.minRetryCount = minRetryCount;
this.downloadsPaused = downloadsPaused;
downloadInternals = new ArrayList<>();
downloadThreads = new HashMap<>();
} }
this.notMetRequirements = notMetRequirements;
logdFlags("Not met requirements are changed", notMetRequirements); @Override
for (int i = 0; i < downloadInternals.size(); i++) { public void handleMessage(Message message) {
downloadInternals.get(i).updateStopState(); boolean processedExternalMessage = true;
switch (message.what) {
case MSG_INITIALIZE:
int notMetRequirements = message.arg1;
initialize(notMetRequirements);
break;
case MSG_SET_DOWNLOADS_PAUSED:
boolean downloadsPaused = message.arg1 != 0;
setDownloadsPaused(downloadsPaused);
break;
case MSG_SET_NOT_MET_REQUIREMENTS:
notMetRequirements = message.arg1;
setNotMetRequirements(notMetRequirements);
break;
case MSG_SET_STOP_REASON:
String id = (String) message.obj;
int stopReason = message.arg1;
setStopReason(id, stopReason);
break;
case MSG_SET_MAX_PARALLEL_DOWNLOADS:
int maxParallelDownloads = message.arg1;
setMaxParallelDownloads(maxParallelDownloads);
break;
case MSG_SET_MIN_RETRY_COUNT:
int minRetryCount = message.arg1;
setMinRetryCount(minRetryCount);
break;
case MSG_ADD_DOWNLOAD:
DownloadRequest request = (DownloadRequest) message.obj;
stopReason = message.arg1;
addDownload(request, stopReason);
break;
case MSG_REMOVE_DOWNLOAD:
id = (String) message.obj;
removeDownload(id);
break;
case MSG_DOWNLOAD_THREAD_STOPPED:
DownloadThread downloadThread = (DownloadThread) message.obj;
onDownloadThreadStopped(downloadThread);
processedExternalMessage = false; // This message is posted internally.
break;
case MSG_CONTENT_LENGTH_CHANGED:
downloadThread = (DownloadThread) message.obj;
onDownloadThreadContentLengthChanged(downloadThread);
processedExternalMessage = false; // This message is posted internally.
break;
case MSG_RELEASE:
release();
return; // Don't post back to mainHandler on release.
default:
throw new IllegalStateException();
}
mainHandler
.obtainMessage(MSG_PROCESSED, processedExternalMessage ? 1 : 0, downloadThreads.size())
.sendToTarget();
}
private void initialize(int notMetRequirements) {
this.notMetRequirements = notMetRequirements;
ArrayList<Download> loadedStates = new ArrayList<>();
try (DownloadCursor cursor =
downloadIndex.getDownloads(
STATE_QUEUED, STATE_STOPPED, STATE_DOWNLOADING, STATE_REMOVING, STATE_RESTARTING)) {
while (cursor.moveToNext()) {
loadedStates.add(cursor.getDownload());
}
logd("Downloads are loaded.");
} catch (Throwable e) {
Log.e(TAG, "Download state loading failed.", e);
loadedStates.clear();
}
for (Download download : loadedStates) {
addDownloadForState(download);
}
logd("Downloads are created.");
mainHandler.obtainMessage(MSG_INITIALIZED, loadedStates).sendToTarget();
for (int i = 0; i < downloadInternals.size(); i++) {
downloadInternals.get(i).start();
}
} }
}
private void setStopReasonInternal(@Nullable String id, int stopReason) { private void setDownloadsPaused(boolean downloadsPaused) {
if (id != null) { this.downloadsPaused = downloadsPaused;
DownloadInternal downloadInternal = getDownload(id); for (int i = 0; i < downloadInternals.size(); i++) {
if (downloadInternal != null) { downloadInternals.get(i).updateStopState();
logd("download stop reason is set to : " + stopReason, downloadInternal); }
downloadInternal.setStopReason(stopReason); }
private void setNotMetRequirements(@Requirements.RequirementFlags int notMetRequirements) {
// TODO: Move this deduplication check to the main thread.
if (this.notMetRequirements == notMetRequirements) {
return; return;
} }
} else { this.notMetRequirements = 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).setStopReason(stopReason); downloadInternals.get(i).updateStopState();
} }
} }
try {
private void setStopReason(@Nullable String id, int stopReason) {
if (id != null) { if (id != null) {
downloadIndex.setStopReason(id, stopReason); DownloadInternal downloadInternal = getDownload(id);
if (downloadInternal != null) {
logd("download stop reason is set to : " + stopReason, downloadInternal);
downloadInternal.setStopReason(stopReason);
return;
}
} else { } else {
downloadIndex.setStopReason(stopReason); for (int i = 0; i < downloadInternals.size(); i++) {
downloadInternals.get(i).setStopReason(stopReason);
}
}
try {
if (id != null) {
downloadIndex.setStopReason(id, stopReason);
} else {
downloadIndex.setStopReason(stopReason);
}
} catch (IOException e) {
Log.e(TAG, "setStopReason failed", e);
} }
} catch (IOException e) {
Log.e(TAG, "setStopReason failed", e);
} }
}
private void setMaxParallelDownloadsInternal(int maxParallelDownloads) { private void setMaxParallelDownloads(int maxParallelDownloads) {
maxParallelDownloadsInternal = maxParallelDownloads; this.maxParallelDownloads = maxParallelDownloads;
// TODO: Start or stop downloads if necessary. // TODO: Start or stop downloads if necessary.
} }
private void setMinRetryCountInternal(int minRetryCount) { private void setMinRetryCount(int minRetryCount) {
minRetryCountInternal = minRetryCount; this.minRetryCount = minRetryCount;
} }
private void addDownloadInternal(DownloadRequest request, int stopReason) { private void addDownload(DownloadRequest request, int stopReason) {
DownloadInternal downloadInternal = getDownload(request.id); DownloadInternal downloadInternal = getDownload(request.id);
if (downloadInternal != null) { if (downloadInternal != null) {
downloadInternal.addRequest(request, stopReason); downloadInternal.addRequest(request, stopReason);
logd("Request is added to existing download", downloadInternal); logd("Request is added to existing download", downloadInternal);
} else {
Download download = loadDownload(request.id);
if (download == null) {
long nowMs = System.currentTimeMillis();
download =
new Download(
request,
stopReason != Download.STOP_REASON_NONE ? STATE_STOPPED : STATE_QUEUED,
/* startTimeMs= */ nowMs,
/* updateTimeMs= */ nowMs,
/* contentLength= */ C.LENGTH_UNSET,
stopReason,
Download.FAILURE_REASON_NONE);
logd("Download state is created for " + request.id);
} else { } else {
download = mergeRequest(download, request, stopReason); Download download = loadDownload(request.id);
logd("Download state is loaded for " + request.id); if (download == null) {
long nowMs = System.currentTimeMillis();
download =
new Download(
request,
stopReason != Download.STOP_REASON_NONE ? STATE_STOPPED : STATE_QUEUED,
/* startTimeMs= */ nowMs,
/* updateTimeMs= */ nowMs,
/* contentLength= */ C.LENGTH_UNSET,
stopReason,
Download.FAILURE_REASON_NONE);
logd("Download state is created for " + request.id);
} else {
download = mergeRequest(download, request, stopReason);
logd("Download state is loaded for " + request.id);
}
addDownloadForState(download);
} }
addDownloadForState(download);
} }
}
private void removeDownloadInternal(String id) { private void removeDownload(String id) {
DownloadInternal downloadInternal = getDownload(id); DownloadInternal downloadInternal = getDownload(id);
if (downloadInternal != null) { if (downloadInternal != null) {
downloadInternal.remove(); downloadInternal.remove();
} else {
Download download = loadDownload(id);
if (download != null) {
addDownloadForState(copyWithState(download, STATE_REMOVING));
} else { } else {
logd("Can't remove download. No download with id: " + id); Download download = loadDownload(id);
if (download != null) {
addDownloadForState(copyWithState(download, STATE_REMOVING));
} else {
logd("Can't remove download. No download with id: " + id);
}
} }
} }
}
private void onDownloadThreadStoppedInternal(DownloadThread downloadThread) { private void onDownloadThreadStopped(DownloadThread downloadThread) {
logd("Download is stopped", downloadThread.request); logd("Download is stopped", downloadThread.request);
String downloadId = downloadThread.request.id; String downloadId = downloadThread.request.id;
downloadThreads.remove(downloadId); downloadThreads.remove(downloadId);
boolean tryToStartDownloads = false; boolean tryToStartDownloads = false;
if (!downloadThread.isRemove) { if (!downloadThread.isRemove) {
// If maxParallelDownloads was hit, there might be a download waiting for a slot. // If maxParallelDownloads was hit, there might be a download waiting for a slot.
tryToStartDownloads = parallelDownloads == maxParallelDownloadsInternal; tryToStartDownloads = parallelDownloads == maxParallelDownloads;
parallelDownloads--; parallelDownloads--;
} }
getDownload(downloadId) getDownload(downloadId)
.onDownloadThreadStopped(downloadThread.isCanceled, downloadThread.finalError); .onDownloadThreadStopped(downloadThread.isCanceled, downloadThread.finalError);
if (tryToStartDownloads) { if (tryToStartDownloads) {
for (int i = 0; for (int i = 0;
parallelDownloads < maxParallelDownloadsInternal && i < downloadInternals.size(); parallelDownloads < maxParallelDownloads && i < downloadInternals.size();
i++) { i++) {
downloadInternals.get(i).start(); downloadInternals.get(i).start();
}
} }
} }
}
private void onDownloadThreadContentLengthChangedInternal(DownloadThread downloadThread) {
String downloadId = downloadThread.request.id;
getDownload(downloadId).setContentLength(downloadThread.contentLength);
}
private void releaseInternal() {
for (DownloadThread downloadThread : downloadThreads.values()) {
downloadThread.cancel(/* released= */ true);
}
downloadThreads.clear();
downloadInternals.clear();
internalThread.quit();
synchronized (releaseLock) {
released = true;
releaseLock.notifyAll();
}
}
private void onDownloadChangedInternal(DownloadInternal downloadInternal, Download download) {
logd("Download state is changed", downloadInternal);
try {
downloadIndex.putDownload(download);
} catch (IOException e) {
Log.e(TAG, "Failed to update index", e);
}
if (downloadInternal.state == STATE_COMPLETED || downloadInternal.state == STATE_FAILED) {
downloadInternals.remove(downloadInternal);
}
mainHandler.obtainMessage(MSG_DOWNLOAD_CHANGED, download).sendToTarget();
}
private void onDownloadRemovedInternal(DownloadInternal downloadInternal, Download download) { private void onDownloadThreadContentLengthChanged(DownloadThread downloadThread) {
logd("Download is removed", downloadInternal); String downloadId = downloadThread.request.id;
try { getDownload(downloadId).setContentLength(downloadThread.contentLength);
downloadIndex.removeDownload(download.request.id);
} catch (IOException e) {
Log.e(TAG, "Failed to remove from index", e);
} }
downloadInternals.remove(downloadInternal);
mainHandler.obtainMessage(MSG_DOWNLOAD_REMOVED, download).sendToTarget();
}
@StartThreadResults private void release() {
private int startDownloadThread(DownloadInternal downloadInternal) { for (DownloadThread downloadThread : downloadThreads.values()) {
DownloadRequest request = downloadInternal.download.request; downloadThread.cancel(/* released= */ true);
String downloadId = request.id;
if (downloadThreads.containsKey(downloadId)) {
if (stopDownloadThreadInternal(downloadId)) {
return START_THREAD_WAIT_DOWNLOAD_CANCELLATION;
} }
return START_THREAD_WAIT_REMOVAL_TO_FINISH; downloadThreads.clear();
} downloadInternals.clear();
boolean isRemove = downloadInternal.isInRemoveState(); thread.quit();
if (!isRemove) { synchronized (this) {
if (parallelDownloads == maxParallelDownloadsInternal) { released = true;
return START_THREAD_TOO_MANY_DOWNLOADS; notifyAll();
} }
parallelDownloads++;
} }
Downloader downloader = downloaderFactory.createDownloader(request);
DownloadProgress downloadProgress = downloadInternal.download.progress;
DownloadThread downloadThread =
new DownloadThread(
request,
downloader,
downloadProgress,
isRemove,
minRetryCountInternal,
internalHandler);
downloadThreads.put(downloadId, downloadThread);
downloadThread.start();
logd("Download is started", downloadInternal);
return START_THREAD_SUCCEEDED;
}
private boolean stopDownloadThreadInternal(String downloadId) { private void onDownloadChangedInternal(DownloadInternal downloadInternal, Download download) {
DownloadThread downloadThread = downloadThreads.get(downloadId); logd("Download state is changed", downloadInternal);
if (downloadThread != null && !downloadThread.isRemove) { try {
downloadThread.cancel(/* released= */ false); downloadIndex.putDownload(download);
logd("Download is cancelled", downloadThread.request); } catch (IOException e) {
return true; Log.e(TAG, "Failed to update index", e);
}
if (downloadInternal.state == STATE_COMPLETED || downloadInternal.state == STATE_FAILED) {
downloadInternals.remove(downloadInternal);
}
mainHandler.obtainMessage(MSG_DOWNLOAD_CHANGED, download).sendToTarget();
} }
return false;
}
@Nullable private void onDownloadRemovedInternal(DownloadInternal downloadInternal, Download download) {
private DownloadInternal getDownload(String id) { logd("Download is removed", downloadInternal);
for (int i = 0; i < downloadInternals.size(); i++) { try {
DownloadInternal downloadInternal = downloadInternals.get(i); downloadIndex.removeDownload(download.request.id);
if (downloadInternal.download.request.id.equals(id)) { } catch (IOException e) {
return downloadInternal; Log.e(TAG, "Failed to remove from index", e);
} }
downloadInternals.remove(downloadInternal);
mainHandler.obtainMessage(MSG_DOWNLOAD_REMOVED, download).sendToTarget();
} }
return null;
}
private Download loadDownload(String id) { @StartThreadResults
try { private int startDownloadThread(DownloadInternal downloadInternal) {
return downloadIndex.getDownload(id); DownloadRequest request = downloadInternal.download.request;
} catch (IOException e) { String downloadId = request.id;
Log.e(TAG, "loadDownload failed", e); if (downloadThreads.containsKey(downloadId)) {
if (stopDownloadThreadInternal(downloadId)) {
return START_THREAD_WAIT_DOWNLOAD_CANCELLATION;
}
return START_THREAD_WAIT_REMOVAL_TO_FINISH;
}
boolean isRemove = downloadInternal.isInRemoveState();
if (!isRemove) {
if (parallelDownloads == maxParallelDownloads) {
return START_THREAD_TOO_MANY_DOWNLOADS;
}
parallelDownloads++;
}
Downloader downloader = downloaderFactory.createDownloader(request);
DownloadProgress downloadProgress = downloadInternal.download.progress;
DownloadThread downloadThread =
new DownloadThread(request, downloader, downloadProgress, isRemove, minRetryCount, this);
downloadThreads.put(downloadId, downloadThread);
downloadThread.start();
logd("Download is started", downloadInternal);
return START_THREAD_SUCCEEDED;
}
private boolean stopDownloadThreadInternal(String downloadId) {
DownloadThread downloadThread = downloadThreads.get(downloadId);
if (downloadThread != null && !downloadThread.isRemove) {
downloadThread.cancel(/* released= */ false);
logd("Download is cancelled", downloadThread.request);
return true;
}
return false;
} }
return null;
}
private void addDownloadForState(Download download) {
DownloadInternal downloadInternal = new DownloadInternal(this, download);
downloadInternals.add(downloadInternal);
logd("Download is added", downloadInternal);
downloadInternal.initialize();
}
private boolean canStartDownloads() {
return !downloadsPausedInternal && notMetRequirements == 0;
}
/* package */ static Download mergeRequest( @Nullable
Download download, DownloadRequest request, int stopReason) { private DownloadInternal getDownload(String id) {
@Download.State int state = download.state; for (int i = 0; i < downloadInternals.size(); i++) {
if (state == STATE_REMOVING || state == STATE_RESTARTING) { DownloadInternal downloadInternal = downloadInternals.get(i);
state = STATE_RESTARTING; if (downloadInternal.download.request.id.equals(id)) {
} else if (stopReason != STOP_REASON_NONE) { return downloadInternal;
state = STATE_STOPPED; }
} else { }
state = STATE_QUEUED; return null;
} }
long nowMs = System.currentTimeMillis();
long startTimeMs = download.isTerminalState() ? nowMs : download.startTimeMs;
return new Download(
download.request.copyWithMergedRequest(request),
state,
startTimeMs,
/* updateTimeMs= */ nowMs,
/* contentLength= */ C.LENGTH_UNSET,
stopReason,
FAILURE_REASON_NONE);
}
private static Download copyWithState(Download download, @Download.State int state) { private Download loadDownload(String id) {
return new Download( try {
download.request, return downloadIndex.getDownload(id);
state, } catch (IOException e) {
download.startTimeMs, Log.e(TAG, "loadDownload failed", e);
/* updateTimeMs= */ System.currentTimeMillis(), }
download.contentLength, return null;
download.stopReason,
FAILURE_REASON_NONE,
download.progress);
}
private static void logd(String message) {
if (DEBUG) {
Log.d(TAG, message);
} }
}
private static void logd(String message, DownloadInternal downloadInternal) { private void addDownloadForState(Download download) {
logd(message, downloadInternal.download.request); DownloadInternal downloadInternal = new DownloadInternal(this, download);
} downloadInternals.add(downloadInternal);
logd("Download is added", downloadInternal);
private static void logd(String message, DownloadRequest request) { downloadInternal.initialize();
if (DEBUG) {
logd(message + ": " + request);
} }
}
private static void logdFlags(String message, int flags) { private boolean canStartDownloads() {
if (DEBUG) { return !downloadsPaused && notMetRequirements == 0;
logd(message + ": " + Integer.toBinaryString(flags));
} }
} }
private static final class DownloadInternal { private static final class DownloadInternal {
private final DownloadManager downloadManager; private final InternalHandler internalHandler;
private Download download; private Download download;
...@@ -967,8 +976,8 @@ public final class DownloadManager { ...@@ -967,8 +976,8 @@ public final class DownloadManager {
private int stopReason; private int stopReason;
@MonotonicNonNull @Download.FailureReason private int failureReason; @MonotonicNonNull @Download.FailureReason private int failureReason;
private DownloadInternal(DownloadManager downloadManager, Download download) { private DownloadInternal(InternalHandler internalHandler, Download download) {
this.downloadManager = downloadManager; this.internalHandler = internalHandler;
this.download = download; this.download = download;
state = download.state; state = download.state;
contentLength = download.contentLength; contentLength = download.contentLength;
...@@ -1016,7 +1025,7 @@ public final class DownloadManager { ...@@ -1016,7 +1025,7 @@ public final class DownloadManager {
if (state == STATE_QUEUED || state == STATE_DOWNLOADING) { if (state == STATE_QUEUED || state == STATE_DOWNLOADING) {
startOrQueue(); startOrQueue();
} else if (isInRemoveState()) { } else if (isInRemoveState()) {
downloadManager.startDownloadThread(this); internalHandler.startDownloadThread(this);
} }
} }
...@@ -1034,7 +1043,7 @@ public final class DownloadManager { ...@@ -1034,7 +1043,7 @@ public final class DownloadManager {
return; return;
} }
this.contentLength = contentLength; this.contentLength = contentLength;
downloadManager.onDownloadChangedInternal(this, getUpdatedDownload()); internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
} }
private void updateStopState() { private void updateStopState() {
...@@ -1045,12 +1054,12 @@ public final class DownloadManager { ...@@ -1045,12 +1054,12 @@ public final class DownloadManager {
} }
} else { } else {
if (state == STATE_DOWNLOADING || state == STATE_QUEUED) { if (state == STATE_DOWNLOADING || state == STATE_QUEUED) {
downloadManager.stopDownloadThreadInternal(download.request.id); internalHandler.stopDownloadThreadInternal(download.request.id);
setState(STATE_STOPPED); setState(STATE_STOPPED);
} }
} }
if (oldDownload == download) { if (oldDownload == download) {
downloadManager.onDownloadChangedInternal(this, getUpdatedDownload()); internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
} }
} }
...@@ -1059,24 +1068,24 @@ public final class DownloadManager { ...@@ -1059,24 +1068,24 @@ public final class DownloadManager {
// state immediately. // state immediately.
state = initialState; state = initialState;
if (isInRemoveState()) { if (isInRemoveState()) {
downloadManager.startDownloadThread(this); internalHandler.startDownloadThread(this);
} else if (canStart()) { } else if (canStart()) {
startOrQueue(); startOrQueue();
} else { } else {
setState(STATE_STOPPED); setState(STATE_STOPPED);
} }
if (state == initialState) { if (state == initialState) {
downloadManager.onDownloadChangedInternal(this, getUpdatedDownload()); internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
} }
} }
private boolean canStart() { private boolean canStart() {
return downloadManager.canStartDownloads() && stopReason == STOP_REASON_NONE; return internalHandler.canStartDownloads() && stopReason == STOP_REASON_NONE;
} }
private void startOrQueue() { private void startOrQueue() {
Assertions.checkState(!isInRemoveState()); Assertions.checkState(!isInRemoveState());
@StartThreadResults int result = downloadManager.startDownloadThread(this); @StartThreadResults int result = internalHandler.startDownloadThread(this);
Assertions.checkState(result != START_THREAD_WAIT_REMOVAL_TO_FINISH); Assertions.checkState(result != START_THREAD_WAIT_REMOVAL_TO_FINISH);
if (result == START_THREAD_SUCCEEDED || result == START_THREAD_WAIT_DOWNLOAD_CANCELLATION) { if (result == START_THREAD_SUCCEEDED || result == START_THREAD_WAIT_DOWNLOAD_CANCELLATION) {
setState(STATE_DOWNLOADING); setState(STATE_DOWNLOADING);
...@@ -1088,7 +1097,7 @@ public final class DownloadManager { ...@@ -1088,7 +1097,7 @@ public final class DownloadManager {
private void setState(@Download.State int newState) { private void setState(@Download.State int newState) {
if (state != newState) { if (state != newState) {
state = newState; state = newState;
downloadManager.onDownloadChangedInternal(this, getUpdatedDownload()); internalHandler.onDownloadChangedInternal(this, getUpdatedDownload());
} }
} }
...@@ -1097,9 +1106,9 @@ public final class DownloadManager { ...@@ -1097,9 +1106,9 @@ public final class DownloadManager {
return; return;
} }
if (isCanceled) { if (isCanceled) {
downloadManager.startDownloadThread(this); internalHandler.startDownloadThread(this);
} else if (state == STATE_REMOVING) { } else if (state == STATE_REMOVING) {
downloadManager.onDownloadRemovedInternal(this, getUpdatedDownload()); internalHandler.onDownloadRemovedInternal(this, getUpdatedDownload());
} else if (state == STATE_RESTARTING) { } else if (state == STATE_RESTARTING) {
initialize(STATE_QUEUED); initialize(STATE_QUEUED);
} else { // STATE_DOWNLOADING } else { // STATE_DOWNLOADING
...@@ -1122,7 +1131,7 @@ public final class DownloadManager { ...@@ -1122,7 +1131,7 @@ public final class DownloadManager {
private final boolean isRemove; private final boolean isRemove;
private final int minRetryCount; private final int minRetryCount;
private volatile Handler updateHandler; private volatile InternalHandler internalHandler;
private volatile boolean isCanceled; private volatile boolean isCanceled;
private Throwable finalError; private Throwable finalError;
...@@ -1134,13 +1143,13 @@ public final class DownloadManager { ...@@ -1134,13 +1143,13 @@ public final class DownloadManager {
DownloadProgress downloadProgress, DownloadProgress downloadProgress,
boolean isRemove, boolean isRemove,
int minRetryCount, int minRetryCount,
Handler updateHandler) { InternalHandler internalHandler) {
this.request = request; this.request = request;
this.downloader = downloader; this.downloader = downloader;
this.downloadProgress = downloadProgress; this.downloadProgress = downloadProgress;
this.isRemove = isRemove; this.isRemove = isRemove;
this.minRetryCount = minRetryCount; this.minRetryCount = minRetryCount;
this.updateHandler = updateHandler; this.internalHandler = internalHandler;
contentLength = C.LENGTH_UNSET; contentLength = C.LENGTH_UNSET;
} }
...@@ -1150,7 +1159,7 @@ public final class DownloadManager { ...@@ -1150,7 +1159,7 @@ public final class DownloadManager {
// cancellation to complete depends on the implementation of the downloader being used. We // cancellation to complete depends on the implementation of the downloader being used. We
// null the handler reference here so that it doesn't prevent garbage collection of the // null the handler reference here so that it doesn't prevent garbage collection of the
// download manager whilst cancellation is ongoing. // download manager whilst cancellation is ongoing.
updateHandler = null; internalHandler = null;
} }
isCanceled = true; isCanceled = true;
downloader.cancel(); downloader.cancel();
...@@ -1192,9 +1201,9 @@ public final class DownloadManager { ...@@ -1192,9 +1201,9 @@ public final class DownloadManager {
} catch (Throwable e) { } catch (Throwable e) {
finalError = e; finalError = e;
} }
Handler updateHandler = this.updateHandler; Handler internalHandler = this.internalHandler;
if (updateHandler != null) { if (internalHandler != null) {
updateHandler.obtainMessage(MSG_DOWNLOAD_THREAD_STOPPED, this).sendToTarget(); internalHandler.obtainMessage(MSG_DOWNLOAD_THREAD_STOPPED, this).sendToTarget();
} }
} }
...@@ -1204,9 +1213,9 @@ public final class DownloadManager { ...@@ -1204,9 +1213,9 @@ public final class DownloadManager {
downloadProgress.percentDownloaded = percentDownloaded; downloadProgress.percentDownloaded = percentDownloaded;
if (contentLength != this.contentLength) { if (contentLength != this.contentLength) {
this.contentLength = contentLength; this.contentLength = contentLength;
Handler updateHandler = this.updateHandler; Handler internalHandler = this.internalHandler;
if (updateHandler != null) { if (internalHandler != null) {
updateHandler.obtainMessage(MSG_CONTENT_LENGTH_CHANGED, this).sendToTarget(); internalHandler.obtainMessage(MSG_CONTENT_LENGTH_CHANGED, this).sendToTarget();
} }
} }
} }
......
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