Use DownloadState in DownloadManager

PiperOrigin-RevId: 233587184
This commit is contained in:
eguven 2019-02-12 13:07:03 +00:00 committed by Andrew Lewis
parent 399a963e02
commit 5782bbc6e5
8 changed files with 125 additions and 555 deletions

View File

@ -172,11 +172,6 @@ public final class DownloadAction {
return output.toByteArray(); return output.toByteArray();
} }
/** Returns whether this is an action for the same media as the {@code other}. */
public boolean isSameMedia(DownloadAction other) {
return id.equals(other.id);
}
/** Returns keys of streams to be downloaded. */ /** Returns keys of streams to be downloaded. */
public List<StreamKey> getKeys() { public List<StreamKey> getKeys() {
return keys; return keys;

View File

@ -1,79 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.offline;
import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
/** {@link DownloadAction} related utility methods. */
public class DownloadActionUtil {
private DownloadActionUtil() {}
/**
* Merge {@link DownloadAction}s in {@code actionQueue} to minimum number of actions.
*
* <p>All actions must have the same type and must be for the same media.
*
* @param actionQueue Queue of actions. Must not be empty.
* @return The first action in the queue.
*/
public static DownloadAction mergeActions(ArrayDeque<DownloadAction> actionQueue) {
DownloadAction removeAction = null;
DownloadAction downloadAction = null;
HashSet<StreamKey> keys = new HashSet<>();
boolean downloadAllTracks = false;
DownloadAction firstAction = Assertions.checkNotNull(actionQueue.peek());
while (!actionQueue.isEmpty()) {
DownloadAction action = actionQueue.remove();
Assertions.checkState(action.type.equals(firstAction.type));
Assertions.checkState(action.isSameMedia(firstAction));
if (action.isRemoveAction) {
removeAction = action;
downloadAction = null;
keys.clear();
downloadAllTracks = false;
} else {
if (!downloadAllTracks) {
if (action.keys.isEmpty()) {
downloadAllTracks = true;
keys.clear();
} else {
keys.addAll(action.keys);
}
}
downloadAction = action;
}
}
if (removeAction != null) {
actionQueue.add(removeAction);
}
if (downloadAction != null) {
actionQueue.add(
DownloadAction.createDownloadAction(
downloadAction.type,
downloadAction.uri,
new ArrayList<>(keys),
downloadAction.customCacheKey,
downloadAction.data));
}
return Assertions.checkNotNull(actionQueue.peek());
}
}

View File

@ -46,7 +46,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
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;
@ -307,7 +309,7 @@ public final class DownloadManager {
Assertions.checkState(!released); Assertions.checkState(!released);
for (int i = 0; i < downloads.size(); i++) { for (int i = 0; i < downloads.size(); i++) {
Download download = downloads.get(i); Download download = downloads.get(i);
if (download.id.equals(id)) { if (download.getId().equals(id)) {
return download.getDownloadState(); return download.getDownloadState();
} }
} }
@ -384,19 +386,15 @@ public final class DownloadManager {
if (released) { if (released) {
return; return;
} }
notifyListenersDownloadStateChange(download);
if (download.isFinished()) {
downloads.remove(download);
saveActions();
}
}
private void notifyListenersDownloadStateChange(Download download) {
logd("Download state is changed", download); logd("Download state is changed", download);
DownloadState downloadState = download.getDownloadState(); DownloadState downloadState = download.getDownloadState();
for (Listener listener : listeners) { for (Listener listener : listeners) {
listener.onDownloadStateChanged(this, downloadState); listener.onDownloadStateChanged(this, downloadState);
} }
if (download.isFinished()) {
downloads.remove(download);
saveActions();
}
} }
private void onRequirementsStateChanged(@Requirements.RequirementFlags int notMetRequirements) { private void onRequirementsStateChanged(@Requirements.RequirementFlags int notMetRequirements) {
@ -464,7 +462,7 @@ public final class DownloadManager {
} }
ArrayList<DownloadAction> actions = new ArrayList<>(downloads.size()); ArrayList<DownloadAction> actions = new ArrayList<>(downloads.size());
for (int i = 0; i < downloads.size(); i++) { for (int i = 0; i < downloads.size(); i++) {
actions.addAll(downloads.get(i).actionQueue); downloads.get(i).addActions(actions);
} }
final DownloadAction[] actionsArray = actions.toArray(new DownloadAction[0]); final DownloadAction[] actionsArray = actions.toArray(new DownloadAction[0]);
fileIOHandler.post( fileIOHandler.post(
@ -551,6 +549,9 @@ public final class DownloadManager {
} }
private void onDownloadThreadStopped(DownloadThread downloadThread, Throwable finalError) { private void onDownloadThreadStopped(DownloadThread downloadThread, Throwable finalError) {
if (released) {
return;
}
Download download = downloadThread.download; Download download = downloadThread.download;
logd("Download is stopped", download); logd("Download is stopped", download);
activeDownloads.remove(download); activeDownloads.remove(download);
@ -581,12 +582,9 @@ public final class DownloadManager {
} }
private static final class Download { private static final class Download {
private final String id;
private final DownloadManager downloadManager; private final DownloadManager downloadManager;
private final long startTimeMs;
private final ArrayDeque<DownloadAction> actionQueue;
private DownloadState downloadState;
@DownloadState.State private int state; @DownloadState.State private int state;
@MonotonicNonNull @DownloadState.FailureReason private int failureReason; @MonotonicNonNull @DownloadState.FailureReason private int failureReason;
@DownloadState.StopFlags private int stopFlags; @DownloadState.StopFlags private int stopFlags;
@ -599,47 +597,26 @@ public final class DownloadManager {
@DownloadState.StopFlags int stopFlags, @DownloadState.StopFlags int stopFlags,
@Requirements.RequirementFlags int notMetRequirements, @Requirements.RequirementFlags int notMetRequirements,
int manualStopReason) { int manualStopReason) {
this.id = action.id;
this.downloadManager = downloadManager; this.downloadManager = downloadManager;
this.notMetRequirements = notMetRequirements; this.notMetRequirements = notMetRequirements;
this.manualStopReason = manualStopReason; this.manualStopReason = manualStopReason;
this.stopFlags = stopFlags; this.stopFlags = stopFlags;
this.startTimeMs = System.currentTimeMillis(); downloadState = new DownloadState(action);
actionQueue = new ArrayDeque<>();
actionQueue.add(action);
// Set to queued state but don't notify listeners until we make sure we don't switch to initialize(downloadState.state);
// another state immediately.
state = STATE_QUEUED;
initialize();
if (state == STATE_QUEUED) {
downloadManager.onDownloadStateChange(this);
} }
public String getId() {
return downloadState.id;
} }
public boolean addAction(DownloadAction newAction) { public boolean addAction(DownloadAction newAction) {
DownloadAction action = actionQueue.peek(); if (!getId().equals(newAction.id)) {
if (!action.isSameMedia(newAction)) {
return false; return false;
} }
Assertions.checkState(action.type.equals(newAction.type)); Assertions.checkState(downloadState.type.equals(newAction.type));
actionQueue.add(newAction); downloadState = downloadState.mergeAction(newAction);
DownloadAction updatedAction = DownloadActionUtil.mergeActions(actionQueue); initialize(downloadState.state);
if (state == STATE_REMOVING) {
Assertions.checkState(updatedAction.isRemoveAction);
if (actionQueue.size() > 1) {
setState(STATE_RESTARTING);
}
} else if (state == STATE_RESTARTING) {
Assertions.checkState(updatedAction.isRemoveAction);
if (actionQueue.size() == 1) {
setState(STATE_REMOVING);
}
} else if (!action.equals(updatedAction)) {
Assertions.checkState(
state == STATE_DOWNLOADING || state == STATE_QUEUED || state == STATE_STOPPED);
initialize();
}
return true; return true;
} }
@ -653,24 +630,25 @@ public final class DownloadManager {
downloadedBytes = downloader.getDownloadedBytes(); downloadedBytes = downloader.getDownloadedBytes();
totalBytes = downloader.getTotalBytes(); totalBytes = downloader.getTotalBytes();
} }
DownloadAction action = actionQueue.peek(); downloadState =
return new DownloadState( new DownloadState(
action.id, downloadState.id,
action.type, downloadState.type,
action.uri, downloadState.uri,
action.customCacheKey, downloadState.cacheKey,
state, state,
downloadPercentage, downloadPercentage,
downloadedBytes, downloadedBytes,
totalBytes, totalBytes,
failureReason, state != STATE_FAILED ? FAILURE_REASON_NONE : failureReason,
stopFlags, stopFlags,
notMetRequirements, notMetRequirements,
manualStopReason, manualStopReason,
startTimeMs, downloadState.startTimeMs,
/* updateTimeMs= */ System.currentTimeMillis(), /* updateTimeMs= */ System.currentTimeMillis(),
action.keys.toArray(new StreamKey[0]), downloadState.streamKeys,
action.data); downloadState.customMetadata);
return downloadState;
} }
public boolean isFinished() { public boolean isFinished() {
@ -683,14 +661,14 @@ public final class DownloadManager {
@Override @Override
public String toString() { public String toString() {
return id + ' ' + DownloadState.getStateString(state); return getId() + ' ' + DownloadState.getStateString(state);
} }
public void start() { public void start() {
if (state == STATE_QUEUED || state == STATE_DOWNLOADING) { if (state == STATE_QUEUED || state == STATE_DOWNLOADING) {
startOrQueue(); startOrQueue();
} else if (state == STATE_REMOVING || state == STATE_RESTARTING) { } else if (state == STATE_REMOVING || state == STATE_RESTARTING) {
downloadManager.startDownloadThread(this, actionQueue.peek()); downloadManager.startDownloadThread(this, getAction());
} }
} }
@ -725,26 +703,25 @@ public final class DownloadManager {
} }
} }
private void initialize() { private void initialize(int initialState) {
DownloadAction action = actionQueue.peek(); // Don't notify listeners with initial state until we make sure we don't switch to
if (action.isRemoveAction) { // another state immediately.
int result = downloadManager.startDownloadThread(this, action); state = initialState;
Assertions.checkState( if (state == STATE_REMOVING || state == STATE_RESTARTING) {
result == START_THREAD_SUCCEEDED downloadManager.startDownloadThread(this, getAction());
|| result == START_THREAD_WAIT_DOWNLOAD_CANCELLATION
|| result == START_THREAD_NOT_ALLOWED);
setState(actionQueue.size() == 1 ? STATE_REMOVING : STATE_RESTARTING);
} else if (stopFlags != 0) { } else if (stopFlags != 0) {
setState(STATE_STOPPED); setState(STATE_STOPPED);
} else { } else {
startOrQueue(); startOrQueue();
} }
if (state == initialState) {
downloadManager.onDownloadStateChange(this);
}
} }
private void startOrQueue() { private void startOrQueue() {
DownloadAction action = Assertions.checkNotNull(actionQueue.peek()); Assertions.checkState(!(state == STATE_REMOVING || state == STATE_RESTARTING));
Assertions.checkState(!action.isRemoveAction); @StartThreadResults int result = downloadManager.startDownloadThread(this, getAction());
@StartThreadResults int result = downloadManager.startDownloadThread(this, action);
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);
@ -753,6 +730,22 @@ public final class DownloadManager {
} }
} }
private DownloadAction getAction() {
Assertions.checkState(state != STATE_REMOVED);
if (state == STATE_REMOVING || state == STATE_RESTARTING) {
return DownloadAction.createRemoveAction(
downloadState.type, downloadState.uri, downloadState.cacheKey);
}
return getDownloadAction(downloadState);
}
private void addActions(List<DownloadAction> actions) {
actions.add(getAction());
if (state == STATE_RESTARTING) {
actions.add(getDownloadAction(downloadState));
}
}
private void setState(@DownloadState.State int newState) { private void setState(@DownloadState.State int newState) {
if (state != newState) { if (state != newState) {
state = newState; state = newState;
@ -761,29 +754,32 @@ public final class DownloadManager {
} }
private void onDownloadThreadStopped(boolean isCanceled, @Nullable Throwable error) { private void onDownloadThreadStopped(boolean isCanceled, @Nullable Throwable error) {
failureReason = FAILURE_REASON_NONE; if (isIdle()) {
if (isCanceled) {
if (!isIdle()) {
downloadManager.startDownloadThread(this, actionQueue.peek());
}
return; return;
} }
if (error != null && state == STATE_DOWNLOADING) { if (isCanceled) {
downloadManager.startDownloadThread(this, getAction());
} else if (state == STATE_RESTARTING) {
initialize(STATE_QUEUED);
} else if (state == STATE_REMOVING) {
setState(STATE_REMOVED);
} else { // STATE_DOWNLOADING
if (error != null) {
failureReason = FAILURE_REASON_UNKNOWN; failureReason = FAILURE_REASON_UNKNOWN;
setState(STATE_FAILED); setState(STATE_FAILED);
return;
}
if (actionQueue.size() == 1) {
if (state == STATE_REMOVING) {
setState(STATE_REMOVED);
} else { } else {
Assertions.checkState(state == STATE_DOWNLOADING);
setState(STATE_COMPLETED); setState(STATE_COMPLETED);
} }
return;
} }
actionQueue.remove(); }
initialize();
private static DownloadAction getDownloadAction(DownloadState downloadState) {
return DownloadAction.createDownloadAction(
downloadState.type,
downloadState.uri,
Arrays.asList(downloadState.streamKeys),
downloadState.cacheKey,
downloadState.customMetadata);
} }
} }

View File

@ -247,7 +247,7 @@ public final class DownloadState {
type, type,
action.uri, action.uri,
action.customCacheKey, action.customCacheKey,
getNextState(action, state), getNextState(state, action.isRemoveAction),
/* downloadPercentage= */ C.PERCENTAGE_UNSET, /* downloadPercentage= */ C.PERCENTAGE_UNSET,
downloadedBytes, downloadedBytes,
/* totalBytes= */ C.LENGTH_UNSET, /* totalBytes= */ C.LENGTH_UNSET,
@ -256,25 +256,25 @@ public final class DownloadState {
notMetRequirements, notMetRequirements,
manualStopReason, manualStopReason,
startTimeMs, startTimeMs,
updateTimeMs, /* updateTimeMs= */ System.currentTimeMillis(),
mergeStreamKeys(this, action), mergeStreamKeys(this, action),
action.data); action.data);
} }
private static int getNextState(DownloadAction action, int currentState) { private static int getNextState(int currentState, boolean remove) {
int newState; int nextState;
if (action.isRemoveAction) { if (remove) {
newState = STATE_REMOVING; nextState = STATE_REMOVING;
} else { } else {
if (currentState == STATE_REMOVING || currentState == STATE_RESTARTING) { if (currentState == STATE_REMOVING || currentState == STATE_RESTARTING) {
newState = STATE_RESTARTING; nextState = STATE_RESTARTING;
} else if (currentState == STATE_STOPPED) { } else if (currentState == STATE_STOPPED) {
newState = STATE_STOPPED; nextState = STATE_STOPPED;
} else { } else {
newState = STATE_QUEUED; nextState = STATE_QUEUED;
} }
} }
return newState; return nextState;
} }
private static StreamKey[] mergeStreamKeys(DownloadState downloadState, DownloadAction action) { private static StreamKey[] mergeStreamKeys(DownloadState downloadState, DownloadAction action) {

View File

@ -66,38 +66,38 @@ public class DownloadActionTest {
} }
@Test @Test
public void testSameUri_IsSameMedia() { public void testSameUri_hasSameId() {
DownloadAction action1 = createDownloadAction(uri1); DownloadAction action1 = createDownloadAction(uri1);
DownloadAction action2 = createDownloadAction(uri1); DownloadAction action2 = createDownloadAction(uri1);
assertThat(action1.isSameMedia(action2)).isTrue(); assertThat(action1.id.equals(action2.id)).isTrue();
} }
@Test @Test
public void testSameUriDifferentAction_IsSameMedia() { public void testSameUriDifferentAction_hasSameId() {
DownloadAction action1 = createDownloadAction(uri1); DownloadAction action1 = createDownloadAction(uri1);
DownloadAction action2 = createRemoveAction(uri1); DownloadAction action2 = createRemoveAction(uri1);
assertThat(action1.isSameMedia(action2)).isTrue(); assertThat(action1.id.equals(action2.id)).isTrue();
} }
@Test @Test
public void testDifferentUri_IsNotSameMedia() { public void testDifferentUri_IsNotSameMedia() {
DownloadAction action1 = createDownloadAction(uri1); DownloadAction action1 = createDownloadAction(uri1);
DownloadAction action2 = createDownloadAction(uri2); DownloadAction action2 = createDownloadAction(uri2);
assertThat(action1.isSameMedia(action2)).isFalse(); assertThat(action1.id.equals(action2.id)).isFalse();
} }
@Test @Test
public void testSameCacheKeyDifferentUri_IsSameMedia() { public void testSameCacheKeyDifferentUri_hasSameId() {
DownloadAction action1 = DownloadAction.createRemoveAction(TYPE_DASH, uri1, "key123"); DownloadAction action1 = DownloadAction.createRemoveAction(TYPE_DASH, uri1, "key123");
DownloadAction action2 = DownloadAction.createRemoveAction(TYPE_DASH, uri2, "key123"); DownloadAction action2 = DownloadAction.createRemoveAction(TYPE_DASH, uri2, "key123");
assertThat(action1.isSameMedia(action2)).isTrue(); assertThat(action1.id.equals(action2.id)).isTrue();
} }
@Test @Test
public void testDifferentCacheDifferentUri_IsNotSameMedia() { public void testDifferentCacheKeyDifferentUri_hasDifferentId() {
DownloadAction action1 = DownloadAction.createRemoveAction(TYPE_DASH, uri1, "key123"); DownloadAction action1 = DownloadAction.createRemoveAction(TYPE_DASH, uri1, "key123");
DownloadAction action2 = DownloadAction.createRemoveAction(TYPE_DASH, uri2, "key456"); DownloadAction action2 = DownloadAction.createRemoveAction(TYPE_DASH, uri2, "key456");
assertThat(action1.isSameMedia(action2)).isFalse(); assertThat(action1.id.equals(action2.id)).isFalse();
} }
@SuppressWarnings("EqualsWithItself") @SuppressWarnings("EqualsWithItself")

View File

@ -1,335 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.offline;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.net.Uri;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link DownloadActionUtil} class. */
@RunWith(RobolectricTestRunner.class)
public class DownloadActionUtilTest {
private Uri uri1;
private Uri uri2;
@Before
public void setUp() throws Exception {
uri1 = Uri.parse("http://abc.com/media1");
uri2 = Uri.parse("http://abc.com/media2");
}
@Test
public void mergeActions_ifQueueEmpty_throwsException() {
try {
DownloadActionUtil.mergeActions(toActionQueue());
fail();
} catch (Exception e) {
// Expected.
}
}
@Test
public void mergeActions_ifOneActionInQueue_returnsTheSameAction() {
DownloadAction action = createDownloadAction(uri1);
assertThat(DownloadActionUtil.mergeActions(toActionQueue(action))).isEqualTo(action);
}
@Test
public void mergeActions_ifActionsHaveDifferentType_throwsException() {
DownloadAction downloadAction1 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri1,
Collections.emptyList(),
/* customCacheKey= */ null,
/* data= */ null);
DownloadAction downloadAction2 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_DASH,
uri1,
Collections.emptyList(),
/* customCacheKey= */ null,
/* data= */ null);
ArrayDeque<DownloadAction> actionQueue = toActionQueue(downloadAction1, downloadAction2);
try {
DownloadActionUtil.mergeActions(actionQueue);
fail();
} catch (Exception e) {
// Expected.
}
}
@Test
public void mergeActions_ifActionsHaveDifferentCacheKeys_throwsException() {
DownloadAction downloadAction1 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri1,
Collections.emptyList(),
/* customCacheKey= */ "cacheKey1",
/* data= */ null);
DownloadAction downloadAction2 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri1,
Collections.emptyList(),
/* customCacheKey= */ "cacheKey2",
/* data= */ null);
ArrayDeque<DownloadAction> actionQueue = toActionQueue(downloadAction1, downloadAction2);
try {
DownloadActionUtil.mergeActions(actionQueue);
fail();
} catch (Exception e) {
// Expected.
}
}
@Test
public void mergeActions_nullCacheKeyAndDifferentUrl_throwsException() {
DownloadAction downloadAction1 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri1,
Collections.emptyList(),
/* customCacheKey= */ null,
/* data= */ null);
DownloadAction downloadAction2 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri2,
Collections.emptyList(),
/* customCacheKey= */ null,
/* data= */ null);
ArrayDeque<DownloadAction> actionQueue = toActionQueue(downloadAction1, downloadAction2);
try {
DownloadActionUtil.mergeActions(actionQueue);
fail();
} catch (Exception e) {
// Expected.
}
}
@Test
public void mergeActions_sameCacheKeyAndDifferentUrl_latterUrlUsed() {
DownloadAction downloadAction1 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri1,
Collections.emptyList(),
/* customCacheKey= */ "cacheKey1",
/* data= */ null);
DownloadAction downloadAction2 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri2,
Collections.emptyList(),
/* customCacheKey= */ "cacheKey1",
/* data= */ null);
ArrayDeque<DownloadAction> actionQueue = toActionQueue(downloadAction1, downloadAction2);
DownloadActionUtil.mergeActions(actionQueue);
DownloadAction mergedAction = DownloadActionUtil.mergeActions(actionQueue);
assertThat(mergedAction.uri).isEqualTo(uri2);
}
@Test
public void mergeActions_differentData_latterDataUsed() {
byte[] data1 = "data1".getBytes();
DownloadAction downloadAction1 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri1,
Collections.emptyList(),
/* customCacheKey= */ null,
/* data= */ data1);
byte[] data2 = "data2".getBytes();
DownloadAction downloadAction2 =
DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE,
uri1,
Collections.emptyList(),
/* customCacheKey= */ null,
/* data= */ data2);
ArrayDeque<DownloadAction> actionQueue = toActionQueue(downloadAction1, downloadAction2);
DownloadActionUtil.mergeActions(actionQueue);
DownloadAction mergedAction = DownloadActionUtil.mergeActions(actionQueue);
assertThat(mergedAction.data).isEqualTo(data2);
}
@Test
public void mergeActions_ifRemoveActionLast_returnsRemoveAction() {
DownloadAction downloadAction = createDownloadAction(uri1);
DownloadAction removeAction = createRemoveAction(uri1);
ArrayDeque<DownloadAction> actionQueue = toActionQueue(downloadAction, removeAction);
DownloadAction action = DownloadActionUtil.mergeActions(actionQueue);
assertThat(action).isEqualTo(removeAction);
assertThat(actionQueue).containsExactly(removeAction);
}
@Test
public void mergeActions_downloadActionAfterRemove_returnsRemoveKeepsDownload() {
DownloadAction removeAction = createRemoveAction(uri1);
DownloadAction downloadAction = createDownloadAction(uri1);
ArrayDeque<DownloadAction> actionQueue = toActionQueue(removeAction, downloadAction);
DownloadAction action = DownloadActionUtil.mergeActions(actionQueue);
assertThat(action).isEqualTo(removeAction);
assertThat(actionQueue).containsExactly(removeAction, downloadAction);
}
@Test
public void mergeActions_downloadActionsAfterRemove_returnsRemoveMergesDownloads() {
DownloadAction removeAction = createRemoveAction(uri1);
StreamKey streamKey1 = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0);
DownloadAction downloadAction1 =
createDownloadAction(uri1, Collections.singletonList(streamKey1));
StreamKey streamKey2 = new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 1);
DownloadAction downloadAction2 =
createDownloadAction(uri1, Collections.singletonList(streamKey2));
ArrayDeque<DownloadAction> actionQueue =
toActionQueue(removeAction, downloadAction1, downloadAction2);
DownloadAction mergedDownloadAction =
createDownloadAction(uri1, Arrays.asList(streamKey1, streamKey2));
DownloadAction action = DownloadActionUtil.mergeActions(actionQueue);
assertThat(action).isEqualTo(removeAction);
assertThat(actionQueue).containsExactly(removeAction, mergedDownloadAction);
}
@Test
public void mergeActions_actionBeforeRemove_ignoresActionBeforeRemove() {
DownloadAction removeAction = createRemoveAction(uri1);
StreamKey streamKey1 = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0);
DownloadAction downloadAction1 =
createDownloadAction(uri1, Collections.singletonList(streamKey1));
StreamKey streamKey2 = new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 1);
DownloadAction downloadAction2 =
createDownloadAction(uri1, Collections.singletonList(streamKey2));
StreamKey streamKey3 = new StreamKey(/* groupIndex= */ 2, /* trackIndex= */ 2);
DownloadAction downloadAction3 =
createDownloadAction(uri1, Collections.singletonList(streamKey3));
ArrayDeque<DownloadAction> actionQueue =
toActionQueue(downloadAction1, removeAction, downloadAction2, downloadAction3);
DownloadAction mergedDownloadAction =
createDownloadAction(uri1, Arrays.asList(streamKey2, streamKey3));
DownloadAction action = DownloadActionUtil.mergeActions(actionQueue);
assertThat(action).isEqualTo(removeAction);
assertThat(actionQueue).containsExactly(removeAction, mergedDownloadAction);
}
@Test
public void mergeActions_returnsMergedAction() {
StreamKey streamKey1 = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0);
StreamKey streamKey2 = new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 1);
StreamKey[] keys1 = new StreamKey[] {streamKey1};
StreamKey[] keys2 = new StreamKey[] {streamKey2};
StreamKey[] expectedKeys = new StreamKey[] {streamKey1, streamKey2};
doTestMergeActionsReturnsMergedKeys(keys1, keys2, expectedKeys);
}
@Test
public void mergeActions_returnsUniqueKeys() {
StreamKey streamKey1 = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0);
StreamKey streamKey1Copy = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0);
StreamKey streamKey2 = new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 1);
StreamKey[] keys1 = new StreamKey[] {streamKey1};
StreamKey[] keys2 = new StreamKey[] {streamKey2, streamKey1Copy};
StreamKey[] expectedKeys = new StreamKey[] {streamKey1, streamKey2};
doTestMergeActionsReturnsMergedKeys(keys1, keys2, expectedKeys);
}
@Test
public void mergeActions_ifFirstActionKeysEmpty_returnsEmptyKeys() {
StreamKey streamKey1 = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0);
StreamKey streamKey2 = new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 1);
StreamKey[] keys1 = new StreamKey[] {};
StreamKey[] keys2 = new StreamKey[] {streamKey2, streamKey1};
StreamKey[] expectedKeys = new StreamKey[] {};
doTestMergeActionsReturnsMergedKeys(keys1, keys2, expectedKeys);
}
@Test
public void mergeActions_ifNotFirstActionKeysEmpty_returnsEmptyKeys() {
StreamKey streamKey1 = new StreamKey(/* groupIndex= */ 0, /* trackIndex= */ 0);
StreamKey streamKey2 = new StreamKey(/* groupIndex= */ 1, /* trackIndex= */ 1);
StreamKey[] keys1 = new StreamKey[] {streamKey2, streamKey1};
StreamKey[] keys2 = new StreamKey[] {};
StreamKey[] expectedKeys = new StreamKey[] {};
doTestMergeActionsReturnsMergedKeys(keys1, keys2, expectedKeys);
}
private void doTestMergeActionsReturnsMergedKeys(
StreamKey[] keys1, StreamKey[] keys2, StreamKey[] expectedKeys) {
DownloadAction action1 = createDownloadAction(uri1, Arrays.asList(keys1));
DownloadAction action2 = createDownloadAction(uri1, Arrays.asList(keys2));
ArrayDeque<DownloadAction> actionQueue = toActionQueue(action1, action2);
DownloadAction mergedAction = DownloadActionUtil.mergeActions(actionQueue);
assertThat(mergedAction.type).isEqualTo(action1.type);
assertThat(mergedAction.uri).isEqualTo(action1.uri);
assertThat(mergedAction.customCacheKey).isEqualTo(action1.customCacheKey);
assertThat(mergedAction.isRemoveAction).isEqualTo(action1.isRemoveAction);
assertThat(mergedAction.keys).containsExactly((Object[]) expectedKeys);
assertThat(actionQueue).containsExactly(mergedAction);
}
private ArrayDeque<DownloadAction> toActionQueue(DownloadAction... actions) {
return new ArrayDeque<>(Arrays.asList(actions));
}
private static DownloadAction createDownloadAction(Uri uri) {
return createDownloadAction(uri, Collections.emptyList());
}
private static DownloadAction createDownloadAction(Uri uri, List<StreamKey> keys) {
return DownloadAction.createDownloadAction(
DownloadAction.TYPE_PROGRESSIVE, uri, keys, /* customCacheKey= */ null, /* data= */ null);
}
private static DownloadAction createRemoveAction(Uri uri) {
return DownloadAction.createRemoveAction(
DownloadAction.TYPE_PROGRESSIVE, uri, /* customCacheKey= */ null);
}
}

View File

@ -251,21 +251,6 @@ public class DownloadManagerTest {
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError(); downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
} }
@Test
public void secondSameDownloadActionIgnored() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1);
FakeDownloader downloader1 = runner.getDownloader(0);
runner.postDownloadAction();
downloader1.assertStarted();
runner.postDownloadAction();
downloader1.unblock().assertNotCanceled();
runner.getTask().assertCompleted();
runner.assertCreatedDownloaderCount(1);
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
@Test @Test
public void differentDownloadActionsMerged() throws Throwable { public void differentDownloadActionsMerged() throws Throwable {
DownloadRunner runner = new DownloadRunner(uri1); DownloadRunner runner = new DownloadRunner(uri1);

View File

@ -276,10 +276,16 @@ public class DownloadStateTest {
} }
static void assertEqual(DownloadState downloadState, DownloadState expected) { static void assertEqual(DownloadState downloadState, DownloadState expected) {
assertThat(areEqual(downloadState, expected)).isTrue(); assertEqual(downloadState, expected, false);
} }
private static boolean areEqual(DownloadState downloadState, DownloadState that) { static void assertEqual(
DownloadState downloadState, DownloadState expected, boolean compareTimeFields) {
assertThat(areEqual(downloadState, expected, compareTimeFields)).isTrue();
}
private static boolean areEqual(
DownloadState downloadState, DownloadState that, boolean compareTimeFields) {
if (downloadState.state != that.state) { if (downloadState.state != that.state) {
return false; return false;
} }
@ -292,12 +298,14 @@ public class DownloadStateTest {
if (downloadState.totalBytes != that.totalBytes) { if (downloadState.totalBytes != that.totalBytes) {
return false; return false;
} }
if (compareTimeFields) {
if (downloadState.startTimeMs != that.startTimeMs) { if (downloadState.startTimeMs != that.startTimeMs) {
return false; return false;
} }
if (downloadState.updateTimeMs != that.updateTimeMs) { if (downloadState.updateTimeMs != that.updateTimeMs) {
return false; return false;
} }
}
if (downloadState.failureReason != that.failureReason) { if (downloadState.failureReason != that.failureReason) {
return false; return false;
} }