Commit 59c620c2 by olly Committed by Oliver Woodman

Clean up offline notifications

- It's much cleaner to split completed/error notification methods.
- Make error notification show the content title in the demo app.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=194920507
parent 5d3c080f
......@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.demo;
import android.app.Notification;
import android.util.Pair;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadManager.TaskState;
import com.google.android.exoplayer2.offline.DownloadService;
......@@ -27,7 +26,6 @@ import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction;
import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction;
import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction;
import com.google.android.exoplayer2.ui.DownloadNotificationUtil;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util;
......@@ -76,32 +74,39 @@ public class DemoDownloadService extends DownloadService {
@Override
protected Notification getForegroundNotification(TaskState[] taskStates) {
return DownloadNotificationUtil.createProgressNotification(
taskStates,
return DownloadNotificationUtil.buildProgressNotification(
/* context= */ this,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
/* message= */ null);
/* message= */ null,
taskStates);
}
@Override
protected void onTaskStateChanged(TaskState taskState) {
if (taskState.action.isRemoveAction) {
return;
}
Notification notification = null;
if (taskState.state == TaskState.STATE_COMPLETED) {
notification =
DownloadNotificationUtil.buildDownloadCompletedNotification(
/* context= */ this,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
taskState.action.data);
} else if (taskState.state == TaskState.STATE_FAILED) {
notification =
DownloadNotificationUtil.buildDownloadFailedNotification(
/* context= */ this,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
taskState.action.data);
}
int notificationId = FOREGROUND_NOTIFICATION_ID + 1 + taskState.taskId;
Notification downloadNotification =
DownloadNotificationUtil.createDownloadFinishedNotification(
taskState,
/* context= */ this,
R.drawable.exo_controls_play,
CHANNEL_ID,
/* contentIntent= */ null,
taskState.action.data,
new ErrorMessageProvider<Throwable>() {
@Override
public Pair<Integer, String> getErrorMessage(Throwable throwable) {
return new Pair<>(0, throwable.getLocalizedMessage());
}
});
NotificationUtil.setNotification(/* context= */ this, notificationId, downloadNotification);
NotificationUtil.setNotification(this, notificationId, notification);
}
}
......@@ -16,8 +16,8 @@
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_ENDED;
import static com.google.android.exoplayer2.offline.DownloadManager.TaskState.STATE_ERROR;
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;
import static com.google.android.exoplayer2.offline.DownloadManager.TaskState.STATE_STARTED;
......@@ -487,23 +487,23 @@ public final class DownloadManager {
*
* <pre>
* -&gt; canceled
* queued &lt;-&gt; started -&gt; ended
* -&gt; error
* queued &lt;-&gt; started -&gt; completed
* -&gt; failed
* </pre>
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_QUEUED, STATE_STARTED, STATE_ENDED, STATE_CANCELED, STATE_ERROR})
@IntDef({STATE_QUEUED, STATE_STARTED, STATE_COMPLETED, STATE_CANCELED, STATE_FAILED})
public @interface State {}
/** The task is waiting to be started. */
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;
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_ERROR = 4;
public static final int STATE_FAILED = 4;
/** Returns the state string for the given state value. */
public static String getStateString(@State int state) {
......@@ -512,12 +512,12 @@ public final class DownloadManager {
return "QUEUED";
case STATE_STARTED:
return "STARTED";
case STATE_ENDED:
return "ENDED";
case STATE_COMPLETED:
return "COMPLETED";
case STATE_CANCELED:
return "CANCELED";
case STATE_ERROR:
return "ERROR";
case STATE_FAILED:
return "FAILED";
default:
throw new IllegalStateException();
}
......@@ -538,7 +538,7 @@ public final class DownloadManager {
/** The total number of downloaded bytes. */
public final long downloadedBytes;
/** If {@link #state} is {@link #STATE_ERROR} then this is the cause, otherwise null. */
/** If {@link #state} is {@link #STATE_FAILED} then this is the cause, otherwise null. */
public final Throwable error;
private TaskState(
......@@ -566,24 +566,24 @@ public final class DownloadManager {
* <p>Transition map (vertical states are source states):
*
* <pre>
* +------+-------+-----+-----------+-----------+--------+--------+-----+
* |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 | | | | | | | |
* +-----------+------+-------+-----+-----------+-----------+--------+--------+-----+
* +------+-------+---------+-----------+-----------+--------+--------+------+
* |queued|started|completed|q_canceling|s_canceling|canceled|stopping|failed|
* +-----------+------+-------+---------+-----------+-----------+--------+--------+------+
* |queued | | X | | X | | | | |
* |started | | | X | | X | | X | X |
* |q_canceling| | | | | | X | | |
* |s_canceling| | | | | | X | | |
* |stopping | X | | | | | | | |
* +-----------+------+-------+---------+-----------+-----------+--------+--------+------+
* </pre>
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STATE_QUEUED,
STATE_STARTED,
STATE_ENDED,
STATE_COMPLETED,
STATE_CANCELED,
STATE_ERROR,
STATE_FAILED,
STATE_QUEUED_CANCELING,
STATE_STARTED_CANCELING,
STATE_STARTED_STOPPING
......@@ -622,8 +622,8 @@ public final class DownloadManager {
/** Returns whether the task is finished. */
public boolean isFinished() {
return currentState == STATE_ERROR
|| currentState == STATE_ENDED
return currentState == STATE_FAILED
|| currentState == STATE_COMPLETED
|| currentState == STATE_CANCELED;
}
......@@ -778,7 +778,9 @@ public final class DownloadManager {
@Override
public void run() {
if (changeStateAndNotify(
STATE_STARTED, finalError != null ? STATE_ERROR : STATE_ENDED, finalError)
STATE_STARTED,
finalError != null ? STATE_FAILED : STATE_COMPLETED,
finalError)
|| changeStateAndNotify(STATE_STARTED_CANCELING, STATE_CANCELED)
|| changeStateAndNotify(STATE_STARTED_STOPPING, STATE_QUEUED)) {
return;
......
......@@ -102,21 +102,21 @@ public class DownloadManagerTest {
for (int i = 0; i <= MIN_RETRY_COUNT; i++) {
fakeDownloader.assertStarted(MAX_RETRY_DELAY).unblock();
}
downloadAction.assertError();
downloadAction.assertFailed();
testDownloadListener.clearDownloadError();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
@Test
public void testDownloadNoRetryWhenCancelled() throws Throwable {
public void testDownloadNoRetryWhenCanceled() throws Throwable {
FakeDownloadAction downloadAction = createDownloadAction("media 1").ignoreInterrupts();
downloadAction.getFakeDownloader().enableDownloadIOException = true;
downloadAction.post().assertStarted();
FakeDownloadAction removeAction = createRemoveAction("media 1").post();
downloadAction.unblock().assertCancelled();
downloadAction.unblock().assertCanceled();
removeAction.unblock();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
......@@ -135,7 +135,7 @@ public class DownloadManagerTest {
}
fakeDownloader.unblock();
}
downloadAction.assertEnded();
downloadAction.assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -156,7 +156,7 @@ public class DownloadManagerTest {
}
fakeDownloader.unblock();
}
downloadAction.assertEnded();
downloadAction.assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -207,13 +207,13 @@ public class DownloadManagerTest {
removeAction1.post().assertDoesNotStart();
// downloadAction2 finishes but it isn't enough to start removeAction1.
downloadAction2.unblock().assertCancelled();
downloadAction2.unblock().assertCanceled();
removeAction1.assertDoesNotStart();
// downloadAction3 is post to DownloadManager but it waits for removeAction1 to finish.
downloadAction3.post().assertDoesNotStart();
// When downloadAction1 finishes, removeAction1 starts.
downloadAction1.unblock().assertCancelled();
downloadAction1.unblock().assertCanceled();
removeAction1.assertStarted();
// downloadAction3 still waits removeAction1
downloadAction3.assertDoesNotStart();
......@@ -221,9 +221,9 @@ public class DownloadManagerTest {
// removeAction2 is posted. removeAction1 and downloadAction3 is canceled so removeAction2
// starts immediately.
removeAction2.post();
removeAction1.assertCancelled();
downloadAction3.assertCancelled();
removeAction2.assertStarted().unblock().assertEnded();
removeAction1.assertCanceled();
downloadAction3.assertCanceled();
removeAction2.assertStarted().unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -237,10 +237,10 @@ public class DownloadManagerTest {
removeAction2.post().assertDoesNotStart();
removeAction3.post().assertDoesNotStart();
removeAction2.assertCancelled();
removeAction2.assertCanceled();
removeAction1.unblock().assertCancelled();
removeAction3.assertStarted().unblock().assertEnded();
removeAction1.unblock().assertCanceled();
removeAction3.assertStarted().unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -272,11 +272,11 @@ public class DownloadManagerTest {
downloadAction1.post().assertDoesNotStart();
downloadAction2.post().assertDoesNotStart();
removeAction.unblock().assertEnded();
removeAction.unblock().assertCompleted();
downloadAction1.assertStarted();
downloadAction2.assertStarted();
downloadAction1.unblock().assertEnded();
downloadAction2.unblock().assertEnded();
downloadAction1.unblock().assertCompleted();
downloadAction2.unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -291,11 +291,11 @@ public class DownloadManagerTest {
downloadAction1.post().assertDoesNotStart();
downloadAction2.post().assertDoesNotStart();
removeAction.unblock().assertEnded();
removeAction.unblock().assertCompleted();
downloadAction1.assertStarted();
downloadAction2.assertStarted();
downloadAction1.unblock().assertEnded();
downloadAction2.unblock().assertEnded();
downloadAction1.unblock().assertCompleted();
downloadAction2.unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -310,11 +310,11 @@ public class DownloadManagerTest {
removeAction1.post().assertDoesNotStart();
removeAction2.post().assertStarted();
downloadAction.unblock().assertCancelled();
removeAction2.unblock().assertEnded();
downloadAction.unblock().assertCanceled();
removeAction2.unblock().assertCompleted();
removeAction1.assertStarted();
removeAction1.unblock().assertEnded();
removeAction1.unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -342,14 +342,14 @@ public class DownloadManagerTest {
download1Action.assertStopped();
// remove actions aren't stopped.
remove2Action.unblock().assertEnded();
remove2Action.unblock().assertCompleted();
// Although remove2Action is finished, download2Action doesn't start.
download2Action.assertDoesNotStart();
// When a new remove action is added, it cancels stopped download actions with the same media.
remove1Action.post();
download1Action.assertCancelled();
remove1Action.assertStarted().unblock().assertEnded();
download1Action.assertCanceled();
remove1Action.assertStarted().unblock().assertCompleted();
// New download actions can be added but they don't start.
download3Action.post().assertDoesNotStart();
......@@ -362,8 +362,8 @@ public class DownloadManagerTest {
}
});
download2Action.assertStarted().unblock().assertEnded();
download3Action.assertStarted().unblock().assertEnded();
download2Action.assertStarted().unblock().assertCompleted();
download3Action.assertStarted().unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -456,7 +456,7 @@ public class DownloadManagerTest {
}
private void doTestActionRuns(FakeDownloadAction action) throws Throwable {
action.post().assertStarted().unblock().assertEnded();
action.post().assertStarted().unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -468,7 +468,7 @@ public class DownloadManagerTest {
action1.unblock();
action2.assertStarted();
action2.unblock().assertEnded();
action2.unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -476,8 +476,8 @@ public class DownloadManagerTest {
throws Throwable {
action1.post().assertStarted();
action2.post().assertStarted();
action1.unblock().assertEnded();
action2.unblock().assertEnded();
action1.unblock().assertCompleted();
action2.unblock().assertCompleted();
testDownloadListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
}
......@@ -504,7 +504,7 @@ public class DownloadManagerTest {
@Override
public void onTaskStateChanged(DownloadManager downloadManager, TaskState taskState) {
if (taskState.state == TaskState.STATE_ERROR && downloadError == null) {
if (taskState.state == TaskState.STATE_FAILED && downloadError == null) {
downloadError = taskState.error;
}
((FakeDownloadAction) taskState.action).onStateChange(taskState.state);
......@@ -583,15 +583,15 @@ public class DownloadManagerTest {
return assertState(TaskState.STATE_STARTED);
}
private FakeDownloadAction assertEnded() {
return assertState(TaskState.STATE_ENDED);
private FakeDownloadAction assertCompleted() {
return assertState(TaskState.STATE_COMPLETED);
}
private FakeDownloadAction assertError() {
return assertState(TaskState.STATE_ERROR);
private FakeDownloadAction assertFailed() {
return assertState(TaskState.STATE_FAILED);
}
private FakeDownloadAction assertCancelled() {
private FakeDownloadAction assertCanceled() {
return assertState(TaskState.STATE_CANCELED);
}
......
......@@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit;
@Override
public void onTaskStateChanged(
DownloadManager downloadManager, DownloadManager.TaskState taskState) {
if (taskState.state == DownloadManager.TaskState.STATE_ERROR && downloadError == null) {
if (taskState.state == DownloadManager.TaskState.STATE_FAILED && downloadError == null) {
downloadError = taskState.error;
}
}
......
......@@ -23,11 +23,9 @@ import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.NotificationCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadManager.TaskState;
import com.google.android.exoplayer2.util.ErrorMessageProvider;
/** Helper class to create notifications for downloads using {@link DownloadManager}. */
/** Helper for creating download notifications. */
public final class DownloadNotificationUtil {
private static final @StringRes int NULL_STRING_ID = 0;
......@@ -35,24 +33,24 @@ public final class DownloadNotificationUtil {
private DownloadNotificationUtil() {}
/**
* Returns a progress notification for the given {@link TaskState}s.
* Returns a progress notification for the given task states.
*
* @param taskStates States of the downloads.
* @param context Used to access resources.
* @param context A context for accessing resources.
* @param smallIcon A small icon for the notification.
* @param channelId The id of the notification channel to use. Only required for API level 26 and
* above.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @return A progress notification for the given {@link TaskState}s.
* @param taskStates The task states.
* @return The notification.
*/
public static @Nullable Notification createProgressNotification(
TaskState[] taskStates,
public static Notification buildProgressNotification(
Context context,
@DrawableRes int smallIcon,
String channelId,
@Nullable PendingIntent contentIntent,
@Nullable String message) {
@Nullable String message,
TaskState[] taskStates) {
float totalPercentage = 0;
int downloadTaskCount = 0;
boolean allDownloadPercentagesUnknown = true;
......@@ -75,7 +73,7 @@ public final class DownloadNotificationUtil {
? R.string.exo_download_downloading
: (taskStates.length > 0 ? R.string.exo_download_removing : NULL_STRING_ID);
NotificationCompat.Builder notificationBuilder =
createNotificationBuilder(
newNotificationBuilder(
context, smallIcon, channelId, contentIntent, message, titleStringId);
int progress = haveDownloadTasks ? (int) (totalPercentage / downloadTaskCount) : 0;
......@@ -88,51 +86,52 @@ public final class DownloadNotificationUtil {
}
/**
* Returns a notification for a {@link TaskState} which is in either {@link TaskState#STATE_ENDED}
* or {@link TaskState#STATE_ERROR} states. Returns null if it's some other state or it's state of
* a remove action.
* Returns a notification for a completed download.
*
* @param taskState State of the download.
* @param context Used to access resources.
* @param context A context for accessing resources.
* @param smallIcon A small icon for the notifications.
* @param channelId The id of the notification channel to use. Only required for API level 26 and
* above.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @param errorMessageProvider An optional {@link ErrorMessageProvider} for translating download
* errors into readable error messages. If not null and there is a download error then the
* error message is displayed instead of {@code message}.
* @return A notification for a {@link TaskState} which is in either {@link TaskState#STATE_ENDED}
* or {@link TaskState#STATE_ERROR} states. Returns null if it's some other state or it's
* state of a remove action.
* @return The notification.
*/
public static @Nullable Notification createDownloadFinishedNotification(
TaskState taskState,
public static Notification buildDownloadCompletedNotification(
Context context,
@DrawableRes int smallIcon,
String channelId,
@Nullable PendingIntent contentIntent,
@Nullable String message,
@Nullable ErrorMessageProvider<Throwable> errorMessageProvider) {
if (taskState.action.isRemoveAction
|| (taskState.state != TaskState.STATE_ENDED && taskState.state != TaskState.STATE_ERROR)) {
return null;
}
if (taskState.error != null && errorMessageProvider != null) {
message = errorMessageProvider.getErrorMessage(taskState.error).second;
}
@StringRes
int titleStringId =
taskState.state == TaskState.STATE_ENDED
? R.string.exo_download_completed
: R.string.exo_download_failed;
NotificationCompat.Builder notificationBuilder =
createNotificationBuilder(
context, smallIcon, channelId, contentIntent, message, titleStringId);
return notificationBuilder.build();
@Nullable String message) {
int titleStringId = R.string.exo_download_completed;
return newNotificationBuilder(
context, smallIcon, channelId, contentIntent, message, titleStringId)
.build();
}
/**
* Returns a notification for a failed download.
*
* @param context A context for accessing resources.
* @param smallIcon A small icon for the notifications.
* @param channelId The id of the notification channel to use. Only required for API level 26 and
* above.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @return The notification.
*/
public static Notification buildDownloadFailedNotification(
Context context,
@DrawableRes int smallIcon,
String channelId,
@Nullable PendingIntent contentIntent,
@Nullable String message) {
@StringRes int titleStringId = R.string.exo_download_failed;
return newNotificationBuilder(
context, smallIcon, channelId, contentIntent, message, titleStringId)
.build();
}
private static NotificationCompat.Builder createNotificationBuilder(
private static NotificationCompat.Builder newNotificationBuilder(
Context context,
@DrawableRes int smallIcon,
String channelId,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment