Search code examples
androidandroid-notificationsforeground-service

How to keep my foreground service notification (MediaStyle) on top like Google Play Music?


So, I have a PlayerService, which is basically an audio player. It is foreground and uses MediaStyle with custom actions (buttons in the notification) like prev, play/pause, next and close.

The problem is that when I launch some other audio app like Google Play Music, it's notification is always above mine! If I launch 2 other audio apps with mine, when I click on the play icon in their notifications, that notification goes to the top and stay there. While mine is always second place from the top. It doesn't matter if my application is playing or not.

Tried with all versions of API. But let's say I mostly use it with pre-Oreo, so we can forget about NotificationChannel stuff.

Also tried to use MediaSession, and set PRIORITY_MAX, but still no luck. By the way, AudioFocus and all other things work well. Notification priority is the only issue.

getNotification() method:

return NotificationCompat.Builder( this, CHANNEL_ID )
            .setTicker(ticker)
            .setContentTitle(title)
            .setContentText(text)
            .setSmallIcon(icon)
            .setLargeIcon(icon)
            .setContentIntent(notificationPendingIntent)
            .addAction(prevAction)
            .addAction(playAction)
            .addAction(nextAction)
            .addAction(stopAction)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setStyle(androidx.media.app.NotificationCompat.MediaStyle()
                .setShowActionsInCompactView(0, 1, 2)
            )
            .setColor(color)
            .setWhen(System.currentTimeMillis())
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .build()

Update it with:

notificationManager.notify( 1, getNotification() )

Action example:

NotificationCompat.Action(
        R.drawable.play,
        "Play",
        PendingIntent.getBroadcast(
            this,
            REQUEST_CODE,
            playIntent,
            PendingIntent.FLAG_UPDATE_CURRENT
        )
    ) 

Solution

  • So, the problem was somewhere in MediaSession API. I tried to make it work with the help of some tutorials and Google documentation but failed. Some of them too hard or just overkill (like UniversalMusicPlayer on Github). I don't need to implement all this stuff with Android Auto etc.

    Finally, I found Exoplayer MediaSession extension. So there is how I do it.

    build.gradle:

        implementation 'com.google.android.exoplayer:extension-mediasession:2.10.1'
    

    Manifest:

        <receiver android:name="androidx.media.session.MediaButtonReceiver">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_BUTTON"/>
            </intent-filter>
        </receiver>
    

    PlayerService.kt:

        ...
        private val player by lazy { ExoPlayerFactory.newSimpleInstance( this ) }
        private val mediaSession by lazy { MediaSessionCompat(this, TAG) }
        private val connector by lazy { MediaSessionConnector(mediaSession) }
        ...
        override fun onCreate() {
            super.onCreate()
    
            mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS or MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS )
            connector.setPlayer(player)
        }
        ...
        private fun getNotification(): Notification {
            return NotificationCompat.Builder( this, CHANNEL_ID )
                ...
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setStyle(androidx.media.app.NotificationCompat.MediaStyle()
                    .setMediaSession(mediaSession.sessionToken)
                    .setShowActionsInCompactView(0, 1, 2)
                )
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .build()
        }
    

    Most important part here is the connector stuff. Other code just like for any other foreground or audio service. But MediaSessionConnector is a Thing here. Just a couple of lines, but handles all stuff for you. You don't have to write some other Media*** code! Except for MediaBrowser things, but it's not my case.