diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index 33a5d8b900..9aa84eaa0b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -16,12 +16,10 @@ package com.google.android.exoplayer2.offline; import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_CANCELED; -import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_CANCELING; import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_ENDED; import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_ERROR; +import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_QUEUED; import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_STARTED; -import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_STOPPING; -import static com.google.android.exoplayer2.offline.DownloadManager.DownloadState.STATE_WAITING; import android.os.ConditionVariable; import android.os.Handler; @@ -31,7 +29,6 @@ import android.support.annotation.IntDef; import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.DownloadAction.Deserializer; -import com.google.android.exoplayer2.offline.DownloadManager.DownloadState.State; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -264,7 +261,7 @@ public final class DownloadManager { return false; } for (int i = 0; i < tasks.size(); i++) { - if (tasks.get(i).isRunning()) { + if (tasks.get(i).isActive()) { return false; } } @@ -344,7 +341,7 @@ public final class DownloadManager { return; } logd("Task state is changed", downloadTask); - boolean stopped = !downloadTask.isRunning(); + boolean stopped = !downloadTask.isActive(); if (stopped) { activeDownloadTasks.remove(downloadTask); } @@ -446,62 +443,39 @@ public final class DownloadManager { /** * Task states. * - *

Transition map (vertical states are source states): + *

Transition diagram: + * *

-     *           +-------+-------+-----+---------+--------+--------+-----+
-     *           |waiting|started|ended|canceling|canceled|stopping|error|
-     * +---------+-------+-------+-----+---------+--------+--------+-----+
-     * |waiting  |       |   X   |     |    X    |        |        |     |
-     * |started  |       |       |  X  |    X    |        |   X    |  X  |
-     * |canceling|       |       |     |         |   X    |        |     |
-     * |stopping |   X   |       |     |         |        |        |     |
-     * +---------+-------+-------+-----+---------+--------+--------+-----+
+     *                    -> canceled
+     * queued <-> started -> ended
+     *                    -> error
      * 
*/ @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_WAITING, STATE_STARTED, STATE_ENDED, STATE_CANCELING, STATE_CANCELED, - STATE_STOPPING, STATE_ERROR}) + @IntDef({STATE_QUEUED, STATE_STARTED, STATE_ENDED, STATE_CANCELED, STATE_ERROR}) public @interface State {} /** The task is waiting to be started. */ - public static final int STATE_WAITING = 0; + public static final int STATE_QUEUED = 0; /** The task is currently started. */ public static final int STATE_STARTED = 1; /** The task completed. */ public static final int STATE_ENDED = 2; - /** The task is about to be canceled. */ - public static final int STATE_CANCELING = 3; /** The task was canceled. */ - public static final int STATE_CANCELED = 4; - /** The task is about to be stopped. */ - public static final int STATE_STOPPING = 5; + public static final int STATE_CANCELED = 3; /** The task failed. */ - public static final int STATE_ERROR = 6; - - /** Returns whether the task is running. */ - public static boolean isRunning(int state) { - return state == STATE_STARTED || state == STATE_STOPPING || state == STATE_CANCELING; - } - - /** Returns whether the task is finished. */ - public static boolean isFinished(int state) { - return state == STATE_ERROR || state == STATE_ENDED || state == STATE_CANCELED; - } + public static final int STATE_ERROR = 4; /** Returns the state string for the given state value. */ public static String getStateString(@State int state) { switch (state) { - case STATE_WAITING: - return "WAITING"; + case STATE_QUEUED: + return "QUEUED"; case STATE_STARTED: return "STARTED"; case STATE_ENDED: return "ENDED"; - case STATE_CANCELING: - return "CANCELING"; case STATE_CANCELED: return "CANCELED"; - case STATE_STOPPING: - return "STOPPING"; case STATE_ERROR: return "ERROR"; default: @@ -531,7 +505,7 @@ public final class DownloadManager { private DownloadState( int taskId, DownloadAction downloadAction, - int state, + @State int state, float downloadPercentage, long downloadedBytes, Throwable error) { @@ -543,24 +517,51 @@ public final class DownloadManager { this.error = error; } - /** Returns whether the task is finished. */ - public boolean isFinished() { - return isFinished(state); - } - - /** Returns whether the task is running. */ - public boolean isRunning() { - return isRunning(state); - } } private static final class DownloadTask implements Runnable { + /** + * Task states. + * + *

Transition map (vertical states are source states): + * + *

+     *             +------+-------+-----+-----------+-----------+--------+--------+-----+
+     *             |queued|started|ended|q_canceling|s_canceling|canceled|stopping|error|
+     * +-----------+------+-------+-----+-----------+-----------+--------+--------+-----+
+     * |queued     |      |   X   |     |     X     |           |        |        |     |
+     * |started    |      |       |  X  |           |     X     |        |   X    |  X  |
+     * |q_canceling|      |       |     |           |           |   X    |        |     |
+     * |s_canceling|      |       |     |           |           |   X    |        |     |
+     * |stopping   |   X  |       |     |           |           |        |        |     |
+     * +-----------+------+-------+-----+-----------+-----------+--------+--------+-----+
+     * 
+ */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + STATE_QUEUED, + STATE_STARTED, + STATE_ENDED, + STATE_CANCELED, + STATE_ERROR, + STATE_QUEUED_CANCELING, + STATE_STARTED_CANCELING, + STATE_STARTED_STOPPING + }) + public @interface InternalState {} + /** The task is about to be canceled. */ + public static final int STATE_QUEUED_CANCELING = 5; + /** The task is about to be canceled. */ + public static final int STATE_STARTED_CANCELING = 6; + /** The task is about to be stopped. */ + public static final int STATE_STARTED_STOPPING = 7; + private final int id; private final DownloadManager downloadManager; private final DownloadAction downloadAction; private final int minRetryCount; - private volatile @State int currentState; + private volatile @InternalState int currentState; private volatile Downloader downloader; private Thread thread; private Throwable error; @@ -570,28 +571,29 @@ public final class DownloadManager { this.id = id; this.downloadManager = downloadManager; this.downloadAction = downloadAction; - this.currentState = STATE_WAITING; + this.currentState = STATE_QUEUED; this.minRetryCount = minRetryCount; } public DownloadState getDownloadState() { + int externalState = getExternalState(); return new DownloadState( - id, downloadAction, currentState, getDownloadPercentage(), getDownloadedBytes(), error); - } - - /** Returns the state of the task. */ - public @State int getState() { - return currentState; + id, downloadAction, externalState, getDownloadPercentage(), getDownloadedBytes(), error); } /** Returns whether the task is finished. */ public boolean isFinished() { - return DownloadState.isFinished(currentState); + return currentState == STATE_ERROR + || currentState == STATE_ENDED + || currentState == STATE_CANCELED; } - /** Returns whether the task is running. */ - public boolean isRunning() { - return DownloadState.isRunning(currentState); + /** Returns whether the task is started. */ + public boolean isActive() { + return currentState == STATE_QUEUED_CANCELING + || currentState == STATE_STARTED + || currentState == STATE_STARTED_STOPPING + || currentState == STATE_STARTED_CANCELING; } /** @@ -621,52 +623,80 @@ public final class DownloadManager { + ' ' + downloadAction.getData() + ' ' - + DownloadState.getStateString(currentState); + + getStateString(); + } + + private String getStateString() { + switch (currentState) { + case STATE_QUEUED_CANCELING: + case STATE_STARTED_CANCELING: + return "CANCELING"; + case STATE_STARTED_STOPPING: + return "STOPPING"; + default: + return DownloadState.getStateString(currentState); + } + } + + private int getExternalState() { + switch (currentState) { + case STATE_QUEUED_CANCELING: + return STATE_QUEUED; + case STATE_STARTED_CANCELING: + case STATE_STARTED_STOPPING: + return STATE_STARTED; + default: + return currentState; + } } private void start() { - if (changeStateAndNotify(STATE_WAITING, STATE_STARTED)) { + if (changeStateAndNotify(STATE_QUEUED, STATE_STARTED)) { thread = new Thread(this); thread.start(); } } private boolean canStart() { - return currentState == STATE_WAITING; + return currentState == STATE_QUEUED; } private void cancel() { - if (changeStateAndNotify(STATE_WAITING, STATE_CANCELING)) { - downloadManager.handler.post(new Runnable() { - @Override - public void run() { - changeStateAndNotify(STATE_CANCELING, STATE_CANCELED); - } - }); - } else if (changeStateAndNotify(STATE_STARTED, STATE_CANCELING)) { + if (changeStateAndNotify(STATE_QUEUED, STATE_QUEUED_CANCELING)) { + downloadManager.handler.post( + new Runnable() { + @Override + public void run() { + changeStateAndNotify(STATE_QUEUED_CANCELING, STATE_CANCELED); + } + }); + } else if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_CANCELING)) { thread.interrupt(); } } private void stop() { - if (changeStateAndNotify(STATE_STARTED, STATE_STOPPING)) { + if (changeStateAndNotify(STATE_STARTED, STATE_STARTED_STOPPING)) { downloadManager.logd("Stopping", this); thread.interrupt(); } } - private boolean changeStateAndNotify(@State int oldState, @State int newState) { + private boolean changeStateAndNotify(@InternalState int oldState, @InternalState int newState) { return changeStateAndNotify(oldState, newState, null); } - private boolean changeStateAndNotify(@State int oldState, @State int newState, - Throwable error) { + private boolean changeStateAndNotify( + @InternalState int oldState, @InternalState int newState, Throwable error) { if (currentState != oldState) { return false; } currentState = newState; this.error = error; - downloadManager.onTaskStateChange(DownloadTask.this); + boolean isInternalState = currentState != getExternalState(); + if (!isInternalState) { + downloadManager.onTaskStateChange(DownloadTask.this); + } return true; } @@ -707,18 +737,19 @@ public final class DownloadManager { error = e; } final Throwable finalError = error; - downloadManager.handler.post(new Runnable() { - @Override - public void run() { - if (changeStateAndNotify(STATE_STARTED, - finalError != null ? STATE_ERROR : STATE_ENDED, finalError) - || changeStateAndNotify(STATE_CANCELING, STATE_CANCELED) - || changeStateAndNotify(STATE_STOPPING, STATE_WAITING)) { - return; - } - throw new IllegalStateException(); - } - }); + downloadManager.handler.post( + new Runnable() { + @Override + public void run() { + if (changeStateAndNotify( + STATE_STARTED, finalError != null ? STATE_ERROR : STATE_ENDED, finalError) + || changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED) + || changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) { + return; + } + throw new IllegalStateException(); + } + }); } private int getRetryDelayMillis(int errorCount) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java index 12369e6e28..d5409ca5e8 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java @@ -607,8 +607,7 @@ public class DownloadManagerTest { } private FakeDownloadAction assertStopped() { - assertState(DownloadState.STATE_STOPPING); - return assertState(DownloadState.STATE_WAITING); + return assertState(DownloadState.STATE_QUEUED); } private FakeDownloadAction assertState(@State int expectedState) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java index af09e1c128..b4f69809a5 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java @@ -67,12 +67,16 @@ public final class DownloadNotificationUtil { int titleStringId = getTitleStringId(downloadState); notificationBuilder.setContentTitle(context.getResources().getString(titleStringId)); - if (downloadState.isRunning()) { + if (downloadState.state == DownloadState.STATE_STARTED) { notificationBuilder.setOngoing(true); float percentage = downloadState.downloadPercentage; boolean indeterminate = Float.isNaN(percentage); notificationBuilder.setProgress(100, indeterminate ? 0 : (int) percentage, indeterminate); } + if (Util.SDK_INT >= 17) { + // Hide timestamp on the notification while download progresses. + notificationBuilder.setShowWhen(downloadState.state != DownloadState.STATE_STARTED); + } if (downloadState.error != null && errorMessageProvider != null) { message = errorMessageProvider.getErrorMessage(downloadState.error).second; @@ -90,12 +94,10 @@ public final class DownloadNotificationUtil { private static int getTitleStringId(DownloadState downloadState) { int titleStringId; switch (downloadState.state) { - case DownloadState.STATE_WAITING: + case DownloadState.STATE_QUEUED: titleStringId = R.string.exo_download_queued; break; case DownloadState.STATE_STARTED: - case DownloadState.STATE_STOPPING: - case DownloadState.STATE_CANCELING: titleStringId = R.string.exo_downloading; break; case DownloadState.STATE_ENDED: