From 9842c273e91eccaaa68e0a72c4c1328446fc778f Mon Sep 17 00:00:00 2001 From: Googler Date: Tue, 4 Oct 2022 07:38:38 +0000 Subject: [PATCH] Allow using different notification IDs for different media sessions When a media service currently produces multiple media sessions, the notification of the second session overwrites the notification of the first one, because all sessions use the same notification ID. When we use different notification IDs for different sessions, multiple media notifications can be up at the same time, which means that they can both be controlled at the same time. PiperOrigin-RevId: 478709069 (cherry picked from commit 7783c6e4f71943b543ddb569b120bdbbab362b96) --- .../DefaultMediaNotificationProvider.java | 39 +++++++++++-- .../DefaultMediaNotificationProviderTest.java | 56 +++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java b/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java index d6b15c0151..29639baff4 100644 --- a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java +++ b/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java @@ -119,7 +119,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi /** A builder for {@link DefaultMediaNotificationProvider} instances. */ public static final class Builder { private final Context context; - private int notificationId; + private NotificationIdProvider notificationIdProvider; private String channelId; @StringRes private int channelNameResourceId; private BitmapLoader bitmapLoader; @@ -132,7 +132,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi */ public Builder(Context context) { this.context = context; - notificationId = DEFAULT_NOTIFICATION_ID; + notificationIdProvider = session -> DEFAULT_NOTIFICATION_ID; channelId = DEFAULT_CHANNEL_ID; channelNameResourceId = DEFAULT_CHANNEL_NAME_RESOURCE_ID; bitmapLoader = new SimpleBitmapLoader(); @@ -142,12 +142,30 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi * Sets the {@link MediaNotification#notificationId} used for the created notifications. By * default, this is set to {@link #DEFAULT_NOTIFICATION_ID}. * + *

Overwrites anything set in {@link #setNotificationIdProvider(NotificationIdProvider)}. + * * @param notificationId The notification ID. * @return This builder. */ @CanIgnoreReturnValue public Builder setNotificationId(int notificationId) { - this.notificationId = notificationId; + this.notificationIdProvider = session -> notificationId; + return this; + } + + /** + * Sets the provider for the {@link MediaNotification#notificationId} used for the created + * notifications. By default, this is set to a provider that always returns {@link + * #DEFAULT_NOTIFICATION_ID}. + * + *

Overwrites anything set in {@link #setNotificationId(int)}. + * + * @param notificationIdProvider The notification ID provider. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setNotificationIdProvider(NotificationIdProvider notificationIdProvider) { + this.notificationIdProvider = notificationIdProvider; return this; } @@ -201,6 +219,16 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi } } + /** + * Provides notification IDs for posting media notifications for given media sessions. + * + * @see Builder#setNotificationIdProvider(NotificationIdProvider) + */ + public interface NotificationIdProvider { + /** Returns the notification ID for the media notification of the given session. */ + int getNotificationId(MediaSession mediaSession); + } + /** * An extras key that can be used to define the index of a {@link CommandButton} in {@linkplain * Notification.MediaStyle#setShowActionsInCompactView(int...) compact view}. @@ -226,7 +254,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi private static final String TAG = "NotificationProvider"; private final Context context; - private final int notificationId; + private final NotificationIdProvider notificationIdProvider; private final String channelId; @StringRes private final int channelNameResourceId; private final NotificationManager notificationManager; @@ -241,7 +269,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi private DefaultMediaNotificationProvider(Builder builder) { this.context = builder.context; - this.notificationId = builder.notificationId; + this.notificationIdProvider = builder.notificationIdProvider; this.channelId = builder.channelId; this.channelNameResourceId = builder.channelNameResourceId; this.bitmapLoader = builder.bitmapLoader; @@ -265,6 +293,7 @@ public class DefaultMediaNotificationProvider implements MediaNotification.Provi Player player = mediaSession.getPlayer(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); + int notificationId = notificationIdProvider.getNotificationId(mediaSession); MediaStyle mediaStyle = new MediaStyle(); int[] compactViewIndices = diff --git a/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java b/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java index a97777e3be..4b444e29bd 100644 --- a/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java +++ b/libraries/session/src/test/java/androidx/media3/session/DefaultMediaNotificationProviderTest.java @@ -15,6 +15,9 @@ */ package androidx.media3.session; +import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.session.DefaultMediaNotificationProvider.DEFAULT_CHANNEL_ID; +import static androidx.media3.session.DefaultMediaNotificationProvider.DEFAULT_NOTIFICATION_ID; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -449,6 +452,33 @@ public class DefaultMediaNotificationProviderTest { verifyNoInteractions(mockOnNotificationChangedCallback1); } + @Test + public void provider_idsNotSpecified_usesDefaultIds() { + Context context = ApplicationProvider.getApplicationContext(); + DefaultMediaNotificationProvider defaultMediaNotificationProvider = + new DefaultMediaNotificationProvider.Builder(context).build(); + MediaSession mockMediaSession = createMockMediaSessionForNotification(MediaMetadata.EMPTY); + DefaultActionFactory defaultActionFactory = + new DefaultActionFactory(Robolectric.setupService(TestService.class)); + + MediaNotification notification = + defaultMediaNotificationProvider.createNotification( + mockMediaSession, + ImmutableList.of(), + defaultActionFactory, + mock(MediaNotification.Provider.Callback.class)); + + assertThat(notification.notificationId).isEqualTo(DEFAULT_NOTIFICATION_ID); + assertThat(notification.notification.getChannelId()).isEqualTo(DEFAULT_CHANNEL_ID); + ShadowNotificationManager shadowNotificationManager = + Shadows.shadowOf( + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); + assertHasNotificationChannel( + shadowNotificationManager.getNotificationChannels(), + /* channelId= */ DEFAULT_CHANNEL_ID, + /* channelName= */ context.getString(R.string.default_notification_channel_name)); + } + @Test public void provider_withCustomIds_notificationsUseCustomIds() { Context context = ApplicationProvider.getApplicationContext(); @@ -480,6 +510,32 @@ public class DefaultMediaNotificationProviderTest { /* channelName= */ context.getString(R.string.media3_controls_play_description)); } + @Test + public void provider_withCustomNotificationIdProvider_notificationsUseCustomId() { + Context context = ApplicationProvider.getApplicationContext(); + DefaultMediaNotificationProvider defaultMediaNotificationProvider = + new DefaultMediaNotificationProvider.Builder(context) + .setNotificationIdProvider( + session -> { + checkNotNull(session); + return 3; + }) + .setChannelName(/* channelNameResourceId= */ R.string.media3_controls_play_description) + .build(); + MediaSession mockMediaSession = createMockMediaSessionForNotification(MediaMetadata.EMPTY); + DefaultActionFactory defaultActionFactory = + new DefaultActionFactory(Robolectric.setupService(TestService.class)); + + MediaNotification notification = + defaultMediaNotificationProvider.createNotification( + mockMediaSession, + ImmutableList.of(), + defaultActionFactory, + mock(MediaNotification.Provider.Callback.class)); + + assertThat(notification.notificationId).isEqualTo(3); + } + @Test public void setCustomSmallIcon_notificationUsesCustomSmallIcon() { Context context = ApplicationProvider.getApplicationContext();