diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java index b561624725..aa85227839 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadService.java @@ -136,9 +136,9 @@ public abstract class DownloadService extends Service { private static final HashMap, DownloadManagerHelper> downloadManagerListeners = new HashMap<>(); - private final @Nullable ForegroundNotificationUpdater foregroundNotificationUpdater; - private final @Nullable String channelId; - private final @StringRes int channelName; + @Nullable private final ForegroundNotificationUpdater foregroundNotificationUpdater; + @Nullable private final String channelId; + @StringRes private final int channelNameResourceId; private DownloadManager downloadManager; private int lastStartId; @@ -148,30 +148,29 @@ public abstract class DownloadService extends Service { /** * Creates a DownloadService. * - *

If {@code foregroundNotificationId} is {@link #FOREGROUND_NOTIFICATION_ID_NONE} (value - * {@value #FOREGROUND_NOTIFICATION_ID_NONE}) then the service runs in the background. No - * foreground notification is displayed and {@link #getScheduler()} isn't called. + *

If {@code foregroundNotificationId} is {@link #FOREGROUND_NOTIFICATION_ID_NONE} then the + * service will only ever run in the background. No foreground notification will be displayed and + * {@link #getScheduler()} will not be called. * - *

If {@code foregroundNotificationId} isn't {@link #FOREGROUND_NOTIFICATION_ID_NONE} (value - * {@value #FOREGROUND_NOTIFICATION_ID_NONE}) the service runs in the foreground with {@link - * #DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL}. In that case {@link - * #getForegroundNotification(DownloadState[])} should be overridden in the subclass. + *

If {@code foregroundNotificationId} is not {@link #FOREGROUND_NOTIFICATION_ID_NONE} then the + * service will run in the foreground. The foreground notification will be updated at least as + * often as the interval specified by {@link #DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL}. * * @param foregroundNotificationId The notification id for the foreground notification, or {@link - * #FOREGROUND_NOTIFICATION_ID_NONE} (value {@value #FOREGROUND_NOTIFICATION_ID_NONE}) + * #FOREGROUND_NOTIFICATION_ID_NONE} if the service should only ever run in the background. */ protected DownloadService(int foregroundNotificationId) { this(foregroundNotificationId, DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL); } /** - * Creates a DownloadService that will run in the foreground. {@link - * #getForegroundNotification(DownloadState[])} should be overridden in the subclass. + * Creates a DownloadService. * - * @param foregroundNotificationId The notification id for the foreground notification, must not - * be 0. - * @param foregroundNotificationUpdateInterval The maximum interval to update foreground - * notification, in milliseconds. + * @param foregroundNotificationId The notification id for the foreground notification, or {@link + * #FOREGROUND_NOTIFICATION_ID_NONE} if the service should only ever run in the background. + * @param foregroundNotificationUpdateInterval The maximum interval between updates to the + * foreground notification, in milliseconds. Ignored if {@code foregroundNotificationId} is + * {@link #FOREGROUND_NOTIFICATION_ID_NONE}. */ protected DownloadService( int foregroundNotificationId, long foregroundNotificationUpdateInterval) { @@ -179,36 +178,42 @@ public abstract class DownloadService extends Service { foregroundNotificationId, foregroundNotificationUpdateInterval, /* channelId= */ null, - /* channelName= */ 0); + /* channelNameResourceId= */ 0); } /** - * Creates a DownloadService that will run in the foreground. {@link - * #getForegroundNotification(DownloadState[])} should be overridden in the subclass. + * Creates a DownloadService. * - * @param foregroundNotificationId The notification id for the foreground notification. Must not - * be 0. + * @param foregroundNotificationId The notification id for the foreground notification, or {@link + * #FOREGROUND_NOTIFICATION_ID_NONE} if the service should only ever run in the background. * @param foregroundNotificationUpdateInterval The maximum interval between updates to the - * foreground notification, in milliseconds. + * foreground notification, in milliseconds. Ignored if {@code foregroundNotificationId} is + * {@link #FOREGROUND_NOTIFICATION_ID_NONE}. * @param channelId An id for a low priority notification channel to create, or {@code null} if * the app will take care of creating a notification channel if needed. If specified, must be - * unique per package and the value may be truncated if it is too long. - * @param channelName A string resource identifier for the user visible name of the channel, if - * {@code channelId} is specified. The recommended maximum length is 40 characters; the value - * may be truncated if it is too long. + * unique per package. The value may be truncated if it's too long. Ignored if {@code + * foregroundNotificationId} is {@link #FOREGROUND_NOTIFICATION_ID_NONE}. + * @param channelNameResourceId A string resource identifier for the user visible name of the + * channel, if {@code channelId} is specified. The recommended maximum length is 40 + * characters. The value may be truncated if it is too long. Ignored if {@code + * foregroundNotificationId} is {@link #FOREGROUND_NOTIFICATION_ID_NONE}. */ protected DownloadService( int foregroundNotificationId, long foregroundNotificationUpdateInterval, @Nullable String channelId, - @StringRes int channelName) { - foregroundNotificationUpdater = - foregroundNotificationId == 0 - ? null - : new ForegroundNotificationUpdater( - foregroundNotificationId, foregroundNotificationUpdateInterval); - this.channelId = channelId; - this.channelName = channelName; + @StringRes int channelNameResourceId) { + if (foregroundNotificationId == FOREGROUND_NOTIFICATION_ID_NONE) { + this.foregroundNotificationUpdater = null; + this.channelId = null; + this.channelNameResourceId = 0; + } else { + this.foregroundNotificationUpdater = + new ForegroundNotificationUpdater( + foregroundNotificationId, foregroundNotificationUpdateInterval); + this.channelId = channelId; + this.channelNameResourceId = channelNameResourceId; + } } /** @@ -350,7 +355,7 @@ public abstract class DownloadService extends Service { logd("onCreate"); if (channelId != null) { NotificationUtil.createNotificationChannel( - this, channelId, channelName, NotificationUtil.IMPORTANCE_LOW); + this, channelId, channelNameResourceId, NotificationUtil.IMPORTANCE_LOW); } Class clazz = getClass(); DownloadManagerHelper downloadManagerHelper = downloadManagerListeners.get(clazz); @@ -477,26 +482,23 @@ public abstract class DownloadService extends Service { protected abstract @Nullable Scheduler getScheduler(); /** - * Should be overridden in the subclass if the service will be run in the foreground. - * - *

Returns a notification to be displayed when this service running in the foreground. - * - *

This method is called when there is a download state change and periodically while there are - * active downloads. The periodic update interval can be set using {@link #DownloadService(int, - * long)}. + * Returns a notification to be displayed when this service running in the foreground. This method + * is called when there is a download state change and periodically while there are active + * downloads. The periodic update interval can be set using {@link #DownloadService(int, long)}. * *

On API level 26 and above, this method may also be called just before the service stops, * with an empty {@code downloadStates} array. The returned notification is used to satisfy system * requirements for foreground services. * + *

Download services that do not wish to run in the foreground should be created by setting the + * {@code foregroundNotificationId} constructor argument to {@link + * #FOREGROUND_NOTIFICATION_ID_NONE}. This method will not be called in this case, meaning it can + * be implemented to throw {@link UnsupportedOperationException}. + * * @param downloadStates The states of all current downloads. * @return The foreground notification to display. */ - protected Notification getForegroundNotification(DownloadState[] downloadStates) { - throw new IllegalStateException( - getClass().getName() - + " is started in the foreground but getForegroundNotification() is not implemented."); - } + protected abstract Notification getForegroundNotification(DownloadState[] downloadStates); /** * Called when the state of a download changes. The default implementation is a no-op. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java index e1c98c6575..4cd03f566d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NotificationUtil.java @@ -28,7 +28,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -/** Utility methods for displaying {@link android.app.Notification}s. */ +/** Utility methods for displaying {@link Notification Notifications}. */ @SuppressLint("InlinedApi") public final class NotificationUtil { @@ -66,25 +66,25 @@ public final class NotificationUtil { * NotificationChannel} and {@link * NotificationManager#createNotificationChannel(NotificationChannel)} for details. * - * @param context A {@link Context} to retrieve {@link NotificationManager}. - * @param id The id of the channel. Must be unique per package. The value may be truncated if it - * is too long. - * @param name A string resource identifier for the user visible name of the channel. You can - * rename this channel when the system locale changes by listening for the {@link - * Intent#ACTION_LOCALE_CHANGED} broadcast. The recommended maximum length is 40 characters; - * the value may be truncated if it is too long. + * @param context A {@link Context}. + * @param id The id of the channel. Must be unique per package. The value may be truncated if it's + * too long. + * @param nameResourceId A string resource identifier for the user visible name of the channel. + * You can rename this channel when the system locale changes by listening for the {@link + * Intent#ACTION_LOCALE_CHANGED} broadcast. The recommended maximum length is 40 characters. + * The value may be truncated if it is too long. * @param importance The importance of the channel. This controls how interruptive notifications * posted to this channel are. One of {@link #IMPORTANCE_UNSPECIFIED}, {@link * #IMPORTANCE_NONE}, {@link #IMPORTANCE_MIN}, {@link #IMPORTANCE_LOW}, {@link * #IMPORTANCE_DEFAULT} and {@link #IMPORTANCE_HIGH}. */ public static void createNotificationChannel( - Context context, String id, @StringRes int name, @Importance int importance) { + Context context, String id, @StringRes int nameResourceId, @Importance int importance) { if (Util.SDK_INT >= 26) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); NotificationChannel channel = - new NotificationChannel(id, context.getString(name), importance); + new NotificationChannel(id, context.getString(nameResourceId), importance); notificationManager.createNotificationChannel(channel); } } @@ -92,13 +92,13 @@ public final class NotificationUtil { /** * Post a notification to be shown in the status bar. If a notification with the same id has * already been posted by your application and has not yet been canceled, it will be replaced by - * the updated information. If {@code notification} is null, then cancels a previously shown - * notification. + * the updated information. If {@code notification} is {@code null} then any notification + * previously shown with the specified id will be cancelled. * - * @param context A {@link Context} to retrieve {@link NotificationManager}. - * @param id An identifier for this notification unique within your application. - * @param notification A {@link Notification} object describing what to show the user. If null, - * then cancels a previously shown notification. + * @param context A {@link Context}. + * @param id The notification id. + * @param notification The {@link Notification} to post, or {@code null} to cancel a previously + * shown notification. */ public static void setNotification(Context context, int id, @Nullable Notification notification) { NotificationManager notificationManager = diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java index 123a02d18a..fa560eb644 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java @@ -21,6 +21,7 @@ import static com.google.android.exoplayer2.source.dash.offline.DashDownloadTest import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmpty; import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData; +import android.app.Notification; import android.content.Context; import android.content.Intent; import androidx.annotation.Nullable; @@ -31,6 +32,7 @@ import com.google.android.exoplayer2.offline.DefaultDownloaderFactory; import com.google.android.exoplayer2.offline.DownloadAction; import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadService; +import com.google.android.exoplayer2.offline.DownloadState; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.scheduler.Requirements; @@ -137,6 +139,11 @@ public class DownloadServiceDashTest { protected Scheduler getScheduler() { return null; } + + @Override + protected Notification getForegroundNotification(DownloadState[] downloadStates) { + throw new UnsupportedOperationException(); + } }; dashDownloadService.onCreate(); });