Rework DownloadManager to fix remaining TODOs

- Removed DownloadInternal and its sometimes-out-of-sync
  duplicate state
- Fixed downloads being in STOPPED rather than QUEUED state
  when the manager is paused
- Fixed setMaxParallelDownloads to start/stop downloads if
  necessary when the value changes
- Fixed isWaitingForRequirements

PiperOrigin-RevId: 246164845
This commit is contained in:
olly 2019-05-01 19:19:02 +01:00 committed by Oliver Woodman
parent faecef0bef
commit eed5d957d8
6 changed files with 473 additions and 471 deletions

View File

@ -67,11 +67,12 @@ public final class ActionFileUpgradeUtil {
if (actionFile.exists()) { if (actionFile.exists()) {
boolean success = false; boolean success = false;
try { try {
long nowMs = System.currentTimeMillis();
for (DownloadRequest request : actionFile.load()) { for (DownloadRequest request : actionFile.load()) {
if (downloadIdProvider != null) { if (downloadIdProvider != null) {
request = request.copyWithId(downloadIdProvider.getId(request)); request = request.copyWithId(downloadIdProvider.getId(request));
} }
mergeRequest(request, downloadIndex, addNewDownloadsAsCompleted); mergeRequest(request, downloadIndex, addNewDownloadsAsCompleted, nowMs);
} }
success = true; success = true;
} finally { } finally {
@ -93,13 +94,13 @@ public final class ActionFileUpgradeUtil {
/* package */ static void mergeRequest( /* package */ static void mergeRequest(
DownloadRequest request, DownloadRequest request,
DefaultDownloadIndex downloadIndex, DefaultDownloadIndex downloadIndex,
boolean addNewDownloadAsCompleted) boolean addNewDownloadAsCompleted,
long nowMs)
throws IOException { throws IOException {
Download download = downloadIndex.getDownload(request.id); Download download = downloadIndex.getDownload(request.id);
if (download != null) { if (download != null) {
download = DownloadManager.mergeRequest(download, request, download.stopReason); download = DownloadManager.mergeRequest(download, request, download.stopReason, nowMs);
} else { } else {
long nowMs = System.currentTimeMillis();
download = download =
new Download( new Download(
request, request,

View File

@ -69,7 +69,9 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
private static final int COLUMN_INDEX_BYTES_DOWNLOADED = 13; private static final int COLUMN_INDEX_BYTES_DOWNLOADED = 13;
private static final String WHERE_ID_EQUALS = COLUMN_ID + " = ?"; private static final String WHERE_ID_EQUALS = COLUMN_ID + " = ?";
private static final String WHERE_STATE_TERMINAL = private static final String WHERE_STATE_IS_DOWNLOADING =
COLUMN_STATE + " = " + Download.STATE_DOWNLOADING;
private static final String WHERE_STATE_IS_TERMINAL =
getStateQuery(Download.STATE_COMPLETED, Download.STATE_FAILED); getStateQuery(Download.STATE_COMPLETED, Download.STATE_FAILED);
private static final String[] COLUMNS = private static final String[] COLUMNS =
@ -218,6 +220,19 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
} }
} }
@Override
public void setDownloadingStatesToQueued() throws DatabaseIOException {
ensureInitialized();
try {
ContentValues values = new ContentValues();
values.put(COLUMN_STATE, Download.STATE_QUEUED);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.update(tableName, values, WHERE_STATE_IS_DOWNLOADING, /* whereArgs= */ null);
} catch (SQLException e) {
throw new DatabaseIOException(e);
}
}
@Override @Override
public void setStopReason(int stopReason) throws DatabaseIOException { public void setStopReason(int stopReason) throws DatabaseIOException {
ensureInitialized(); ensureInitialized();
@ -225,7 +240,7 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COLUMN_STOP_REASON, stopReason); values.put(COLUMN_STOP_REASON, stopReason);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase(); SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.update(tableName, values, WHERE_STATE_TERMINAL, /* whereArgs= */ null); writableDatabase.update(tableName, values, WHERE_STATE_IS_TERMINAL, /* whereArgs= */ null);
} catch (SQLException e) { } catch (SQLException e) {
throw new DatabaseIOException(e); throw new DatabaseIOException(e);
} }
@ -239,7 +254,10 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
values.put(COLUMN_STOP_REASON, stopReason); values.put(COLUMN_STOP_REASON, stopReason);
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase(); SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
writableDatabase.update( writableDatabase.update(
tableName, values, WHERE_STATE_TERMINAL + " AND " + WHERE_ID_EQUALS, new String[] {id}); tableName,
values,
WHERE_STATE_IS_TERMINAL + " AND " + WHERE_ID_EQUALS,
new String[] {id});
} catch (SQLException e) { } catch (SQLException e) {
throw new DatabaseIOException(e); throw new DatabaseIOException(e);
} }

View File

@ -37,6 +37,13 @@ public interface WritableDownloadIndex extends DownloadIndex {
*/ */
void removeDownload(String id) throws IOException; void removeDownload(String id) throws IOException;
/**
* Sets all {@link Download#STATE_DOWNLOADING} states to {@link Download#STATE_QUEUED}.
*
* @throws IOException If an error occurs updating the state.
*/
void setDownloadingStatesToQueued() throws IOException;
/** /**
* Sets the stop reason of the downloads in a terminal state ({@link Download#STATE_COMPLETED}, * Sets the stop reason of the downloads in a terminal state ({@link Download#STATE_COMPLETED},
* {@link Download#STATE_FAILED}). * {@link Download#STATE_FAILED}).

View File

@ -38,6 +38,8 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class ActionFileUpgradeUtilTest { public class ActionFileUpgradeUtilTest {
private static final long NOW_MS = 1234;
private File tempFile; private File tempFile;
private ExoDatabaseProvider databaseProvider; private ExoDatabaseProvider databaseProvider;
private DefaultDownloadIndex downloadIndex; private DefaultDownloadIndex downloadIndex;
@ -113,7 +115,7 @@ public class ActionFileUpgradeUtilTest {
data); data);
ActionFileUpgradeUtil.mergeRequest( ActionFileUpgradeUtil.mergeRequest(
request, downloadIndex, /* addNewDownloadAsCompleted= */ false); request, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS);
assertDownloadIndexContainsRequest(request, Download.STATE_QUEUED); assertDownloadIndexContainsRequest(request, Download.STATE_QUEUED);
} }
@ -141,9 +143,9 @@ public class ActionFileUpgradeUtilTest {
/* customCacheKey= */ "key123", /* customCacheKey= */ "key123",
new byte[] {5, 4, 3, 2, 1}); new byte[] {5, 4, 3, 2, 1});
ActionFileUpgradeUtil.mergeRequest( ActionFileUpgradeUtil.mergeRequest(
request1, downloadIndex, /* addNewDownloadAsCompleted= */ false); request1, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS);
ActionFileUpgradeUtil.mergeRequest( ActionFileUpgradeUtil.mergeRequest(
request2, downloadIndex, /* addNewDownloadAsCompleted= */ false); request2, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS);
Download download = downloadIndex.getDownload(request2.id); Download download = downloadIndex.getDownload(request2.id);
assertThat(download).isNotNull(); assertThat(download).isNotNull();
@ -178,16 +180,16 @@ public class ActionFileUpgradeUtilTest {
/* customCacheKey= */ "key123", /* customCacheKey= */ "key123",
new byte[] {5, 4, 3, 2, 1}); new byte[] {5, 4, 3, 2, 1});
ActionFileUpgradeUtil.mergeRequest( ActionFileUpgradeUtil.mergeRequest(
request1, downloadIndex, /* addNewDownloadAsCompleted= */ false); request1, downloadIndex, /* addNewDownloadAsCompleted= */ false, NOW_MS);
// Merging existing download, keeps it queued. // Merging existing download, keeps it queued.
ActionFileUpgradeUtil.mergeRequest( ActionFileUpgradeUtil.mergeRequest(
request1, downloadIndex, /* addNewDownloadAsCompleted= */ true); request1, downloadIndex, /* addNewDownloadAsCompleted= */ true, NOW_MS);
assertThat(downloadIndex.getDownload(request1.id).state).isEqualTo(Download.STATE_QUEUED); assertThat(downloadIndex.getDownload(request1.id).state).isEqualTo(Download.STATE_QUEUED);
// New download is merged as completed. // New download is merged as completed.
ActionFileUpgradeUtil.mergeRequest( ActionFileUpgradeUtil.mergeRequest(
request2, downloadIndex, /* addNewDownloadAsCompleted= */ true); request2, downloadIndex, /* addNewDownloadAsCompleted= */ true, NOW_MS);
assertThat(downloadIndex.getDownload(request2.id).state).isEqualTo(Download.STATE_COMPLETED); assertThat(downloadIndex.getDownload(request2.id).state).isEqualTo(Download.STATE_COMPLETED);
} }

View File

@ -61,6 +61,8 @@ public class DownloadManagerTest {
private static final int APP_STOP_REASON = 1; private static final int APP_STOP_REASON = 1;
/** The minimum number of times a task must be retried before failing. */ /** The minimum number of times a task must be retried before failing. */
private static final int MIN_RETRY_COUNT = 3; private static final int MIN_RETRY_COUNT = 3;
/** Dummy value for the current time. */
private static final long NOW_MS = 1234;
private Uri uri1; private Uri uri1;
private Uri uri2; private Uri uri2;
@ -132,6 +134,7 @@ public class DownloadManagerTest {
task.assertCompleted(); task.assertCompleted();
runner.assertCreatedDownloaderCount(1); runner.assertCreatedDownloaderCount(1);
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError(); downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
assertThat(downloadManager.getCurrentDownloads()).isEmpty();
} }
@Test @Test
@ -143,6 +146,7 @@ public class DownloadManagerTest {
task.assertRemoved(); task.assertRemoved();
runner.assertCreatedDownloaderCount(2); runner.assertCreatedDownloaderCount(2);
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError(); downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
assertThat(downloadManager.getCurrentDownloads()).isEmpty();
} }
@Test @Test
@ -158,6 +162,7 @@ public class DownloadManagerTest {
downloader.assertReleased().assertStartCount(MIN_RETRY_COUNT + 1); downloader.assertReleased().assertStartCount(MIN_RETRY_COUNT + 1);
runner.getTask().assertFailed(); runner.getTask().assertFailed();
downloadManagerListener.blockUntilTasksComplete(); downloadManagerListener.blockUntilTasksComplete();
assertThat(downloadManager.getCurrentDownloads()).isEmpty();
} }
@Test @Test
@ -174,6 +179,7 @@ public class DownloadManagerTest {
downloader.assertReleased().assertStartCount(MIN_RETRY_COUNT + 1); downloader.assertReleased().assertStartCount(MIN_RETRY_COUNT + 1);
runner.getTask().assertCompleted(); runner.getTask().assertCompleted();
downloadManagerListener.blockUntilTasksComplete(); downloadManagerListener.blockUntilTasksComplete();
assertThat(downloadManager.getCurrentDownloads()).isEmpty();
} }
@Test @Test
@ -341,7 +347,7 @@ public class DownloadManagerTest {
} }
@Test @Test
public void getTasks_returnTasks() { public void getCurrentDownloads_returnsCurrentDownloads() {
TaskWrapper task1 = new DownloadRunner(uri1).postDownloadRequest().getTask(); TaskWrapper task1 = new DownloadRunner(uri1).postDownloadRequest().getTask();
TaskWrapper task2 = new DownloadRunner(uri2).postDownloadRequest().getTask(); TaskWrapper task2 = new DownloadRunner(uri2).postDownloadRequest().getTask();
TaskWrapper task3 = TaskWrapper task3 =
@ -370,13 +376,11 @@ public class DownloadManagerTest {
runOnMainThread(() -> downloadManager.pauseDownloads()); runOnMainThread(() -> downloadManager.pauseDownloads());
// TODO: This should be assertQueued. Fix implementation and update test. runner1.getTask().assertQueued();
runner1.getTask().assertStopped();
// remove requests aren't stopped. // remove requests aren't stopped.
runner2.getDownloader(1).unblock().assertReleased(); runner2.getDownloader(1).unblock().assertReleased();
// TODO: This should be assertQueued. Fix implementation and update test. runner2.getTask().assertQueued();
runner2.getTask().assertStopped();
// Although remove2 is finished, download2 doesn't start. // Although remove2 is finished, download2 doesn't start.
runner2.getDownloader(2).assertDoesNotStart(); runner2.getDownloader(2).assertDoesNotStart();
@ -397,7 +401,7 @@ public class DownloadManagerTest {
} }
@Test @Test
public void manuallyStopAndResumeSingleDownload() throws Throwable { public void setAndClearSingleDownloadStopReason() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1).postDownloadRequest(); DownloadRunner runner = new DownloadRunner(uri1).postDownloadRequest();
TaskWrapper task = runner.getTask(); TaskWrapper task = runner.getTask();
@ -415,7 +419,7 @@ public class DownloadManagerTest {
} }
@Test @Test
public void manuallyStoppedDownloadCanBeCancelled() throws Throwable { public void setSingleDownloadStopReasonThenRemove_removesDownload() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1).postDownloadRequest(); DownloadRunner runner = new DownloadRunner(uri1).postDownloadRequest();
TaskWrapper task = runner.getTask(); TaskWrapper task = runner.getTask();
@ -433,7 +437,7 @@ public class DownloadManagerTest {
} }
@Test @Test
public void manuallyStoppedSingleDownload_doesNotAffectOthers() throws Throwable { public void setSingleDownloadStopReason_doesNotAffectOtherDownloads() throws Throwable {
DownloadRunner runner1 = new DownloadRunner(uri1); DownloadRunner runner1 = new DownloadRunner(uri1);
DownloadRunner runner2 = new DownloadRunner(uri2); DownloadRunner runner2 = new DownloadRunner(uri2);
DownloadRunner runner3 = new DownloadRunner(uri3); DownloadRunner runner3 = new DownloadRunner(uri3);
@ -455,21 +459,22 @@ public class DownloadManagerTest {
} }
@Test @Test
public void mergeRequest_removingDownload_becomesRestarting() { public void mergeRequest_removing_becomesRestarting() {
DownloadRequest downloadRequest = createDownloadRequest(); DownloadRequest downloadRequest = createDownloadRequest();
DownloadBuilder downloadBuilder = DownloadBuilder downloadBuilder =
new DownloadBuilder(downloadRequest).setState(Download.STATE_REMOVING); new DownloadBuilder(downloadRequest).setState(Download.STATE_REMOVING);
Download download = downloadBuilder.build(); Download download = downloadBuilder.build();
Download mergedDownload = Download mergedDownload =
DownloadManager.mergeRequest(download, downloadRequest, download.stopReason); DownloadManager.mergeRequest(download, downloadRequest, download.stopReason, NOW_MS);
Download expectedDownload = downloadBuilder.setState(Download.STATE_RESTARTING).build(); Download expectedDownload =
assertEqualIgnoringTimeFields(mergedDownload, expectedDownload); downloadBuilder.setStartTimeMs(NOW_MS).setState(Download.STATE_RESTARTING).build();
assertEqualIgnoringUpdateTime(mergedDownload, expectedDownload);
} }
@Test @Test
public void mergeRequest_failedDownload_becomesQueued() { public void mergeRequest_failed_becomesQueued() {
DownloadRequest downloadRequest = createDownloadRequest(); DownloadRequest downloadRequest = createDownloadRequest();
DownloadBuilder downloadBuilder = DownloadBuilder downloadBuilder =
new DownloadBuilder(downloadRequest) new DownloadBuilder(downloadRequest)
@ -478,18 +483,19 @@ public class DownloadManagerTest {
Download download = downloadBuilder.build(); Download download = downloadBuilder.build();
Download mergedDownload = Download mergedDownload =
DownloadManager.mergeRequest(download, downloadRequest, download.stopReason); DownloadManager.mergeRequest(download, downloadRequest, download.stopReason, NOW_MS);
Download expectedDownload = Download expectedDownload =
downloadBuilder downloadBuilder
.setStartTimeMs(NOW_MS)
.setState(Download.STATE_QUEUED) .setState(Download.STATE_QUEUED)
.setFailureReason(Download.FAILURE_REASON_NONE) .setFailureReason(Download.FAILURE_REASON_NONE)
.build(); .build();
assertEqualIgnoringTimeFields(mergedDownload, expectedDownload); assertEqualIgnoringUpdateTime(mergedDownload, expectedDownload);
} }
@Test @Test
public void mergeRequest_stoppedDownload_staysStopped() { public void mergeRequest_stopped_staysStopped() {
DownloadRequest downloadRequest = createDownloadRequest(); DownloadRequest downloadRequest = createDownloadRequest();
DownloadBuilder downloadBuilder = DownloadBuilder downloadBuilder =
new DownloadBuilder(downloadRequest) new DownloadBuilder(downloadRequest)
@ -498,13 +504,13 @@ public class DownloadManagerTest {
Download download = downloadBuilder.build(); Download download = downloadBuilder.build();
Download mergedDownload = Download mergedDownload =
DownloadManager.mergeRequest(download, downloadRequest, download.stopReason); DownloadManager.mergeRequest(download, downloadRequest, download.stopReason, NOW_MS);
assertEqualIgnoringTimeFields(mergedDownload, download); assertEqualIgnoringUpdateTime(mergedDownload, download);
} }
@Test @Test
public void mergeRequest_stopReasonSetButNotStopped_becomesStopped() { public void mergeRequest_completedWithStopReason_becomesStopped() {
DownloadRequest downloadRequest = createDownloadRequest(); DownloadRequest downloadRequest = createDownloadRequest();
DownloadBuilder downloadBuilder = DownloadBuilder downloadBuilder =
new DownloadBuilder(downloadRequest) new DownloadBuilder(downloadRequest)
@ -513,10 +519,11 @@ public class DownloadManagerTest {
Download download = downloadBuilder.build(); Download download = downloadBuilder.build();
Download mergedDownload = Download mergedDownload =
DownloadManager.mergeRequest(download, downloadRequest, download.stopReason); DownloadManager.mergeRequest(download, downloadRequest, download.stopReason, NOW_MS);
Download expectedDownload = downloadBuilder.setState(Download.STATE_STOPPED).build(); Download expectedDownload =
assertEqualIgnoringTimeFields(mergedDownload, expectedDownload); downloadBuilder.setStartTimeMs(NOW_MS).setState(Download.STATE_STOPPED).build();
assertEqualIgnoringUpdateTime(mergedDownload, expectedDownload);
} }
private void setUpDownloadManager(final int maxParallelDownloads) throws Exception { private void setUpDownloadManager(final int maxParallelDownloads) throws Exception {
@ -554,9 +561,10 @@ public class DownloadManagerTest {
dummyMainThread.runTestOnMainThread(r); dummyMainThread.runTestOnMainThread(r);
} }
private static void assertEqualIgnoringTimeFields(Download download, Download that) { private static void assertEqualIgnoringUpdateTime(Download download, Download that) {
assertThat(download.request).isEqualTo(that.request); assertThat(download.request).isEqualTo(that.request);
assertThat(download.state).isEqualTo(that.state); assertThat(download.state).isEqualTo(that.state);
assertThat(download.startTimeMs).isEqualTo(that.startTimeMs);
assertThat(download.contentLength).isEqualTo(that.contentLength); assertThat(download.contentLength).isEqualTo(that.contentLength);
assertThat(download.failureReason).isEqualTo(that.failureReason); assertThat(download.failureReason).isEqualTo(that.failureReason);
assertThat(download.stopReason).isEqualTo(that.stopReason); assertThat(download.stopReason).isEqualTo(that.stopReason);