Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
230a798f
authored
Dec 11, 2018
by
eguven
Committed by
Oliver Woodman
Dec 14, 2018
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Create only one task for all DownloadActions for the same content
PiperOrigin-RevId: 225060323
parent
05bfeca5
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
504 additions
and
392 deletions
RELEASENOTES.md
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java
library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java
testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java
RELEASENOTES.md
View file @
230a798f
...
...
@@ -25,6 +25,8 @@
skippping.
*
Workaround for MiTV (dangal) issue when swapping output surface
(
[
#5169
](
https://github.com/google/ExoPlayer/issues/5169
)
).
*
DownloadManager:
*
Create only one task for all DownloadActions for the same content.
### 2.9.2 ###
...
...
library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java
View file @
230a798f
...
...
@@ -15,7 +15,6 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
offline
;
import
static
com
.
google
.
android
.
exoplayer2
.
offline
.
DownloadManager
.
TaskState
.
STATE_CANCELED
;
import
static
com
.
google
.
android
.
exoplayer2
.
offline
.
DownloadManager
.
TaskState
.
STATE_COMPLETED
;
import
static
com
.
google
.
android
.
exoplayer2
.
offline
.
DownloadManager
.
TaskState
.
STATE_FAILED
;
import
static
com
.
google
.
android
.
exoplayer2
.
offline
.
DownloadManager
.
TaskState
.
STATE_QUEUED
;
...
...
@@ -35,6 +34,7 @@ import java.io.IOException;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.ArrayDeque
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.concurrent.CopyOnWriteArraySet
;
...
...
@@ -187,11 +187,13 @@ public final class DownloadManager {
}
/**
* Handles the given action. A task is created and added to the task queue. If it's a remove
* action then any download tasks for the same media are immediately canceled.
* Handles the given action.
*
* <p>A task is created and added to the task queue if there isn't one already for the same
* content.
*
* @param action The action to be executed.
* @return The id of the newly created task.
* @return The id of the newly created
or the existing
task.
*/
public
int
handleAction
(
DownloadAction
action
)
{
Assertions
.
checkState
(!
released
);
...
...
@@ -218,8 +220,10 @@ public final class DownloadManager {
public
int
getDownloadCount
()
{
int
count
=
0
;
for
(
int
i
=
0
;
i
<
tasks
.
size
();
i
++)
{
if
(!
tasks
.
get
(
i
).
action
.
isRemoveAction
)
{
count
++;
for
(
DownloadAction
action
:
tasks
.
get
(
i
).
actionQueue
)
{
if
(!
action
.
isRemoveAction
)
{
count
++;
}
}
}
return
count
;
...
...
@@ -287,6 +291,14 @@ public final class DownloadManager {
}
private
Task
addTaskForAction
(
DownloadAction
action
)
{
for
(
int
i
=
0
;
i
<
tasks
.
size
();
i
++)
{
Task
task
=
tasks
.
get
(
i
);
if
(
task
.
action
.
isSameMedia
(
action
))
{
task
.
addAction
(
action
);
logd
(
"Action is added to existing task"
,
task
);
return
task
;
}
}
Task
task
=
new
Task
(
nextTaskId
++,
this
,
downloaderFactory
,
action
,
minRetryCount
);
tasks
.
add
(
task
);
logd
(
"Task is added"
,
task
);
...
...
@@ -298,12 +310,8 @@ public final class DownloadManager {
*
* <ul>
* <li>It hasn't started yet.
* <li>There are no preceding conflicting tasks.
* <li>If it's a download task then there are no preceding download tasks on hold and the
* maximum number of active downloads hasn't been reached.
* <li>If it's a download task then the maximum number of active downloads hasn't been reached.
* </ul>
*
* If the task is a remove action then preceding conflicting tasks are canceled.
*/
private
void
maybeStartTasks
()
{
if
(!
initialized
||
released
)
{
...
...
@@ -317,31 +325,8 @@ public final class DownloadManager {
if
(!
task
.
canStart
())
{
continue
;
}
DownloadAction
action
=
task
.
action
;
boolean
isRemoveAction
=
action
.
isRemoveAction
;
if
(!
isRemoveAction
&&
skipDownloadActions
)
{
continue
;
}
boolean
canStartTask
=
true
;
for
(
int
j
=
0
;
j
<
i
;
j
++)
{
Task
otherTask
=
tasks
.
get
(
j
);
if
(
otherTask
.
action
.
isSameMedia
(
action
))
{
if
(
isRemoveAction
)
{
canStartTask
=
false
;
logd
(
task
+
" clashes with "
+
otherTask
);
otherTask
.
cancel
();
// Continue loop to cancel any other preceding clashing tasks.
}
else
if
(
otherTask
.
action
.
isRemoveAction
)
{
canStartTask
=
false
;
skipDownloadActions
=
true
;
break
;
}
}
}
if
(
canStartTask
)
{
boolean
isRemoveAction
=
task
.
action
.
isRemoveAction
;
if
(
isRemoveAction
||
!
skipDownloadActions
)
{
task
.
start
();
if
(!
isRemoveAction
)
{
activeDownloadTasks
.
add
(
task
);
...
...
@@ -436,14 +421,15 @@ public final class DownloadManager {
if
(
released
)
{
return
;
}
final
DownloadAction
[]
actions
=
new
DownloadAction
[
tasks
.
size
()]
;
ArrayList
<
DownloadAction
>
actions
=
new
ArrayList
<>(
tasks
.
size
())
;
for
(
int
i
=
0
;
i
<
tasks
.
size
();
i
++)
{
actions
[
i
]
=
tasks
.
get
(
i
).
action
;
actions
.
addAll
(
tasks
.
get
(
i
).
actionQueue
)
;
}
final
DownloadAction
[]
actionsArray
=
actions
.
toArray
(
new
DownloadAction
[
0
]);
fileIOHandler
.
post
(
()
->
{
try
{
actionFile
.
store
(
actions
);
actionFile
.
store
(
actions
Array
);
logd
(
"Actions persisted."
);
}
catch
(
IOException
e
)
{
Log
.
e
(
TAG
,
"Persisting actions failed."
,
e
);
...
...
@@ -465,20 +451,19 @@ public final class DownloadManager {
public
static
final
class
TaskState
{
/**
* Task states. One of {@link #STATE_QUEUED}, {@link #STATE_STARTED}, {@link #STATE_COMPLETED}
,
*
{@link #STATE_CANCELED}
or {@link #STATE_FAILED}.
* Task states. One of {@link #STATE_QUEUED}, {@link #STATE_STARTED}, {@link #STATE_COMPLETED}
* or {@link #STATE_FAILED}.
*
* <p>Transition diagram:
*
* <pre>
* ┌────────┬─────→ canceled
* queued ↔ started ┬→ completed
* └→ failed
* </pre>
*/
@Documented
@Retention
(
RetentionPolicy
.
SOURCE
)
@IntDef
({
STATE_QUEUED
,
STATE_STARTED
,
STATE_COMPLETED
,
STATE_
CANCELED
,
STATE_
FAILED
})
@IntDef
({
STATE_QUEUED
,
STATE_STARTED
,
STATE_COMPLETED
,
STATE_FAILED
})
public
@interface
State
{}
/** The task is waiting to be started. */
public
static
final
int
STATE_QUEUED
=
0
;
...
...
@@ -486,10 +471,8 @@ public final class DownloadManager {
public
static
final
int
STATE_STARTED
=
1
;
/** The task completed. */
public
static
final
int
STATE_COMPLETED
=
2
;
/** The task was canceled. */
public
static
final
int
STATE_CANCELED
=
3
;
/** The task failed. */
public
static
final
int
STATE_FAILED
=
4
;
public
static
final
int
STATE_FAILED
=
3
;
/** Returns the state string for the given state value. */
public
static
String
getStateString
(
@State
int
state
)
{
...
...
@@ -500,8 +483,6 @@ public final class DownloadManager {
return
"STARTED"
;
case
STATE_COMPLETED:
return
"COMPLETED"
;
case
STATE_CANCELED:
return
"CANCELED"
;
case
STATE_FAILED:
return
"FAILED"
;
default
:
...
...
@@ -553,14 +534,15 @@ public final class DownloadManager {
/** Target states for the download thread. */
@Documented
@Retention
(
RetentionPolicy
.
SOURCE
)
@IntDef
({
STATE_
COMPLETED
,
STATE_QUEUED
,
STATE_CANCEL
ED
})
@IntDef
({
STATE_
QUEUED
,
STATE_COMPLET
ED
})
public
@interface
TargetState
{}
private
final
int
id
;
private
final
DownloadManager
downloadManager
;
private
final
DownloaderFactory
downloaderFactory
;
private
final
DownloadAction
action
;
private
final
int
minRetryCount
;
private
final
ArrayDeque
<
DownloadAction
>
actionQueue
;
private
DownloadAction
action
;
/** The current state of the task. */
@TaskState
.
State
private
int
state
;
/**
...
...
@@ -586,6 +568,26 @@ public final class DownloadManager {
this
.
minRetryCount
=
minRetryCount
;
state
=
STATE_QUEUED
;
targetState
=
STATE_COMPLETED
;
actionQueue
=
new
ArrayDeque
<>();
actionQueue
.
add
(
action
);
}
public
void
addAction
(
DownloadAction
newAction
)
{
Assertions
.
checkState
(
action
.
type
.
equals
(
newAction
.
type
));
actionQueue
.
add
(
newAction
);
DownloadAction
updatedAction
=
DownloadActionUtil
.
mergeActions
(
actionQueue
);
if
(
action
.
equals
(
updatedAction
))
{
return
;
}
if
(
state
==
STATE_STARTED
)
{
if
(
targetState
==
STATE_COMPLETED
)
{
stopDownloadThread
();
}
}
else
{
Assertions
.
checkState
(
state
==
STATE_QUEUED
);
action
=
updatedAction
;
downloadManager
.
onTaskStateChange
(
this
);
}
}
public
TaskState
getTaskState
()
{
...
...
@@ -603,7 +605,7 @@ public final class DownloadManager {
/** Returns whether the task is finished. */
public
boolean
isFinished
()
{
return
state
==
STATE_FAILED
||
state
==
STATE_COMPLETED
||
state
==
STATE_CANCELED
;
return
state
==
STATE_FAILED
||
state
==
STATE_COMPLETED
;
}
/** Returns whether the task is started. */
...
...
@@ -629,46 +631,45 @@ public final class DownloadManager {
public
void
start
()
{
if
(
state
==
STATE_QUEUED
)
{
state
=
STATE_STARTED
;
action
=
actionQueue
.
peek
();
targetState
=
STATE_COMPLETED
;
downloadManager
.
onTaskStateChange
(
this
);
downloader
=
downloaderFactory
.
createDownloader
(
action
);
downloadThread
=
new
DownloadThread
(
this
,
downloader
,
action
.
isRemoveAction
,
minRetryCount
,
downloadManager
.
handler
);
}
}
public
void
cancel
()
{
if
(
state
==
STATE_STARTED
)
{
stopDownloadThread
(
STATE_CANCELED
);
}
else
if
(
state
==
STATE_QUEUED
)
{
state
=
STATE_CANCELED
;
downloadManager
.
handler
.
post
(()
->
downloadManager
.
onTaskStateChange
(
this
));
downloadManager
.
onTaskStateChange
(
this
);
}
}
public
void
stop
()
{
if
(
state
==
STATE_STARTED
&&
targetState
==
STATE_COMPLETED
)
{
stopDownloadThread
(
STATE_QUEUED
);
if
(
state
==
STATE_STARTED
)
{
stopDownloadThread
();
}
}
// Internal methods running on the main thread.
private
void
stopDownloadThread
(
@TargetState
int
targetState
)
{
this
.
targetState
=
targetState
;
private
void
stopDownloadThread
()
{
this
.
targetState
=
TaskState
.
STATE_QUEUED
;
Assertions
.
checkNotNull
(
downloadThread
).
cancel
();
}
private
void
onDownloadThreadStopped
(
@Nullable
Throwable
finalError
)
{
@TaskState
.
State
int
finalState
=
targetState
;
if
(
targetState
==
STATE_COMPLETED
&&
finalError
!=
null
)
{
finalState
=
STATE_FAILED
;
}
else
{
finalError
=
null
;
state
=
targetState
;
error
=
null
;
if
(
targetState
==
STATE_COMPLETED
)
{
if
(
finalError
!=
null
)
{
state
=
STATE_FAILED
;
error
=
finalError
;
}
else
{
actionQueue
.
remove
();
if
(!
actionQueue
.
isEmpty
())
{
// Don't continue running. Wait to be restarted by maybeStartTasks().
state
=
STATE_QUEUED
;
action
=
actionQueue
.
peek
();
}
}
}
state
=
finalState
;
error
=
finalError
;
downloadManager
.
onTaskStateChange
(
this
);
}
}
...
...
library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java
View file @
230a798f
...
...
@@ -28,10 +28,12 @@ import com.google.android.exoplayer2.testutil.TestDownloadManagerListener;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.Collections
;
import
java.util.IdentityHashMap
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.concurrent.CountDownLatch
;
import
java.util.concurrent.TimeUnit
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Test
;
...
...
@@ -52,7 +54,9 @@ public class DownloadManagerTest {
private
static
final
int
ASSERT_FALSE_TIME
=
1000
;
/* Maximum retry delay in DownloadManager. */
private
static
final
int
MAX_RETRY_DELAY
=
5000
;
/* Maximum number of times a downloader can be restarted before doing a released check. */
private
static
final
int
MAX_STARTS_BEFORE_RELEASED
=
1
;
/** The minimum number of times a task must be retried before failing. */
private
static
final
int
MIN_RETRY_COUNT
=
3
;
private
Uri
uri1
;
...
...
@@ -84,309 +88,329 @@ public class DownloadManagerTest {
}
@Test
public
void
testDownloadActionRuns
()
throws
Throwable
{
doTestDownloaderRuns
(
createDownloadRunner
(
uri1
));
public
void
downloadRunner_multipleInstancePerContent_throwsException
()
{
boolean
exceptionThrown
=
false
;
try
{
new
DownloadRunner
(
uri1
);
new
DownloadRunner
(
uri1
);
// can't put fail() here as it would be caught in the catch below.
}
catch
(
Throwable
e
)
{
exceptionThrown
=
true
;
}
assertThat
(
exceptionThrown
).
isTrue
();
}
@Test
public
void
testRemoveActionRuns
()
throws
Throwable
{
doTestDownloaderRuns
(
createRemoveRunner
(
uri1
));
public
void
downloadRunner_handleActionReturnsDifferentTaskId_throwsException
()
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
).
postDownloadAction
();
TaskWrapper
task
=
runner
.
getTask
();
runner
.
setTask
(
new
TaskWrapper
(
task
.
taskId
+
10000
));
boolean
exceptionThrown
=
false
;
try
{
runner
.
postDownloadAction
();
// can't put fail() here as it would be caught in the catch below.
}
catch
(
Throwable
e
)
{
exceptionThrown
=
true
;
}
assertThat
(
exceptionThrown
).
isTrue
();
}
@Test
public
void
testDownloadRetriesThenFails
()
throws
Throwable
{
DownloadRunner
downloadRunner
=
createDownloadRunner
(
uri1
);
downloadRunner
.
postAction
();
FakeDownloader
fakeDownloader
=
downloadRunner
.
downloader
;
fakeDownloader
.
enableDownloadIOException
=
true
;
for
(
int
i
=
0
;
i
<=
MIN_RETRY_COUNT
;
i
++)
{
fakeDownloader
.
assertStarted
(
MAX_RETRY_DELAY
).
unblock
();
}
downloadRunner
.
assertFailed
();
downloadManagerListener
.
clearDownloadError
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
public
void
multipleActionsForTheSameContent_executedOnTheSameTask
()
{
// Two download actions on first task
new
DownloadRunner
(
uri1
).
postDownloadAction
().
postDownloadAction
();
// One download, one remove actions on second task
new
DownloadRunner
(
uri2
).
postDownloadAction
().
postRemoveAction
();
// Two remove actions on third task
new
DownloadRunner
(
uri3
).
postRemoveAction
().
postRemoveAction
();
}
@Test
public
void
testDownloadNoRetryWhenCanceled
()
throws
Throwable
{
DownloadRunner
downloadRunner
=
createDownloadRunner
(
uri1
).
ignoreInterrupts
();
downloadRunner
.
downloader
.
enableDownloadIOException
=
true
;
downloadRunner
.
postAction
().
assertStarted
();
public
void
actionsForDifferentContent_executedOnDifferentTasks
()
{
TaskWrapper
task1
=
new
DownloadRunner
(
uri1
).
postDownloadAction
().
getTask
();
TaskWrapper
task2
=
new
DownloadRunner
(
uri2
).
postDownloadAction
().
getTask
()
;
TaskWrapper
task3
=
new
DownloadRunner
(
uri3
).
postRemoveAction
().
getTask
();
DownloadRunner
removeRunner
=
createRemoveRunner
(
uri1
).
postAction
();
assertThat
(
task1
).
isNoneOf
(
task2
,
task3
);
assertThat
(
task2
).
isNotEqualTo
(
task3
);
}
downloadRunner
.
unblock
().
assertCanceled
();
removeRunner
.
unblock
();
@Test
public
void
postDownloadAction_downloads
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
TaskWrapper
task
=
runner
.
postDownloadAction
().
getTask
();
task
.
assertStarted
();
runner
.
getDownloader
(
0
).
unblock
().
assertReleased
().
assertStartCount
(
1
);
task
.
assertCompleted
();
runner
.
assertCreatedDownloaderCount
(
1
);
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
postRemoveAction_removes
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
TaskWrapper
task
=
runner
.
postRemoveAction
().
getTask
();
task
.
assertStarted
();
runner
.
getDownloader
(
0
).
unblock
().
assertReleased
().
assertStartCount
(
1
);
task
.
assertCompleted
();
runner
.
assertCreatedDownloaderCount
(
1
);
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
testDownloadRetriesThenContinue
s
()
throws
Throwable
{
DownloadRunner
downloadRunner
=
create
DownloadRunner
(
uri1
);
downloadRunner
.
post
Action
();
FakeDownloader
fakeDownloader
=
downloadRunner
.
downloader
;
fakeDownloader
.
enableDownloadIOException
=
true
;
public
void
downloadFails_retriesThenTaskFail
s
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
runner
.
postDownload
Action
();
FakeDownloader
downloader
=
runner
.
getDownloader
(
0
)
;
for
(
int
i
=
0
;
i
<=
MIN_RETRY_COUNT
;
i
++)
{
fakeDownloader
.
assertStarted
(
MAX_RETRY_DELAY
);
if
(
i
==
MIN_RETRY_COUNT
)
{
fakeDownloader
.
enableDownloadIOException
=
false
;
}
fakeDownloader
.
unblock
();
downloader
.
assertStarted
(
MAX_RETRY_DELAY
).
fail
();
}
downloadRunner
.
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
downloader
.
assertReleased
().
assertStartCount
(
MIN_RETRY_COUNT
+
1
);
runner
.
getTask
().
assertFailed
();
downloadManagerListener
.
blockUntilTasksComplete
();
}
@Test
@SuppressWarnings
({
"NonAtomicVolatileUpdate"
,
"NonAtomicOperationOnVolatileField"
})
public
void
testDownloadRetryCountResetsOnProgress
()
throws
Throwable
{
DownloadRunner
downloadRunner
=
createDownloadRunner
(
uri1
);
downloadRunner
.
postAction
();
FakeDownloader
fakeDownloader
=
downloadRunner
.
downloader
;
fakeDownloader
.
enableDownloadIOException
=
true
;
fakeDownloader
.
downloadedBytes
=
0
;
for
(
int
i
=
0
;
i
<=
MIN_RETRY_COUNT
+
10
;
i
++)
{
fakeDownloader
.
assertStarted
(
MAX_RETRY_DELAY
);
fakeDownloader
.
downloadedBytes
++;
if
(
i
==
MIN_RETRY_COUNT
+
10
)
{
fakeDownloader
.
enableDownloadIOException
=
false
;
}
fakeDownloader
.
unblock
();
public
void
downloadFails_retries
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
runner
.
postDownloadAction
();
FakeDownloader
downloader
=
runner
.
getDownloader
(
0
);
for
(
int
i
=
0
;
i
<
MIN_RETRY_COUNT
;
i
++)
{
downloader
.
assertStarted
(
MAX_RETRY_DELAY
).
fail
();
}
download
Runner
.
assertCompleted
();
download
er
.
assertStarted
(
MAX_RETRY_DELAY
).
unblock
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
downloader
.
assertReleased
().
assertStartCount
(
MIN_RETRY_COUNT
+
1
);
runner
.
getTask
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksComplete
();
}
@Test
public
void
testDifferentMediaDownloadActionsStartInParallel
()
throws
Throwable
{
doTestDownloadersRunInParallel
(
createDownloadRunner
(
uri1
),
createDownloadRunner
(
uri2
));
}
public
void
downloadProgressOnRetry_retryCountResets
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
runner
.
postDownloadAction
();
FakeDownloader
downloader
=
runner
.
getDownloader
(
0
);
int
tooManyRetries
=
MIN_RETRY_COUNT
+
10
;
for
(
int
i
=
0
;
i
<
tooManyRetries
;
i
++)
{
downloader
.
increaseDownloadedByteCount
();
downloader
.
assertStarted
(
MAX_RETRY_DELAY
).
fail
();
}
downloader
.
assertStarted
(
MAX_RETRY_DELAY
).
unblock
();
@Test
public
void
testDifferentMediaDifferentActionsStartInParallel
()
throws
Throwable
{
do
TestDownloadersRunInParallel
(
createDownloadRunner
(
uri1
),
createRemoveRunner
(
uri2
)
);
downloader
.
assertReleased
().
assertStartCount
(
tooManyRetries
+
1
);
runner
.
getTask
().
assertCompleted
();
do
wnloadManagerListener
.
blockUntilTasksComplete
(
);
}
@Test
public
void
testSameMediaDownloadActionsStartInParallel
()
throws
Throwable
{
doTestDownloadersRunInParallel
(
createDownloadRunner
(
uri1
),
createDownloadRunner
(
uri1
)
);
}
public
void
removeCancelsDownload
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
FakeDownloader
downloader1
=
runner
.
getDownloader
(
0
);
@Test
public
void
testSameMediaRemoveActionWaitsDownloadAction
()
throws
Throwable
{
doTestDownloadersRunSequentially
(
createDownloadRunner
(
uri1
),
createRemoveRunner
(
uri1
));
}
runner
.
postDownloadAction
();
downloader1
.
assertStarted
();
runner
.
postRemoveAction
();
@Test
public
void
testSameMediaDownloadActionWaitsRemoveAction
()
throws
Throwable
{
do
TestDownloadersRunSequentially
(
createRemoveRunner
(
uri1
),
createDownloadRunner
(
uri1
)
);
downloader1
.
assertCanceled
().
assertStartCount
(
1
);
runner
.
getDownloader
(
1
).
unblock
().
assertNotCanceled
();
do
wnloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
(
);
}
@Test
public
void
testSameMediaRemoveActionWaitsRemoveAction
()
throws
Throwable
{
doTestDownloadersRunSequentially
(
createRemoveRunner
(
uri1
),
createRemoveRunner
(
uri1
)
);
}
public
void
downloadNotCancelRemove
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
FakeDownloader
downloader1
=
runner
.
getDownloader
(
0
);
@Test
public
void
testSameMediaMultipleActions
()
throws
Throwable
{
DownloadRunner
downloadAction1
=
createDownloadRunner
(
uri1
).
ignoreInterrupts
();
DownloadRunner
downloadAction2
=
createDownloadRunner
(
uri1
).
ignoreInterrupts
();
DownloadRunner
removeAction1
=
createRemoveRunner
(
uri1
);
DownloadRunner
downloadAction3
=
createDownloadRunner
(
uri1
);
DownloadRunner
removeAction2
=
createRemoveRunner
(
uri1
);
// Two download actions run in parallel.
downloadAction1
.
postAction
().
assertStarted
();
downloadAction2
.
postAction
().
assertStarted
();
// removeAction1 is added. It interrupts the two download actions' threads but they are
// configured to ignore it so removeAction1 doesn't start.
removeAction1
.
postAction
().
assertDoesNotStart
();
// downloadAction2 finishes but it isn't enough to start removeAction1.
downloadAction2
.
unblock
().
assertCanceled
();
removeAction1
.
assertDoesNotStart
();
// downloadAction3 is postAction to DownloadManager but it waits for removeAction1 to finish.
downloadAction3
.
postAction
().
assertDoesNotStart
();
// When downloadAction1 finishes, removeAction1 starts.
downloadAction1
.
unblock
().
assertCanceled
();
removeAction1
.
assertStarted
();
// downloadAction3 still waits removeAction1
downloadAction3
.
assertDoesNotStart
();
// removeAction2 is posted. removeAction1 and downloadAction3 is canceled so removeAction2
// starts immediately.
removeAction2
.
postAction
();
removeAction1
.
assertCanceled
();
downloadAction3
.
assertCanceled
();
removeAction2
.
assertStarted
().
unblock
().
assertCompleted
();
runner
.
postRemoveAction
();
downloader1
.
assertStarted
();
runner
.
postDownloadAction
();
downloader1
.
unblock
().
assertNotCanceled
();
runner
.
getDownloader
(
1
).
unblock
().
assertNotCanceled
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
testMultipleRemoveActionWaitsLastCancelsAllOther
()
throws
Throwable
{
DownloadRunner
removeAction1
=
createRemoveRunner
(
uri1
).
ignoreInterrupts
();
DownloadRunner
removeAction2
=
createRemoveRunner
(
uri1
);
DownloadRunner
removeAction3
=
createRemoveRunner
(
uri1
);
removeAction1
.
postAction
().
assertStarted
();
removeAction2
.
postAction
().
assertDoesNotStart
();
removeAction3
.
postAction
().
assertDoesNotStart
();
public
void
secondSameRemoveActionIgnored
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
FakeDownloader
downloader1
=
runner
.
getDownloader
(
0
);
removeAction2
.
assertCanceled
();
removeAction1
.
unblock
().
assertCanceled
();
removeAction3
.
assertStarted
().
unblock
().
assertCompleted
();
runner
.
postRemoveAction
();
downloader1
.
assertStarted
();
runner
.
postRemoveAction
();
downloader1
.
unblock
().
assertNotCanceled
();
runner
.
getTask
().
assertCompleted
();
runner
.
assertCreatedDownloaderCount
(
1
);
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
testGetTasks
()
throws
Throwable
{
DownloadRunner
removeAction
=
createRemoveRunner
(
uri1
);
DownloadRunner
downloadAction1
=
createDownloadRunner
(
uri1
);
DownloadRunner
downloadAction2
=
createDownloadRunner
(
uri1
);
public
void
secondSameDownloadActionIgnored
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
FakeDownloader
downloader1
=
runner
.
getDownloader
(
0
);
r
emoveAction
.
postAction
().
assertStarted
();
download
Action1
.
postAction
().
assertDoesNotStart
();
downloadAction2
.
postAction
().
assertDoesNotStart
();
r
unner
.
postDownloadAction
();
download
er1
.
assertStarted
();
runner
.
postDownloadAction
();
TaskState
[]
states
=
downloadManager
.
getAllTaskStates
();
assertThat
(
states
).
hasLength
(
3
);
assertThat
(
states
[
0
].
action
).
isEqualTo
(
removeAction
.
action
);
assertThat
(
states
[
1
].
action
).
isEqualTo
(
downloadAction1
.
action
);
assertThat
(
states
[
2
].
action
).
isEqualTo
(
downloadAction2
.
action
);
downloader1
.
unblock
().
assertNotCanceled
();
runner
.
getTask
().
assertCompleted
();
runner
.
assertCreatedDownloaderCount
(
1
);
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
testMultipleWaitingDownloadActionStartsInParallel
()
throws
Throwable
{
DownloadRunner
removeAction
=
createRemoveRunner
(
uri1
);
DownloadRunner
downloadAction1
=
createDownloadRunner
(
uri1
);
DownloadRunner
downloadAction2
=
createDownloadRunner
(
uri1
);
public
void
differentDownloadActionsMerged
()
throws
Throwable
{
DownloadRunner
runner
=
new
DownloadRunner
(
uri1
);
FakeDownloader
downloader1
=
runner
.
getDownloader
(
0
);
StreamKey
streamKey1
=
new
StreamKey
(
/* groupIndex= */
0
,
/* trackIndex= */
0
);
StreamKey
streamKey2
=
new
StreamKey
(
/* groupIndex= */
1
,
/* trackIndex= */
1
);
r
emoveAction
.
postAction
().
assertStarted
(
);
download
Action1
.
postAction
().
assertDoesNotStart
();
downloadAction2
.
postAction
().
assertDoesNotStart
(
);
r
unner
.
postDownloadAction
(
streamKey1
);
download
er1
.
assertStarted
();
runner
.
postDownloadAction
(
streamKey2
);
removeAction
.
unblock
().
assertCompleted
();
downloadAction1
.
assertStarted
();
downloadAction2
.
assertStarted
();
downloadAction1
.
unblock
().
assertCompleted
();
downloadAction2
.
unblock
().
assertCompleted
();
downloader1
.
unblock
().
assertCanceled
();
FakeDownloader
downloader2
=
runner
.
getDownloader
(
1
);
downloader2
.
assertStarted
();
assertThat
(
downloader2
.
action
.
keys
).
containsExactly
(
streamKey1
,
streamKey2
);
downloader2
.
unblock
();
runner
.
getTask
().
assertCompleted
();
runner
.
assertCreatedDownloaderCount
(
2
);
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
testDifferentMediaDownloadActionsPreserveOrder
()
throws
Throwable
{
DownloadRunner
removeRunner
=
createRemoveRunner
(
uri1
).
ignoreInterrupts
();
DownloadRunner
downloadRunner1
=
createDownloadRunner
(
uri1
);
DownloadRunner
downloadRunner2
=
createDownloadRunner
(
uri2
);
removeRunner
.
postAction
().
assertStarted
();
downloadRunner1
.
postAction
().
assertDoesNotStart
();
downloadRunner2
.
postAction
().
assertDoesNotStart
();
removeRunner
.
unblock
().
assertCompleted
();
downloadRunner1
.
assertStarted
();
downloadRunner2
.
assertStarted
();
downloadRunner1
.
unblock
().
assertCompleted
();
downloadRunner2
.
unblock
().
assertCompleted
();
public
void
actionsForDifferentContent_executedInParallel
()
throws
Throwable
{
DownloadRunner
runner1
=
new
DownloadRunner
(
uri1
).
postDownloadAction
();
DownloadRunner
runner2
=
new
DownloadRunner
(
uri2
).
postDownloadAction
();
FakeDownloader
downloader1
=
runner1
.
getDownloader
(
0
);
FakeDownloader
downloader2
=
runner2
.
getDownloader
(
0
);
downloader1
.
assertStarted
();
downloader2
.
assertStarted
();
downloader1
.
unblock
();
downloader2
.
unblock
();
runner1
.
getTask
().
assertCompleted
();
runner2
.
getTask
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
actionsForDifferentContent_ifMaxDownloadIs1_executedSequentially
()
throws
Throwable
{
setUpDownloadManager
(
1
);
DownloadRunner
runner1
=
new
DownloadRunner
(
uri1
).
postDownloadAction
();
DownloadRunner
runner2
=
new
DownloadRunner
(
uri2
).
postDownloadAction
();
FakeDownloader
downloader1
=
runner1
.
getDownloader
(
0
);
FakeDownloader
downloader2
=
runner2
.
getDownloader
(
0
);
downloader1
.
assertStarted
();
downloader2
.
assertDoesNotStart
();
downloader1
.
unblock
();
downloader2
.
assertStarted
();
downloader2
.
unblock
();
runner1
.
getTask
().
assertCompleted
();
runner2
.
getTask
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
testDifferentMediaRemoveActionsDoNotPreserveOrder
()
throws
Throwable
{
DownloadRunner
downloadRunner
=
createDownloadRunner
(
uri1
).
ignoreInterrupts
();
DownloadRunner
removeRunner1
=
createRemoveRunner
(
uri1
);
DownloadRunner
removeRunner2
=
createRemoveRunner
(
uri2
);
public
void
removeActionForDifferentContent_ifMaxDownloadIs1_executedInParallel
()
throws
Throwable
{
setUpDownloadManager
(
1
);
DownloadRunner
runner1
=
new
DownloadRunner
(
uri1
).
postDownloadAction
();
DownloadRunner
runner2
=
new
DownloadRunner
(
uri2
).
postRemoveAction
();
FakeDownloader
downloader1
=
runner1
.
getDownloader
(
0
);
FakeDownloader
downloader2
=
runner2
.
getDownloader
(
0
);
downloader1
.
assertStarted
();
downloader2
.
assertStarted
();
downloader1
.
unblock
();
downloader2
.
unblock
();
runner1
.
getTask
().
assertCompleted
();
runner2
.
getTask
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
downloadRunner
.
postAction
().
assertStarted
();
removeRunner1
.
postAction
().
assertDoesNotStart
();
removeRunner2
.
postAction
().
assertStarted
();
@Test
public
void
downloadActionFollowingRemove_ifMaxDownloadIs1_isNotStarted
()
throws
Throwable
{
setUpDownloadManager
(
1
);
DownloadRunner
runner1
=
new
DownloadRunner
(
uri1
).
postDownloadAction
();
DownloadRunner
runner2
=
new
DownloadRunner
(
uri2
).
postRemoveAction
().
postDownloadAction
();
FakeDownloader
downloader1
=
runner1
.
getDownloader
(
0
);
FakeDownloader
downloader2
=
runner2
.
getDownloader
(
0
);
FakeDownloader
downloader3
=
runner2
.
getDownloader
(
1
);
downloader1
.
assertStarted
();
downloader2
.
assertStarted
();
downloader2
.
unblock
();
downloader3
.
assertDoesNotStart
();
downloader1
.
unblock
();
downloader3
.
assertStarted
();
downloader3
.
unblock
();
runner1
.
getTask
().
assertCompleted
();
runner2
.
getTask
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
downloadRunner
.
unblock
().
assertCanceled
();
removeRunner2
.
unblock
().
assertCompleted
();
@Test
public
void
getTasks_returnTasks
()
{
TaskWrapper
task1
=
new
DownloadRunner
(
uri1
).
postDownloadAction
().
getTask
();
TaskWrapper
task2
=
new
DownloadRunner
(
uri2
).
postDownloadAction
().
getTask
();
TaskWrapper
task3
=
new
DownloadRunner
(
uri3
).
postRemoveAction
().
getTask
();
removeRunner1
.
assertStarted
();
removeRunner1
.
unblock
().
assertCompleted
();
TaskState
[]
states
=
downloadManager
.
getAllTaskStates
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
assertThat
(
states
).
hasLength
(
3
);
int
[]
taskIds
=
{
task1
.
taskId
,
task2
.
taskId
,
task3
.
taskId
};
int
[]
stateTaskIds
=
{
states
[
0
].
taskId
,
states
[
1
].
taskId
,
states
[
2
].
taskId
};
assertThat
(
stateTaskIds
).
isEqualTo
(
taskIds
);
}
@Test
public
void
testStopAndResume
()
throws
Throwable
{
DownloadRunner
download1Runner
=
createDownloadRunner
(
uri1
);
DownloadRunner
remove2Runner
=
createRemoveRunner
(
uri2
);
DownloadRunner
download2Runner
=
createDownloadRunner
(
uri2
);
DownloadRunner
remove1Runner
=
createRemoveRunner
(
uri1
);
DownloadRunner
download3Runner
=
createDownloadRunner
(
uri3
);
public
void
stopAndResume
()
throws
Throwable
{
DownloadRunner
runner1
=
new
DownloadRunner
(
uri1
);
DownloadRunner
runner2
=
new
DownloadRunner
(
uri2
);
DownloadRunner
runner3
=
new
DownloadRunner
(
uri3
);
download1Runner
.
postAction
().
assertStarted
();
r
emove2Runner
.
postAction
().
assertStarted
();
download2Runner
.
postAction
().
assertDoesNotStart
();
runner1
.
postDownloadAction
().
getTask
().
assertStarted
();
r
unner2
.
postRemoveAction
().
getTask
().
assertStarted
();
runner2
.
postDownloadAction
();
runOnMainThread
(()
->
downloadManager
.
stopDownloads
());
download1Runner
.
assertStopp
ed
();
runner1
.
getTask
().
assertQueu
ed
();
// remove actions aren't stopped.
remove2Runner
.
unblock
().
assertCompleted
();
runner2
.
getDownloader
(
0
).
unblock
().
assertReleased
();
runner2
.
getTask
().
assertQueued
();
// Although remove2 is finished, download2 doesn't start.
download2Runner
.
assertDoesNotStart
();
runner2
.
getDownloader
(
1
)
.
assertDoesNotStart
();
// When a new remove action is added, it cancels stopped download actions with the same media.
r
emove1Runner
.
post
Action
();
download1Runner
.
assertCanceled
();
r
emove1Runner
.
assertStarted
().
unbloc
k
().
assertCompleted
();
r
unner1
.
postRemove
Action
();
runner1
.
getDownloader
(
1
).
assertStarted
().
unblock
();
r
unner1
.
getTas
k
().
assertCompleted
();
// New download actions can be added but they don't start.
download3Runner
.
postAction
().
assertDoesNotStart
();
runOnMainThread
(()
->
downloadManager
.
startDownloads
());
download2Runner
.
assertStarted
().
unblock
().
assertCompleted
();
download3Runner
.
assertStarted
().
unblock
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
@Test
public
void
testResumeBeforeTotallyStopped
()
throws
Throwable
{
setUpDownloadManager
(
2
);
DownloadRunner
download1Runner
=
createDownloadRunner
(
uri1
).
ignoreInterrupts
();
DownloadRunner
download2Runner
=
createDownloadRunner
(
uri2
);
DownloadRunner
download3Runner
=
createDownloadRunner
(
uri3
);
download1Runner
.
postAction
().
assertStarted
();
download2Runner
.
postAction
().
assertStarted
();
// download3 doesn't start as DM was configured to run two downloads in parallel.
download3Runner
.
postAction
().
assertDoesNotStart
();
runOnMainThread
(()
->
downloadManager
.
stopDownloads
());
// download1 doesn't stop yet as it ignores interrupts.
download2Runner
.
assertStopped
();
runner3
.
postDownloadAction
().
getDownloader
(
0
).
assertDoesNotStart
();
runOnMainThread
(()
->
downloadManager
.
startDownloads
());
// download2 starts immediately.
download2Runner
.
assertStarted
();
// download3 doesn't start as download1 still holds its slot.
download3Runner
.
assertDoesNotStart
();
// when unblocked download1 stops and starts immediately.
download1Runner
.
unblock
().
assertStopped
().
assertStarted
();
download1Runner
.
unblock
();
download2Runner
.
unblock
();
download3Runner
.
unblock
();
runner2
.
getDownloader
(
1
).
assertStarted
().
unblock
();
runner3
.
getDownloader
(
0
).
assertStarted
().
unblock
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
...
...
@@ -419,101 +443,105 @@ public class DownloadManagerTest {
}
}
private
void
doTestDownloaderRuns
(
DownloadRunner
runner
)
throws
Throwable
{
runner
.
postAction
().
assertStarted
().
unblock
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
private
void
runOnMainThread
(
final
Runnable
r
)
{
dummyMainThread
.
runOnMainThread
(
r
);
}
private
void
doTestDownloadersRunSequentially
(
DownloadRunner
runner1
,
DownloadRunner
runner2
)
throws
Throwable
{
runner1
.
ignoreInterrupts
().
postAction
().
assertStarted
();
runner2
.
postAction
().
assertDoesNotStart
();
private
final
class
DownloadRunner
{
runner1
.
unblock
();
runner2
.
assertStarted
();
private
final
Uri
uri
;
private
final
ArrayList
<
FakeDownloader
>
downloaders
;
private
int
createdDownloaderCount
=
0
;
private
FakeDownloader
downloader
;
private
TaskWrapper
taskWrapper
;
runner2
.
unblock
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
private
DownloadRunner
(
Uri
uri
)
{
this
.
uri
=
uri
;
downloaders
=
new
ArrayList
<>();
downloader
=
addDownloader
();
downloaderFactory
.
registerDownloadRunner
(
this
);
}
private
void
doTestDownloadersRunInParallel
(
DownloadRunner
runner1
,
DownloadRunner
runner2
)
throws
Throwable
{
runner1
.
postAction
().
assertStarted
();
runner2
.
postAction
().
assertStarted
();
runner1
.
unblock
().
assertCompleted
();
runner2
.
unblock
().
assertCompleted
();
downloadManagerListener
.
blockUntilTasksCompleteAndThrowAnyDownloadError
();
}
private
DownloadRunner
postRemoveAction
()
{
return
postAction
(
createRemoveAction
(
uri
));
}
private
DownloadRunner
createDownloadRunner
(
Uri
uri
)
{
return
new
DownloadRunner
(
uri
,
/* isRemoveAction= */
false
);
}
private
DownloadRunner
postDownloadAction
(
StreamKey
...
keys
)
{
return
postAction
(
createDownloadAction
(
uri
,
keys
)
);
}
private
DownloadRunner
createRemoveRunner
(
Uri
uri
)
{
return
new
DownloadRunner
(
uri
,
/* isRemoveAction= */
true
);
}
private
DownloadRunner
postAction
(
DownloadAction
action
)
{
AtomicInteger
taskIdHolder
=
new
AtomicInteger
();
runOnMainThread
(()
->
taskIdHolder
.
set
(
downloadManager
.
handleAction
(
action
)));
int
taskId
=
taskIdHolder
.
get
();
if
(
taskWrapper
==
null
)
{
taskWrapper
=
new
TaskWrapper
(
taskId
);
}
else
{
assertThat
(
taskId
).
isEqualTo
(
taskWrapper
.
taskId
);
}
return
this
;
}
private
void
runOnMainThread
(
final
Runnable
r
)
{
dummyMainThread
.
runOnMainThread
(
r
);
}
private
synchronized
FakeDownloader
addDownloader
()
{
FakeDownloader
fakeDownloader
=
new
FakeDownloader
();
downloaders
.
add
(
fakeDownloader
);
return
fakeDownloader
;
}
private
class
DownloadRunner
{
private
synchronized
FakeDownloader
getDownloader
(
int
index
)
{
while
(
downloaders
.
size
()
<=
index
)
{
addDownloader
();
}
return
downloaders
.
get
(
index
);
}
public
final
DownloadAction
action
;
public
final
FakeDownloader
downloader
;
private
synchronized
Downloader
createDownloader
(
DownloadAction
action
)
{
downloader
=
getDownloader
(
createdDownloaderCount
++);
downloader
.
action
=
action
;
return
downloader
;
}
private
DownloadRunner
(
Uri
uri
,
boolean
isRemoveAction
)
{
action
=
isRemoveAction
?
DownloadAction
.
createRemoveAction
(
DownloadAction
.
TYPE_PROGRESSIVE
,
uri
,
/* customCacheKey= */
null
)
:
DownloadAction
.
createDownloadAction
(
DownloadAction
.
TYPE_PROGRESSIVE
,
uri
,
/* keys= */
Collections
.
emptyList
(),
/* customCacheKey= */
null
,
/* data= */
null
);
downloader
=
new
FakeDownloader
(
isRemoveAction
);
downloaderFactory
.
putFakeDownloader
(
action
,
downloader
);
private
TaskWrapper
getTask
()
{
return
taskWrapper
;
}
private
DownloadRunner
postAction
()
{
runOnMainThread
(()
->
downloadManager
.
handleAction
(
action
));
return
this
;
public
void
setTask
(
TaskWrapper
taskWrapper
)
{
this
.
taskWrapper
=
taskWrapper
;
}
private
DownloadRunner
assertDoesNotStart
()
throws
InterruptedException
{
Thread
.
sleep
(
ASSERT_FALSE_TIME
);
assertThat
(
downloader
.
started
.
getCount
()).
isEqualTo
(
1
);
return
this
;
private
void
assertCreatedDownloaderCount
(
int
count
)
{
assertThat
(
createdDownloaderCount
).
isEqualTo
(
count
);
}
}
private
final
class
TaskWrapper
{
private
final
int
taskId
;
private
TaskWrapper
(
int
taskId
)
{
this
.
taskId
=
taskId
;
}
private
DownloadRunner
assertStarted
()
throws
InterruptedException
{
downloader
.
assertStarted
(
ASSERT_TRUE_TIMEOUT
);
private
TaskWrapper
assertStarted
()
throws
InterruptedException
{
return
assertState
(
TaskState
.
STATE_STARTED
);
}
private
DownloadRunn
er
assertCompleted
()
{
private
TaskWrapp
er
assertCompleted
()
{
return
assertState
(
TaskState
.
STATE_COMPLETED
);
}
private
DownloadRunn
er
assertFailed
()
{
private
TaskWrapp
er
assertFailed
()
{
return
assertState
(
TaskState
.
STATE_FAILED
);
}
private
DownloadRunner
assertCanceled
()
{
return
assertState
(
TaskState
.
STATE_CANCELED
);
}
private
DownloadRunner
assertStopped
()
{
private
TaskWrapper
assertQueued
()
{
return
assertState
(
TaskState
.
STATE_QUEUED
);
}
private
DownloadRunn
er
assertState
(
@State
int
expectedState
)
{
private
TaskWrapp
er
assertState
(
@State
int
expectedState
)
{
while
(
true
)
{
Integer
state
=
null
;
try
{
state
=
downloadManagerListener
.
pollStateChange
(
action
,
ASSERT_TRUE_TIMEOUT
);
state
=
downloadManagerListener
.
pollStateChange
(
taskId
,
ASSERT_TRUE_TIMEOUT
);
}
catch
(
InterruptedException
e
)
{
fail
(
e
.
getMessage
());
}
...
...
@@ -523,69 +551,98 @@ public class DownloadManagerTest {
}
}
private
DownloadRunner
unblock
()
{
downloader
.
unblock
();
return
this
;
@Override
public
boolean
equals
(
Object
o
)
{
if
(
this
==
o
)
{
return
true
;
}
if
(
o
==
null
||
getClass
()
!=
o
.
getClass
())
{
return
false
;
}
return
taskId
==
((
TaskWrapper
)
o
).
taskId
;
}
private
DownloadRunner
ignoreInterrupts
()
{
downloader
.
ignoreInterrupts
=
true
;
return
t
his
;
@Override
public
int
hashCode
()
{
return
t
askId
;
}
}
private
static
class
FakeDownloaderFactory
implements
DownloaderFactory
{
private
static
DownloadAction
createDownloadAction
(
Uri
uri
,
StreamKey
...
keys
)
{
return
DownloadAction
.
createDownloadAction
(
DownloadAction
.
TYPE_PROGRESSIVE
,
uri
,
Arrays
.
asList
(
keys
),
/* customCacheKey= */
null
,
/* data= */
null
);
}
private
static
DownloadAction
createRemoveAction
(
Uri
uri
)
{
return
DownloadAction
.
createRemoveAction
(
DownloadAction
.
TYPE_PROGRESSIVE
,
uri
,
/* customCacheKey= */
null
);
}
public
IdentityHashMap
<
DownloadAction
,
FakeDownloader
>
downloaders
;
private
static
final
class
FakeDownloaderFactory
implements
DownloaderFactory
{
private
final
HashMap
<
Uri
,
DownloadRunner
>
downloaders
;
public
FakeDownloaderFactory
()
{
downloaders
=
new
Identity
HashMap
<>();
downloaders
=
new
HashMap
<>();
}
public
void
putFakeDownloader
(
DownloadAction
action
,
FakeDownloader
download
er
)
{
downloaders
.
put
(
action
,
downloader
);
public
void
registerDownloadRunner
(
DownloadRunner
downloadRunn
er
)
{
assertThat
(
downloaders
.
put
(
downloadRunner
.
uri
,
downloadRunner
)).
isNull
(
);
}
@Override
public
Downloader
createDownloader
(
DownloadAction
action
)
{
return
downloaders
.
get
(
action
);
return
downloaders
.
get
(
action
.
uri
).
createDownloader
(
action
);
}
}
private
static
class
FakeDownloader
implements
Downloader
{
private
static
final
class
FakeDownloader
implements
Downloader
{
private
final
com
.
google
.
android
.
exoplayer2
.
util
.
ConditionVariable
blocker
;
private
final
boolean
isRemove
;
private
DownloadAction
action
;
private
CountDownLatch
started
;
private
boolean
ignoreInterrupts
;
private
volatile
boolean
interrupted
;
private
volatile
boolean
cancelled
;
private
volatile
boolean
enableDownloadIOException
;
private
volatile
int
downloadedBytes
=
C
.
LENGTH_UNSET
;
private
volatile
int
downloadedBytes
;
private
volatile
int
startCount
;
private
FakeDownloader
(
boolean
isRemove
)
{
this
.
isRemove
=
isRemove
;
private
FakeDownloader
()
{
this
.
started
=
new
CountDownLatch
(
1
);
this
.
blocker
=
new
com
.
google
.
android
.
exoplayer2
.
util
.
ConditionVariable
();
downloadedBytes
=
C
.
LENGTH_UNSET
;
}
@SuppressWarnings
({
"NonAtomicOperationOnVolatileField"
,
"NonAtomicVolatileUpdate"
})
@Override
public
void
download
()
throws
InterruptedException
,
IOException
{
assertThat
(
isRemove
).
isFalse
();
// It's ok to update this directly as no other thread will update it.
startCount
++;
assertThat
(
action
.
isRemoveAction
).
isFalse
();
started
.
countDown
();
block
();
if
(
enableDownloadIOException
)
{
enableDownloadIOException
=
false
;
throw
new
IOException
();
}
}
@Override
public
void
cancel
()
{
// Do nothing.
cancelled
=
true
;
}
@SuppressWarnings
({
"NonAtomicOperationOnVolatileField"
,
"NonAtomicVolatileUpdate"
})
@Override
public
void
remove
()
throws
InterruptedException
{
assertThat
(
isRemove
).
isTrue
();
// It's ok to update this directly as no other thread will update it.
startCount
++;
assertThat
(
action
.
isRemoveAction
).
isTrue
();
started
.
countDown
();
block
();
}
...
...
@@ -597,9 +654,8 @@ public class DownloadManagerTest {
blocker
.
block
();
break
;
}
catch
(
InterruptedException
e
)
{
if
(!
ignoreInterrupts
)
{
throw
e
;
}
interrupted
=
true
;
throw
e
;
}
}
}
finally
{
...
...
@@ -607,17 +663,56 @@ public class DownloadManagerTest {
}
}
private
FakeDownloader
assertStarted
()
throws
InterruptedException
{
return
assertStarted
(
ASSERT_TRUE_TIMEOUT
);
}
private
FakeDownloader
assertStarted
(
int
timeout
)
throws
InterruptedException
{
assertThat
(
started
.
await
(
timeout
,
TimeUnit
.
MILLISECONDS
)).
isTrue
();
started
=
new
CountDownLatch
(
1
);
return
this
;
}
private
FakeDownloader
assertStartCount
(
int
count
)
throws
InterruptedException
{
assertThat
(
startCount
).
isEqualTo
(
count
);
return
this
;
}
private
FakeDownloader
assertReleased
()
throws
InterruptedException
{
int
count
=
0
;
while
(
started
.
await
(
ASSERT_TRUE_TIMEOUT
,
TimeUnit
.
MILLISECONDS
))
{
if
(
count
++
>=
MAX_STARTS_BEFORE_RELEASED
)
{
fail
();
}
started
=
new
CountDownLatch
(
1
);
}
return
this
;
}
private
FakeDownloader
assertCanceled
()
throws
InterruptedException
{
assertReleased
();
assertThat
(
interrupted
).
isTrue
();
assertThat
(
cancelled
).
isTrue
();
return
this
;
}
private
FakeDownloader
assertNotCanceled
()
throws
InterruptedException
{
assertReleased
();
assertThat
(
interrupted
).
isFalse
();
assertThat
(
cancelled
).
isFalse
();
return
this
;
}
private
FakeDownloader
unblock
()
{
blocker
.
open
();
return
this
;
}
private
FakeDownloader
fail
()
{
enableDownloadIOException
=
true
;
return
unblock
();
}
@Override
public
long
getDownloadedBytes
()
{
return
downloadedBytes
;
...
...
@@ -632,5 +727,15 @@ public class DownloadManagerTest {
public
float
getDownloadPercentage
()
{
return
C
.
PERCENTAGE_UNSET
;
}
private
void
assertDoesNotStart
()
throws
InterruptedException
{
Thread
.
sleep
(
ASSERT_FALSE_TIME
);
assertThat
(
started
.
getCount
()).
isEqualTo
(
1
);
}
@SuppressWarnings
({
"NonAtomicOperationOnVolatileField"
,
"NonAtomicVolatileUpdate"
})
private
void
increaseDownloadedByteCount
()
{
downloadedBytes
++;
}
}
}
testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/TestDownloadManagerListener.java
View file @
230a798f
...
...
@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.testutil;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
com.google.android.exoplayer2.offline.DownloadAction
;
import
com.google.android.exoplayer2.offline.DownloadManager
;
import
java.util.HashMap
;
import
java.util.concurrent.ArrayBlockingQueue
;
...
...
@@ -31,7 +30,7 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen
private
final
DownloadManager
downloadManager
;
private
final
DummyMainThread
dummyMainThread
;
private
final
HashMap
<
DownloadAction
,
ArrayBlockingQueue
<
Integer
>>
actionStates
;
private
final
HashMap
<
Integer
,
ArrayBlockingQueue
<
Integer
>>
actionStates
;
private
CountDownLatch
downloadFinishedCondition
;
private
Throwable
downloadError
;
...
...
@@ -43,8 +42,8 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen
actionStates
=
new
HashMap
<>();
}
public
int
pollStateChange
(
DownloadAction
action
,
long
timeoutMs
)
throws
InterruptedException
{
return
getStateQueue
(
action
).
poll
(
timeoutMs
,
TimeUnit
.
MILLISECONDS
);
public
Integer
pollStateChange
(
int
taskId
,
long
timeoutMs
)
throws
InterruptedException
{
return
getStateQueue
(
taskId
).
poll
(
timeoutMs
,
TimeUnit
.
MILLISECONDS
);
}
public
void
clearDownloadError
()
{
...
...
@@ -62,7 +61,7 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen
if
(
taskState
.
state
==
DownloadManager
.
TaskState
.
STATE_FAILED
&&
downloadError
==
null
)
{
downloadError
=
taskState
.
error
;
}
getStateQueue
(
taskState
.
action
).
add
(
taskState
.
state
);
getStateQueue
(
taskState
.
taskId
).
add
(
taskState
.
state
);
}
@Override
...
...
@@ -77,6 +76,14 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen
* error.
*/
public
void
blockUntilTasksCompleteAndThrowAnyDownloadError
()
throws
Throwable
{
blockUntilTasksComplete
();
if
(
downloadError
!=
null
)
{
throw
new
Exception
(
downloadError
);
}
}
/** Blocks until all remove and download tasks are complete. Task errors are ignored. */
public
void
blockUntilTasksComplete
()
throws
InterruptedException
{
synchronized
(
this
)
{
downloadFinishedCondition
=
new
CountDownLatch
(
1
);
}
...
...
@@ -87,17 +94,14 @@ public final class TestDownloadManagerListener implements DownloadManager.Listen
}
});
assertThat
(
downloadFinishedCondition
.
await
(
TIMEOUT
,
TimeUnit
.
MILLISECONDS
)).
isTrue
();
if
(
downloadError
!=
null
)
{
throw
new
Exception
(
downloadError
);
}
}
private
ArrayBlockingQueue
<
Integer
>
getStateQueue
(
DownloadAction
action
)
{
private
ArrayBlockingQueue
<
Integer
>
getStateQueue
(
int
taskId
)
{
synchronized
(
actionStates
)
{
if
(!
actionStates
.
containsKey
(
action
))
{
actionStates
.
put
(
action
,
new
ArrayBlockingQueue
<>(
10
));
if
(!
actionStates
.
containsKey
(
taskId
))
{
actionStates
.
put
(
taskId
,
new
ArrayBlockingQueue
<>(
10
));
}
return
actionStates
.
get
(
action
);
return
actionStates
.
get
(
taskId
);
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment