My Android app is used to remotely control a media player (Winamp on a PC). To allow the user to control the remote player even when the Android app is not currently active, it uses a MediaSession that is associated with a background service and a system notification. The system notification uses the DecoratedMediaCustomViewStyle style and I set the custom view using the setCustomContentView() and setCustomBigContentView() methods. I call setPlaybackToRemote() on the MediaSession with a VolumeProviderCompat. This is an important distinction because the media is not played on the Android device, but on a remote device. And no, it's not Chromecast, something completely different (Winamp on a PC using a custom protocol).
This all works fine on versions of Android prior to 11 (API 30). But on Android 11, the custom view (both "regular" and "big") are completely ignored. Here are screen shots of the "big" notification, top on API 27 (same as API 28 and 29) and followed by API 30.
Note that the top one doesn't mention "Ampwifi" twice, and there's an "X" button on the right side. This "X" button is important as it allows the user to close the notification and shutdown the background service.
I searched the docs and Android 11 change logs and didn't find anything about this. I also have an older AVD image from Android "R" pre-release where it also worked like it did in API 29 and older. I'm wondering if this is indeed a bug with Android? If not, has anyone else encountered this and anyone have any suggestions on how to get around this?
One thing I did try was to simply not associate the MediaSession with the notification. That actually restores the custom view, but I lose all the benefits of the MediaSession (bluetooth/Google Assistant integration and auto-coloring of the notification actions buttons). So fixing this would be really nice.
Here's some code:
/////
final ConnectionProfile profile = mSettings.getActiveConnectionProfile();
final String profileName = profile != null ? profile.name : "";
final RemoteViews contentView = getContent(profileName);
final RemoteViews bigContentView = getBigContent(profileName);
final NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(ServiceBackgroundMediapPlayer.this, NotificationChannelBackgroundMediaPlayerServiceId)
.setColorized(true)
.setOngoing(true)
.setAutoCancel(false)
.setContentIntent(getDefaultIntent())
.setDeleteIntent(getDeleteIntent())
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(getBitmap())
.setContentTitle("Ampwifi")
.setContentText(profileName)
.addAction(R.drawable.ic_media_previous, null, MediaButtonReceiver.buildMediaButtonPendingIntent(ServiceBackgroundMediapPlayer.this, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS))
.addAction(R.drawable.ic_media_play, null, MediaButtonReceiver.buildMediaButtonPendingIntent(ServiceBackgroundMediapPlayer.this, PlaybackStateCompat.ACTION_PLAY))
.addAction(R.drawable.ic_media_pause, null, MediaButtonReceiver.buildMediaButtonPendingIntent(ServiceBackgroundMediapPlayer.this, PlaybackStateCompat.ACTION_PAUSE))
.addAction(R.drawable.ic_media_stop, null, MediaButtonReceiver.buildMediaButtonPendingIntent(ServiceBackgroundMediapPlayer.this, PlaybackStateCompat.ACTION_STOP))
.addAction(R.drawable.ic_media_next, null, MediaButtonReceiver.buildMediaButtonPendingIntent(ServiceBackgroundMediapPlayer.this, PlaybackStateCompat.ACTION_SKIP_TO_NEXT))
.setStyle(getStyle())
.setCustomContentView(contentView)
.setCustomBigContentView(bigContentView)
.setOnlyAlertOnce(true);
mNotification = notificationBuilder.build();
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(R.id.ServiceBackgroundMediapPlayerNotification, mNotification);
/////
public NotificationCompat.Style getStyle() {
return new androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle()
.setShowCancelButton(true)
.setCancelButtonIntent(getDeleteIntent())
.setShowActionsInCompactView(1, 2, 4)
.setMediaSession(getMediaSessionToken());
}
public RemoteViews getContent(String title) {
final RemoteViews layout = new RemoteViews("com.blitterhead.ampwifi", R.layout.notification_media);
if (layout != null) {
layout.setTextViewText(R.id.title, title);
}
return layout;
}
public RemoteViews getBigContent(String title) {
final RemoteViews layout = new RemoteViews("com.blitterhead.ampwifi", R.layout.notification_media_big);
if (layout != null) {
layout.setTextViewText(R.id.title, title);
layout.setOnClickPendingIntent(R.id.dismiss, getDeleteIntent());
}
return layout;
}
private MediaSessionCompat.Token getMediaSessionToken() {
if (_mediaSessionToken == null) {
PlaybackStateCompat playbackState = mPlaybackStateBuilder
.setActiveQueueItemId(1)
.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS | PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1.0f)
.build();
ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
mMediaSession = new MediaSessionCompat(ServiceBackgroundMediapPlayer.this, MediaSessionTag, mediaButtonReceiver, null);
mMediaSession.setPlaybackState(playbackState);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setMetadata(getMetaData());
mMediaSession.setCallback(getMediaSessionCallback());
mMediaSession.setActive(true);
if (mSettings.getBackgroundMediaVolume()) {
mMediaSession.setPlaybackToRemote(getVolumeProvider());
}
_mediaSessionToken = mMediaSession.getSessionToken();
setSessionToken(_mediaSessionToken);
}
return _mediaSessionToken;
}