Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: NotificationCompat.MediaStyle action buttons don't do anything

I've got a simple Android app containing one Activity and a Service that derives from MediaBrowserServiceCompat. I've successfully gotten it set up to play audio from my main activity by using MediaBrowserCompat and MediaControllerCompat. It can even play and pause the audio from my Bluetooth headphones. All good.

My challenge concerns the NotificationCompat.MediaStyle notification that appears on the lock screen and in the notifications tray. The notification appears properly. However, when I add buttons using addAction() and MediaButtonReceiver.buildMediaButtonPendingIntent, they don't do anything. If I instead add a dummy PendingIntent that just launches my main activity, that works fine.

Here's my code to generate the notification (apologies, this is C# running in Xamarin, so the casing and names will be slightly different from what you might expect). This is inside my service class.

var builder = new NotificationCompat.Builder(this, CHANNEL_ID)
    .SetVisibility(NotificationCompat.VisibilityPublic)
    .SetSmallIcon(Resource.Drawable.ic_launcher)
    .SetContentTitle("Title")
    .SetContentText("Content")
    .SetSubText("Subtext")
    .SetLargeIcon(icon)
    .SetColor(Android.Graphics.Color.DarkOrange)

    .SetContentIntent(intent)
    .SetDeleteIntent(MediaButtonReceiver.BuildMediaButtonPendingIntent(this, PlaybackStateCompat.ActionStop))

    .AddAction(new NotificationCompat.Action(
        Resource.Drawable.ic_pause, "Pause",
        MediaButtonReceiver.BuildMediaButtonPendingIntent(this, PlaybackStateCompat.ActionPause)))

    .SetStyle(new Android.Support.V4.Media.App.NotificationCompat.MediaStyle()
        .SetShowActionsInCompactView(0)
        .SetMediaSession(this.mediaSession.SessionToken)
        .SetShowCancelButton(true)
        .SetCancelButtonIntent(MediaButtonReceiver.BuildMediaButtonPendingIntent(this, PlaybackStateCompat.ActionStop))
    );

this.StartForeground(NOTIFICATION_ID, builder.Build());

Here's what I have looked at so far to try to solve this:

  • When I start playback, I use MediaSession.setActive(true)
  • Each time I start and stop playback, I set the appropriate actions in PlaybackStateCompat
  • I have the session token set correctly.
  • I do not have anything set up as a MediaButtonReceiver in my manifest, nor have I set anything up to handle android.intent.action.MEDIA_BUTTON, because I am targeting Android 5.0 and higher and using the *Compat classes, and my understanding is that that is no longer necessary.

I know that media button events are being routed properly to my app, since my Bluetooth headphone buttons work. I tried it in my car and it works there too. It's just the buttons in the notification that won't work. I'm expecting them to generate calls to the appropriate methods of MediaSessionCompat.Callback. Is this incorrect? What am I doing wrong here?

I would be grateful for any pointers.


UPDATE: I got it working. I needed to add the following inside the <application> node of the manifest:

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

...and the following inside the node of the Service that implements MediaBrowserServiceCompat:

<intent-filter>
    <action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>

I'm still a little confused about why this was necessary, since button presses from my Bluetooth headphones and car infotainment system were routed to the app just fine. More importantly, Google says:

If you already have a MediaBrowserServiceCompat in your app, MediaButtonReceiver will deliver the received key events to the MediaBrowserServiceCompat by default. You can handle them in your MediaSessionCompat.Callback.

They gave this as an alternative to the option "Service Handling ACTION_MEDIA_BUTTON," so I took that to mean I didn't need to do anything more with my manifest. If anyone could enlighten me here, I would appreciate it.

But, for what it's worth, this worked for me.

like image 257
Brian Rak Avatar asked Nov 07 '22 08:11

Brian Rak


1 Answers

Probably you have not set up the actions. Look the code below, it show how to bind the buttons with an intent. Please modify it for android O devices which require a channel.

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.app.NotificationCompat;

/**
 * Keeps track of a notification and updates it automatically for a given MediaSession. This is
 * required so that the music service don't get killed during playback.
 */
public class MediaNotificationManager extends BroadcastReceiver {
    private static final int NOTIFICATION_ID = 412;
    private static final int REQUEST_CODE = 100;

    private static final String ACTION_PAUSE = "com.example.android.musicplayercodelab.pause";
    private static final String ACTION_PLAY = "com.example.android.musicplayercodelab.play";
    private static final String ACTION_NEXT = "com.example.android.musicplayercodelab.next";
    private static final String ACTION_PREV = "com.example.android.musicplayercodelab.prev";

    private final MusicService mService;

    private final NotificationManager mNotificationManager;

    private final NotificationCompat.Action mPlayAction;
    private final NotificationCompat.Action mPauseAction;
    private final NotificationCompat.Action mNextAction;
    private final NotificationCompat.Action mPrevAction;

    private boolean mStarted;

    public MediaNotificationManager(MusicService service) {
        mService = service;

        String pkg = mService.getPackageName();
        PendingIntent playIntent =
                PendingIntent.getBroadcast(
                        mService,
                        REQUEST_CODE,
                        new Intent(ACTION_PLAY).setPackage(pkg),
                        PendingIntent.FLAG_CANCEL_CURRENT);
        PendingIntent pauseIntent =
                PendingIntent.getBroadcast(
                        mService,
                        REQUEST_CODE,
                        new Intent(ACTION_PAUSE).setPackage(pkg),
                        PendingIntent.FLAG_CANCEL_CURRENT);
        PendingIntent nextIntent =
                PendingIntent.getBroadcast(
                        mService,
                        REQUEST_CODE,
                        new Intent(ACTION_NEXT).setPackage(pkg),
                        PendingIntent.FLAG_CANCEL_CURRENT);
        PendingIntent prevIntent =
                PendingIntent.getBroadcast(
                        mService,
                        REQUEST_CODE,
                        new Intent(ACTION_PREV).setPackage(pkg),
                        PendingIntent.FLAG_CANCEL_CURRENT);

        mPlayAction =
                new NotificationCompat.Action(
                        R.drawable.ic_play_arrow_white_24dp,
                        mService.getString(R.string.label_play),
                        playIntent);
        mPauseAction =
                new NotificationCompat.Action(
                        R.drawable.ic_pause_white_24dp,
                        mService.getString(R.string.label_pause),
                        pauseIntent);
        mNextAction =
                new NotificationCompat.Action(
                        R.drawable.ic_skip_next_white_24dp,
                        mService.getString(R.string.label_next),
                        nextIntent);
        mPrevAction =
                new NotificationCompat.Action(
                        R.drawable.ic_skip_previous_white_24dp,
                        mService.getString(R.string.label_previous),
                        prevIntent);

        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_NEXT);
        filter.addAction(ACTION_PAUSE);
        filter.addAction(ACTION_PLAY);
        filter.addAction(ACTION_PREV);

        mService.registerReceiver(this, filter);

        mNotificationManager =
                (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);

        // Cancel all notifications to handle the case where the Service was killed and
        // restarted by the system.
        mNotificationManager.cancelAll();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        switch (action) {
            case ACTION_PAUSE:
                mService.mCallback.onPause();
                break;
            case ACTION_PLAY:
                mService.mCallback.onPlay();
                break;
            case ACTION_NEXT:
                mService.mCallback.onSkipToNext();
                break;
            case ACTION_PREV:
                mService.mCallback.onSkipToPrevious();
                break;
        }
    }

    public void update(
            MediaMetadataCompat metadata,
            PlaybackStateCompat state,
            MediaSessionCompat.Token token) {
        if (state == null
                || state.getState() == PlaybackStateCompat.STATE_STOPPED
                || state.getState() == PlaybackStateCompat.STATE_NONE) {
            mService.stopForeground(true);
            try {
                mService.unregisterReceiver(this);
            } catch (IllegalArgumentException ex) {
                // ignore receiver not registered
            }
            mService.stopSelf();
            return;
        }
        if (metadata == null) {
            return;
        }
        boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(mService);
        MediaDescriptionCompat description = metadata.getDescription();

        notificationBuilder
                .setStyle(
                        new NotificationCompat.MediaStyle()
                                .setMediaSession(token)
                                .setShowActionsInCompactView(0, 1, 2))
                .setColor(
                        mService.getApplication().getResources().getColor(R.color.notification_bg))
                .setSmallIcon(R.drawable.ic_notification)
                .setVisibility(Notification.VISIBILITY_PUBLIC)
                .setContentIntent(createContentIntent())
                .setContentTitle(description.getTitle())
                .setContentText(description.getSubtitle())
                .setLargeIcon(MusicLibrary.getAlbumBitmap(mService, description.getMediaId()))
                .setOngoing(isPlaying)
                .setWhen(isPlaying ? System.currentTimeMillis() - state.getPosition() : 0)
                .setShowWhen(isPlaying)
                .setUsesChronometer(isPlaying);

        // If skip to next action is enabled
        if ((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
            notificationBuilder.addAction(mPrevAction);
        }

        notificationBuilder.addAction(isPlaying ? mPauseAction : mPlayAction);

        // If skip to prev action is enabled
        if ((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
            notificationBuilder.addAction(mNextAction);
        }

        Notification notification = notificationBuilder.build();

        if (isPlaying && !mStarted) {
            mService.startService(new Intent(mService.getApplicationContext(), MusicService.class));
            mService.startForeground(NOTIFICATION_ID, notification);
            mStarted = true;
        } else {
            if (!isPlaying) {
                mService.stopForeground(false);
                mStarted = false;
            }
            mNotificationManager.notify(NOTIFICATION_ID, notification);
        }
    }

    private PendingIntent createContentIntent() {
        Intent openUI = new Intent(mService, MusicPlayerActivity.class);
        openUI.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        return PendingIntent.getActivity(
                mService, REQUEST_CODE, openUI, PendingIntent.FLAG_CANCEL_CURRENT);
    }
}
like image 172
Bro Nicholas Avatar answered Nov 14 '22 23:11

Bro Nicholas