Skip to content

Commit 8064c6d

Browse files
toniheiicbaker
authored andcommitted
Declare foreground service type for DownloadService
This ensures the DownloadService stays functional on Android 14 where defining this type is required. On Android 14 and above, the app also needs to define the DATA_SYNC permission, which is added to the demo app as well. In the future, this service type will no longer be supported and DownloadService needs to be rewritten with another background scheduling framework. Issue: google/ExoPlayer#11239 PiperOrigin-RevId: 548994842
1 parent 035934c commit 8064c6d

File tree

5 files changed

+82
-33
lines changed

5 files changed

+82
-33
lines changed

RELEASENOTES.md

+5
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@
115115
playback if a suitable device is connected within a configurable timeout
116116
(default is 5 minutes).
117117
* Downloads:
118+
* Declare "data sync" foreground service type for `DownloadService` for
119+
Android 14 compatibility. When using this service, the app also needs to
120+
add `dataSync` as `foregroundServiceType` in the manifest and add the
121+
`FOREGROUND_SERVICE_DATA_SYNC` permission
122+
([#11239](https://github.com/google/ExoPlayer/issues/11239)).
118123
* OkHttp Extension:
119124
* Cronet Extension:
120125
* RTMP Extension:

demos/main/src/main/AndroidManifest.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
2424
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
2525
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
26+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
2627
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
2728

2829
<uses-feature android:name="android.software.leanback" android:required="false"/>
@@ -94,7 +95,8 @@
9495
</activity>
9596

9697
<service android:name=".DemoDownloadService"
97-
android:exported="false">
98+
android:exported="false"
99+
android:foregroundServiceType="dataSync">
98100
<intent-filter>
99101
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
100102
<category android:name="android.intent.category.DEFAULT"/>

libraries/common/src/main/java/androidx/media3/common/util/Util.java

+57
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import android.Manifest.permission;
3737
import android.annotation.SuppressLint;
3838
import android.app.Activity;
39+
import android.app.Notification;
40+
import android.app.Service;
3941
import android.app.UiModeManager;
4042
import android.content.BroadcastReceiver;
4143
import android.content.ComponentName;
@@ -298,6 +300,36 @@ public static ComponentName startForegroundService(Context context, Intent inten
298300
}
299301
}
300302

303+
/**
304+
* Sets the notification required for a foreground service.
305+
*
306+
* @param service The foreground {@link Service}.
307+
* @param notificationId The notification id.
308+
* @param notification The {@link Notification}.
309+
* @param foregroundServiceType The foreground service type defined in {@link
310+
* android.content.pm.ServiceInfo}.
311+
* @param foregroundServiceManifestType The required foreground service type string for the {@code
312+
* <service>} element in the manifest.
313+
*/
314+
@UnstableApi
315+
public static void setForegroundServiceNotification(
316+
Service service,
317+
int notificationId,
318+
Notification notification,
319+
int foregroundServiceType,
320+
String foregroundServiceManifestType) {
321+
if (Util.SDK_INT >= 29) {
322+
Api29.startForeground(
323+
service,
324+
notificationId,
325+
notification,
326+
foregroundServiceType,
327+
foregroundServiceManifestType);
328+
} else {
329+
service.startForeground(notificationId, notification);
330+
}
331+
}
332+
301333
/**
302334
* Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE}
303335
* permission read the specified {@link Uri}s, requesting the permission if necessary.
@@ -3356,4 +3388,29 @@ public static Drawable getDrawable(Context context, Resources resources, @Drawab
33563388
return resources.getDrawable(res, context.getTheme());
33573389
}
33583390
}
3391+
3392+
@RequiresApi(29)
3393+
private static class Api29 {
3394+
3395+
@DoNotInline
3396+
public static void startForeground(
3397+
Service mediaSessionService,
3398+
int notificationId,
3399+
Notification notification,
3400+
int foregroundServiceType,
3401+
String foregroundServiceManifestType) {
3402+
try {
3403+
// startForeground() will throw if the service's foregroundServiceType is not defined.
3404+
mediaSessionService.startForeground(notificationId, notification, foregroundServiceType);
3405+
} catch (RuntimeException e) {
3406+
Log.e(
3407+
TAG,
3408+
"The service must be declared with a foregroundServiceType that includes "
3409+
+ foregroundServiceManifestType);
3410+
throw e;
3411+
}
3412+
}
3413+
3414+
private Api29() {}
3415+
}
33593416
}

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/offline/DownloadService.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
import static androidx.media3.exoplayer.offline.Download.STOP_REASON_NONE;
1919

20+
import android.annotation.SuppressLint;
2021
import android.app.Notification;
2122
import android.app.NotificationManager;
2223
import android.app.Service;
2324
import android.content.Context;
2425
import android.content.Intent;
26+
import android.content.pm.ServiceInfo;
2527
import android.os.Handler;
2628
import android.os.IBinder;
2729
import android.os.Looper;
@@ -910,14 +912,20 @@ public void invalidate() {
910912
}
911913
}
912914

915+
@SuppressLint("InlinedApi") // Using compile time constant FOREGROUND_SERVICE_TYPE_DATA_SYNC
913916
private void update() {
914917
DownloadManager downloadManager =
915918
Assertions.checkNotNull(downloadManagerHelper).downloadManager;
916919
List<Download> downloads = downloadManager.getCurrentDownloads();
917920
@RequirementFlags int notMetRequirements = downloadManager.getNotMetRequirements();
918921
Notification notification = getForegroundNotification(downloads, notMetRequirements);
919922
if (!notificationDisplayed) {
920-
startForeground(notificationId, notification);
923+
Util.setForegroundServiceNotification(
924+
/* service= */ DownloadService.this,
925+
notificationId,
926+
notification,
927+
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
928+
"dataSync");
921929
notificationDisplayed = true;
922930
} else {
923931
// Update the notification via NotificationManager rather than by repeatedly calling

libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java

+8-31
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static android.app.Service.STOP_FOREGROUND_REMOVE;
2020
import static androidx.media3.common.util.Assertions.checkStateNotNull;
2121

22+
import android.annotation.SuppressLint;
2223
import android.app.Notification;
2324
import android.content.Intent;
2425
import android.content.pm.ServiceInfo;
@@ -346,14 +347,15 @@ public void onDisconnected(MediaController controller) {
346347
}
347348
}
348349

350+
@SuppressLint("InlinedApi") // Using compile time constant FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
349351
private void startForeground(MediaNotification mediaNotification) {
350352
ContextCompat.startForegroundService(mediaSessionService, startSelfIntent);
351-
if (Util.SDK_INT >= 29) {
352-
Api29.startForeground(mediaSessionService, mediaNotification);
353-
} else {
354-
mediaSessionService.startForeground(
355-
mediaNotification.notificationId, mediaNotification.notification);
356-
}
353+
Util.setForegroundServiceNotification(
354+
mediaSessionService,
355+
mediaNotification.notificationId,
356+
mediaNotification.notification,
357+
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
358+
"mediaPlayback");
357359
startedInForeground = true;
358360
}
359361

@@ -380,29 +382,4 @@ public static void stopForeground(MediaSessionService service, boolean removeNot
380382

381383
private Api24() {}
382384
}
383-
384-
@RequiresApi(29)
385-
private static class Api29 {
386-
387-
@DoNotInline
388-
public static void startForeground(
389-
MediaSessionService mediaSessionService, MediaNotification mediaNotification) {
390-
try {
391-
// startForeground() will throw if the service's foregroundServiceType is not defined in the
392-
// manifest to include mediaPlayback.
393-
mediaSessionService.startForeground(
394-
mediaNotification.notificationId,
395-
mediaNotification.notification,
396-
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
397-
} catch (RuntimeException e) {
398-
Log.e(
399-
TAG,
400-
"The service must be declared with a foregroundServiceType that includes "
401-
+ " mediaPlayback");
402-
throw e;
403-
}
404-
}
405-
406-
private Api29() {}
407-
}
408385
}

0 commit comments

Comments
 (0)