diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f9ce2de3a1..5ebf5ef7ad 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -115,6 +115,11 @@ playback if a suitable device is connected within a configurable timeout (default is 5 minutes). * Downloads: + * Declare "data sync" foreground service type for `DownloadService` for + Android 14 compatibility. When using this service, the app also needs to + add `dataSync` as `foregroundServiceType` in the manifest and add the + `FOREGROUND_SERVICE_DATA_SYNC` permission + ([#11239](https://github.com/google/ExoPlayer/issues/11239)). * OkHttp Extension: * Cronet Extension: * RTMP Extension: diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 10ce807160..63266bfbd4 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ + @@ -94,7 +95,8 @@ + android:exported="false" + android:foregroundServiceType="dataSync"> diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java index 6d53487af0..8e76c0bada 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java @@ -36,6 +36,8 @@ import static java.lang.Math.min; import android.Manifest.permission; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Notification; +import android.app.Service; import android.app.UiModeManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -298,6 +300,36 @@ public final class Util { } } + /** + * Sets the notification required for a foreground service. + * + * @param service The foreground {@link Service}. + * @param notificationId The notification id. + * @param notification The {@link Notification}. + * @param foregroundServiceType The foreground service type defined in {@link + * android.content.pm.ServiceInfo}. + * @param foregroundServiceManifestType The required foreground service type string for the {@code + * } element in the manifest. + */ + @UnstableApi + public static void setForegroundServiceNotification( + Service service, + int notificationId, + Notification notification, + int foregroundServiceType, + String foregroundServiceManifestType) { + if (Util.SDK_INT >= 29) { + Api29.startForeground( + service, + notificationId, + notification, + foregroundServiceType, + foregroundServiceManifestType); + } else { + service.startForeground(notificationId, notification); + } + } + /** * Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE} * permission read the specified {@link Uri}s, requesting the permission if necessary. @@ -3356,4 +3388,29 @@ public final class Util { return resources.getDrawable(res, context.getTheme()); } } + + @RequiresApi(29) + private static class Api29 { + + @DoNotInline + public static void startForeground( + Service mediaSessionService, + int notificationId, + Notification notification, + int foregroundServiceType, + String foregroundServiceManifestType) { + try { + // startForeground() will throw if the service's foregroundServiceType is not defined. + mediaSessionService.startForeground(notificationId, notification, foregroundServiceType); + } catch (RuntimeException e) { + Log.e( + TAG, + "The service must be declared with a foregroundServiceType that includes " + + foregroundServiceManifestType); + throw e; + } + } + + private Api29() {} + } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadService.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadService.java index b66d7996b5..9f80fa02e5 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadService.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadService.java @@ -17,11 +17,13 @@ package androidx.media3.exoplayer.offline; import static androidx.media3.exoplayer.offline.Download.STOP_REASON_NONE; +import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -910,6 +912,7 @@ public abstract class DownloadService extends Service { } } + @SuppressLint("InlinedApi") // Using compile time constant FOREGROUND_SERVICE_TYPE_DATA_SYNC private void update() { DownloadManager downloadManager = Assertions.checkNotNull(downloadManagerHelper).downloadManager; @@ -917,7 +920,12 @@ public abstract class DownloadService extends Service { @RequirementFlags int notMetRequirements = downloadManager.getNotMetRequirements(); Notification notification = getForegroundNotification(downloads, notMetRequirements); if (!notificationDisplayed) { - startForeground(notificationId, notification); + Util.setForegroundServiceNotification( + /* service= */ DownloadService.this, + notificationId, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC, + "dataSync"); notificationDisplayed = true; } else { // Update the notification via NotificationManager rather than by repeatedly calling diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java b/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java index a65bbf2837..a0c9e065a9 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java @@ -19,6 +19,7 @@ import static android.app.Service.STOP_FOREGROUND_DETACH; import static android.app.Service.STOP_FOREGROUND_REMOVE; import static androidx.media3.common.util.Assertions.checkStateNotNull; +import android.annotation.SuppressLint; import android.app.Notification; import android.content.Intent; import android.content.pm.ServiceInfo; @@ -346,14 +347,15 @@ import java.util.concurrent.TimeoutException; } } + @SuppressLint("InlinedApi") // Using compile time constant FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK private void startForeground(MediaNotification mediaNotification) { ContextCompat.startForegroundService(mediaSessionService, startSelfIntent); - if (Util.SDK_INT >= 29) { - Api29.startForeground(mediaSessionService, mediaNotification); - } else { - mediaSessionService.startForeground( - mediaNotification.notificationId, mediaNotification.notification); - } + Util.setForegroundServiceNotification( + mediaSessionService, + mediaNotification.notificationId, + mediaNotification.notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, + "mediaPlayback"); startedInForeground = true; } @@ -380,29 +382,4 @@ import java.util.concurrent.TimeoutException; private Api24() {} } - - @RequiresApi(29) - private static class Api29 { - - @DoNotInline - public static void startForeground( - MediaSessionService mediaSessionService, MediaNotification mediaNotification) { - try { - // startForeground() will throw if the service's foregroundServiceType is not defined in the - // manifest to include mediaPlayback. - mediaSessionService.startForeground( - mediaNotification.notificationId, - mediaNotification.notification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); - } catch (RuntimeException e) { - Log.e( - TAG, - "The service must be declared with a foregroundServiceType that includes " - + " mediaPlayback"); - throw e; - } - } - - private Api29() {} - } }