Ensure media notification provider can be updated

Some interactions create a default notification provider if
no custom one is set yet (e.g. setForegroundServiceTimeoutMs).
This means a later call to setMediaNotificationProvider will
silently fail to apply the new provider.

This can be fixed by making the media notification provider
updatable.

Issue: androidx/media#2305
PiperOrigin-RevId: 746428193
(cherry picked from commit 9ca8540f85fba2ff5c8ff151706f36e7b30b46e6)
This commit is contained in:
tonihei 2025-04-11 05:54:48 -07:00
parent d652ba6ae2
commit 24a8185f4d
4 changed files with 58 additions and 10 deletions

View File

@ -72,6 +72,11 @@
player doesn't have `COMMAND_GET_TIMELINE` available while
`COMMAND_GET_CURRENT_MEDIA_ITEM` is available and the wrapped player is
empty ([#2320](https://github.com/androidx/media/issues/2320)).
* Fix a bug where calling
`MediaSessionService.setMediaNotificationProvider` is silently ignored
after other interactions with the service like
`setForegroundServiceTimeoutMs`
([#2305](https://github.com/androidx/media/issues/2305)).
* UI:
* Enable `PlayerSurface` to work with `ExoPlayer.setVideoEffects` and
`CompositionPlayer`.

View File

@ -60,7 +60,7 @@ import java.util.concurrent.TimeoutException;
private static final int MSG_USER_ENGAGED_TIMEOUT = 1;
private final MediaSessionService mediaSessionService;
private final MediaNotification.Provider mediaNotificationProvider;
private final MediaNotification.ActionFactory actionFactory;
private final NotificationManagerCompat notificationManagerCompat;
private final Handler mainHandler;
@ -68,6 +68,7 @@ import java.util.concurrent.TimeoutException;
private final Intent startSelfIntent;
private final Map<MediaSession, ControllerInfo> controllerMap;
private MediaNotification.Provider mediaNotificationProvider;
private int totalNotificationCount;
@Nullable private MediaNotification mediaNotification;
private boolean startedInForeground;
@ -146,6 +147,15 @@ import java.util.concurrent.TimeoutException;
});
}
/**
* Updates the media notification provider.
*
* @param mediaNotificationProvider The {@link MediaNotification.Provider}.
*/
public void setMediaNotificationProvider(MediaNotification.Provider mediaNotificationProvider) {
this.mediaNotificationProvider = mediaNotificationProvider;
}
/**
* Updates the notification.
*

View File

@ -180,9 +180,6 @@ public abstract class MediaSessionService extends Service {
@GuardedBy("lock")
private @MonotonicNonNull MediaNotificationManager mediaNotificationManager;
@GuardedBy("lock")
private MediaNotification.@MonotonicNonNull Provider mediaNotificationProvider;
@GuardedBy("lock")
private @MonotonicNonNull DefaultActionFactory actionFactory;
@ -637,8 +634,6 @@ public abstract class MediaSessionService extends Service {
/**
* Sets the {@link MediaNotification.Provider} to customize notifications.
*
* <p>This should be called before {@link #onCreate()} returns.
*
* <p>This method can be called from any thread.
*/
@UnstableApi
@ -646,7 +641,8 @@ public abstract class MediaSessionService extends Service {
MediaNotification.Provider mediaNotificationProvider) {
checkNotNull(mediaNotificationProvider);
synchronized (lock) {
this.mediaNotificationProvider = mediaNotificationProvider;
getMediaNotificationManager(/* initialMediaNotificationProvider= */ mediaNotificationProvider)
.setMediaNotificationProvider(mediaNotificationProvider);
}
}
@ -679,16 +675,23 @@ public abstract class MediaSessionService extends Service {
}
private MediaNotificationManager getMediaNotificationManager() {
return getMediaNotificationManager(/* initialMediaNotificationProvider= */ null);
}
private MediaNotificationManager getMediaNotificationManager(
@Nullable MediaNotification.Provider initialMediaNotificationProvider) {
synchronized (lock) {
if (mediaNotificationManager == null) {
if (mediaNotificationProvider == null) {
if (initialMediaNotificationProvider == null) {
checkStateNotNull(getBaseContext(), "Accessing service context before onCreate()");
mediaNotificationProvider =
initialMediaNotificationProvider =
new DefaultMediaNotificationProvider.Builder(getApplicationContext()).build();
}
mediaNotificationManager =
new MediaNotificationManager(
/* mediaSessionService= */ this, mediaNotificationProvider, getActionFactory());
/* mediaSessionService= */ this,
initialMediaNotificationProvider,
getActionFactory());
}
return mediaNotificationManager;
}

View File

@ -504,6 +504,36 @@ public class MediaSessionServiceTest {
serviceController.destroy();
}
@Test
public void setMediaNotificationProvider_afterSetForegroundServiceTimeoutMs_usesCustomProvider()
throws TimeoutException {
Context context = ApplicationProvider.getApplicationContext();
ExoPlayer player = new TestExoPlayerBuilder(context).build();
MediaSession session = new MediaSession.Builder(context, player).build();
ServiceController<TestService> serviceController = Robolectric.buildService(TestService.class);
TestService service = serviceController.create().get();
service.setForegroundServiceTimeoutMs(100);
service.setMediaNotificationProvider(
new DefaultMediaNotificationProvider(
service,
/* notificationIdProvider= */ mediaSession -> 2000,
DefaultMediaNotificationProvider.DEFAULT_CHANNEL_ID,
DefaultMediaNotificationProvider.DEFAULT_CHANNEL_NAME_RESOURCE_ID));
service.addSession(session);
// Start a player to trigger notification creation.
player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4"));
player.prepare();
player.play();
runMainLooperUntil(() -> notificationManager.getActiveNotifications().length == 1);
assertThat(getStatusBarNotification(/* notificationId= */ 2000)).isNotNull();
session.release();
player.release();
serviceController.destroy();
}
@Test
public void onStartCommand_mediaButtonEvent_pausedByMediaNotificationController()
throws InterruptedException {