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 7783c6e4f7)
This commit is contained in:
Googler 2022-10-04 07:38:38 +00:00 committed by microkatz
parent c8917b50e6
commit 9842c273e9
2 changed files with 90 additions and 5 deletions

View file

@ -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}.
*
* <p>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}.
*
* <p>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 =

View file

@ -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();