diff --git a/RELEASENOTES.md b/RELEASENOTES.md index eb04bdb5e4..40a14057db 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -14,6 +14,10 @@ * Move `Player.addListener(EventListener)` and `Player.removeListener(EventListener)` out of `Player` into subclasses. * Android 12 compatibility: + * Keep `DownloadService` started and in the foreground whilst waiting for + requirements to be met on Android 12. This is necessary due to new + [foreground service launch restrictions](https://developer.android.com/about/versions/12/foreground-services). + `DownloadService.getScheduler` will not be called on Android 12 devices. * Disable platform transcoding when playing content URIs on Android 12. * Add `ExoPlayer.setVideoChangeFrameRateStrategy` to allow disabling of calls from the player to `Surface.setFrameRate`. This is useful for @@ -23,6 +27,14 @@ * `SubtitleView` no longer implements `TextOutput`. `SubtitleView` implements `Player.Listener`, so can be registered to a player with `Player.addListener`. +* Downloads and caching: + * Modify `DownloadService` behavior when `DownloadService.getScheduler` + returns `null`, or returns a `Scheduler` that does not support the + requirements for downloads to continue. In both cases, `DownloadService` + will now remain started and in the foreground whilst waiting for + requirements to be met. + * Modify `DownlaodService` behavior when running on Android 12 and above. + See the "Android 12 compatibility" section above. * RTSP: * Support RFC4566 SDP attribute field grammar ([#9430](https://github.com/google/ExoPlayer/issues/9430)). 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 f0f22af1ac..ea9e6f8569 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 @@ -28,6 +28,7 @@ import android.os.Looper; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import com.google.android.exoplayer2.scheduler.Requirements; +import com.google.android.exoplayer2.scheduler.Requirements.RequirementFlags; import com.google.android.exoplayer2.scheduler.Scheduler; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; @@ -36,6 +37,7 @@ import com.google.android.exoplayer2.util.Util; import java.util.HashMap; import java.util.List; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** A {@link Service} for downloading media. */ public abstract class DownloadService extends Service { @@ -179,8 +181,7 @@ public abstract class DownloadService extends Service { @StringRes private final int channelNameResourceId; @StringRes private final int channelDescriptionResourceId; - private @MonotonicNonNull Scheduler scheduler; - private @MonotonicNonNull DownloadManager downloadManager; + private @MonotonicNonNull DownloadManagerHelper downloadManagerHelper; private int lastStartId; private boolean startedInForeground; private boolean taskRemoved; @@ -191,8 +192,7 @@ public abstract class DownloadService extends Service { * Creates a DownloadService. * *
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. + * service will only ever run in the background, and no foreground notification will be displayed. * *
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 @@ -583,22 +583,19 @@ public abstract class DownloadService extends Service { @Nullable DownloadManagerHelper downloadManagerHelper = downloadManagerHelpers.get(clazz); if (downloadManagerHelper == null) { boolean foregroundAllowed = foregroundNotificationUpdater != null; - @Nullable Scheduler scheduler = foregroundAllowed ? getScheduler() : null; - if (scheduler != null) { - this.scheduler = scheduler; - } - downloadManager = getDownloadManager(); + // See https://developer.android.com/about/versions/12/foreground-services. + boolean canStartForegroundServiceFromBackground = Util.SDK_INT < 31; + @Nullable + Scheduler scheduler = + foregroundAllowed && canStartForegroundServiceFromBackground ? getScheduler() : null; + DownloadManager downloadManager = getDownloadManager(); downloadManager.resumeDownloads(); downloadManagerHelper = new DownloadManagerHelper( getApplicationContext(), downloadManager, foregroundAllowed, scheduler, clazz); downloadManagerHelpers.put(clazz, downloadManagerHelper); - } else { - if (downloadManagerHelper.scheduler != null) { - scheduler = downloadManagerHelper.scheduler; - } - downloadManager = downloadManagerHelper.downloadManager; } + this.downloadManagerHelper = downloadManagerHelper; downloadManagerHelper.attachService(this); } @@ -618,7 +615,8 @@ public abstract class DownloadService extends Service { if (intentAction == null) { intentAction = ACTION_INIT; } - DownloadManager downloadManager = Assertions.checkNotNull(this.downloadManager); + DownloadManager downloadManager = + Assertions.checkNotNull(downloadManagerHelper).downloadManager; switch (intentAction) { case ACTION_INIT: case ACTION_RESTART: @@ -666,21 +664,6 @@ public abstract class DownloadService extends Service { if (requirements == null) { Log.e(TAG, "Ignored SET_REQUIREMENTS: Missing " + KEY_REQUIREMENTS + " extra"); } else { - if (scheduler != null) { - Requirements supportedRequirements = scheduler.getSupportedRequirements(requirements); - if (!supportedRequirements.equals(requirements)) { - Log.w( - TAG, - "Ignoring requirements not supported by the Scheduler: " - + (requirements.getRequirements() ^ supportedRequirements.getRequirements())); - // We need to make sure DownloadManager only uses requirements supported by the - // Scheduler. If we don't do this, DownloadManager can report itself as idle due to an - // unmet requirement that the Scheduler doesn't support. This can then lead to the - // service being destroyed, even though the Scheduler won't be able to restart it when - // the requirement is subsequently met. - requirements = supportedRequirements; - } - } downloadManager.setRequirements(requirements); } break; @@ -696,7 +679,7 @@ public abstract class DownloadService extends Service { isStopped = false; if (downloadManager.isIdle()) { - stop(); + onIdle(); } return START_STICKY; } @@ -709,9 +692,7 @@ public abstract class DownloadService extends Service { @Override public void onDestroy() { isDestroyed = true; - DownloadManagerHelper downloadManagerHelper = - Assertions.checkNotNull(downloadManagerHelpers.get(getClass())); - downloadManagerHelper.detachService(this); + Assertions.checkNotNull(downloadManagerHelper).detachService(this); if (foregroundNotificationUpdater != null) { foregroundNotificationUpdater.stopPeriodicUpdates(); } @@ -733,14 +714,35 @@ public abstract class DownloadService extends Service { protected abstract DownloadManager getDownloadManager(); /** - * Returns a {@link Scheduler} to restart the service when requirements allowing downloads to take - * place are met. If {@code null}, the service will only be restarted if the process is still in - * memory when the requirements are met. + * Returns a {@link Scheduler} to restart the service when requirements for downloads to continue + * are met. * - *
This method is not called for services whose {@code foregroundNotificationId} is set to - * {@link #FOREGROUND_NOTIFICATION_ID_NONE}. Such services will only be restarted if the process - * is still in memory and considered non-idle, meaning that it's either in the foreground or was - * backgrounded within the last few minutes. + *
This method is not called on all devices or for all service configurations. When it is + * called, it's called only once in the life cycle of the process. If a service has unfinished + * downloads that cannot make progress due to unmet requirements, it will behave according to the + * first matching case below: + * + *