'Catching android media button events

I have an app that launches a foreground service to play some media and I want to be able to control it with media buttons on smart watches/headphones and control it from a mediastyle notification etc.

I cannot get the media buttons to work consistently though. In the logs I can see they are often sent to other apps even though I started my MediaSession and playback last.

But I cannot get it to work despite having a media session where I setActive(true) and having the media callback defined?

Manifest:

<service
    android:name=".services.MediaControllerService"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.FOREGROUND_SERVICE">
    <intent-filter android:priority="999">
        <action android:name="android.intent.action.MEDIA_BUTTON"/>
    </intent-filter>
</service>

Code (Note the packages, it was hard in android 10 finding the correct combination of packages that worked together and gave me MediaStyle)...

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.media.session.MediaButtonReceiver;

public class TextToSpeechMediaControllerService extends Service { 
    public static final String START_SERVICE_ACTION_INTENT = "serviceStart";
    private TextToSpeechPlayer player;
    private MediaMetadataCompat mediaMetaData;
    private MediaSessionCompat mediaSession;
    private TextToSpeechMediaControllerService.AudioFocusHelper mAudioFocusHelper;
    private AudioManager mAudioManager;
    private final String NOTIFICATION_CHANNEL_TEXT_TO_SPEECH_CONTROLS = "fp_tts_media_controls";
    public MediaControllerCompat.TransportControls transportControls;
    private String pageTitle;
    private String pageAddress;
    private String pageDomain;
    private SpeechBank currentSpeechBank;
    private MediaButtonReceiver mediaButtonReceiver;
    private AudioFocusRequest audioFocusRequest;
    private AudioAttributes playbackAttributes;
    private Handler handler;

    public TextToSpeechMediaControllerService() {
    }

    @Override
    public void onCreate() {
        Log.d("TTSMEDIAPLAYER", "---------- STARTING SERVICE ----------");
        player = new TextToSpeechPlayer(this);
        mAudioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
        mAudioFocusHelper = new TextToSpeechMediaControllerService.AudioFocusHelper();

        mediaSession = new MediaSessionCompat(this, "fpt2s");
        mediaSession.setCallback(callback);
        mediaSession.setActive(true);
        handler = new Handler(); // something to do with handling delayed focus https://developer.android.com/guide/topics/media-apps/audio-focus#audio-focus-change
        transportControls = mediaSession.getController().getTransportControls();
        NotificationChannel channel = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            channel = new NotificationChannel(NOTIFICATION_CHANNEL_TEXT_TO_SPEECH_CONTROLS,
                    getString(R.string.media_controls_notification_channel_title),
                    NotificationManager.IMPORTANCE_HIGH);
            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        }
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("TTSMEDIAPLAYER", "---------- onStartCOmmand ("+intent.getAction()+", startId) ----------");
        MediaButtonReceiver.handleIntent(mediaSession, intent); // Required to catch media button events and send them to mediasession callback
        if (intent !=null && intent.getExtras()!=null){
            int closeCommand = intent.getExtras().getInt("swipeToClose", 0);
            if(closeCommand==1){
                Log.d("TTSMEDIAPLAYER", "Close service intent received.");
                // Pre-lollipop media style close button. Unable to test
                stopSelf();
                return super.onStartCommand(intent, flags, startId);
            }
            //Request audio focus
            if (false ) { // mAudioFocusHelper.requestAudioFocus() == false not needed because we request focus onPlay
                Log.d("TTSMEDIAPLAYER", "Starting and requesting focus...");
                //Could not gain focus
                stopSelf();
            }else {
                Log.d("TTSMEDIAPLAYER", "Focued. Starting...");
                boolean isPlaying = player.isPlaying();
                String nodesAsJsonString = intent.getExtras().getString("nodesAsJsonString", "[]");
                if (nodesAsJsonString != null && !nodesAsJsonString.equals("[]")) {
                    isPlaying = true;
                    // New TTS playback has been requested
                    pageTitle = intent.getExtras().getString("pageTitle", "");
                    pageAddress = intent.getExtras().getString("pageAddress", "");
                    pageDomain = UrlHelper.getDomain(pageAddress, true, true, true);
                    final Locale languageToSpeak = UrlHelper.getLanguageFromAddress(pageAddress);

                    try {
                        currentSpeechBank = new SpeechBank(nodesAsJsonString, languageToSpeak);
                    } catch (JSONException e) {
                        stopSelf();
                        return super.onStartCommand(intent, flags, startId);
                    }
                    mediaMetaData = new MediaMetadataCompat.Builder()
                            // TODO i guessed at these, I think this might be used on things like bluetooth speakers that have a display
                            .putString(MediaMetadata.METADATA_KEY_ARTIST, pageAddress)
                            .putString(MediaMetadata.METADATA_KEY_TITLE, pageTitle)
                            .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, pageTitle)
                            .build();
                    mediaSession.setMetadata(mediaMetaData);
                }
                Log.d("TTSMEDIAPLAYER", "isPlaying: " + isPlaying);
                /*mediaSession.setFlags(
                        MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | // apparently no longer needed
                                //MediaSession.FLAG_HANDLES_QUEUE_COMMANDS | //
                                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS 
                );*/

                if(intent.getAction()!=null && intent.getAction().equals(START_SERVICE_ACTION_INTENT)) {
                    Log.d("TTSMEDIAPLAYER", "START_SERVICE_ACTION_INTENT detected, triggering transportcontrols.play");
                    transportControls.play();
                }
            }
        }
        return super.onStartCommand(intent, flags, startId); //  START_NOT_STICKY;?
    }

    private void updateNotificationAndMediaButtons(boolean isPlaying) {
        // ... notification stuff ...
        startForeground(1, notificationBuilder.build());
    }

    @Override
    public void onDestroy() {
        mediaSession.release();
        mAudioFocusHelper.abandonAudioFocus();
        player.freeUpResources();
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {

        @Override
        public void onSkipToNext() {
            Log.d("TTSMEDIAPLAYER", "SKIP TO NEXT");
            super.onSkipToNext();
            handleFastForward();
        }

        @Override
        public void onPlay() {
            Log.d("TTSMEDIAPLAYER", "onPLAY!");
            if(mAudioFocusHelper.requestAudioFocus()) {
                if(player.isPaused()){
                    Log.d("TTSMEDIAPLAYER", "(resuming)");
                    player.resume();
                }else{
                    Log.d("TTSMEDIAPLAYER", "(not started? playNew)");
                    player.playNew(currentSpeechBank);
                }
                // TODO TTS textToSpeechPlayer.play();
                PlaybackStateCompat state = new PlaybackStateCompat.Builder()
                        // Supported actions in current state
                        .setActions(
                                PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP
                                        | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND
                        )
                        // Current state
                        .setState(PlaybackStateCompat.STATE_PLAYING, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
                        .build();
                mediaSession.setPlaybackState(state);
                updateNotificationAndMediaButtons(true);
            }
            super.onPlay();
        }

        @Override
        public void onPause() {
            Log.d("TTSMEDIAPLAYER", "onPAUSE!");
            player.pause();
            PlaybackStateCompat state = new PlaybackStateCompat.Builder()
                    // Set supported actions in current state
                    .setActions(
                            PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP |
                                    PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND)
                    // Set current state
                    .setState(PlaybackStateCompat.STATE_PAUSED, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
                    .build();
            mediaSession.setPlaybackState(state);
            updateNotificationAndMediaButtons(false);
            super.onPause();
        }

        @Override
        public void onSkipToPrevious() {
            Log.d("TTSMEDIAPLAYER", "SKIP TRACK PREV!");
            player.rewind();
            super.onSkipToPrevious();
        }

        @Override
        public void onFastForward() {
            Log.d("TTSMEDIAPLAYER", "FAST FORWARD!");
            super.onFastForward();
            handleFastForward();
        }

        @Override
        public void onRewind() {
            Log.d("TTSMEDIAPLAYER", "REWIND!");
            player.rewind();
            PlaybackStateCompat state = new PlaybackStateCompat.Builder()
                    // Set supported actions in current state
                    .setActions(
                            PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP |
                                    PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND)
                    // Set current state
                    .setState(PlaybackStateCompat.STATE_PLAYING, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
                    .build();
            mediaSession.setPlaybackState(state);
            updateNotificationAndMediaButtons(true);
            super.onRewind();
        }

        @Override
        public void onStop() {
            Log.d("TTSMEDIAPLAYER", "STOP!");
            player.stop();
            PlaybackStateCompat state = new PlaybackStateCompat.Builder()
                    // Set supported actions in current state
                    //    .setActions(null)
                    // Set current state
                    .setState(PlaybackStateCompat.STATE_STOPPED, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
                    .build();
            mediaSession.setPlaybackState(state);
            mAudioFocusHelper.abandonAudioFocus();
            stopSelf();
            //super.onStop();
        }
    };

    private void handleFastForward() {
        boolean hasReachedEnd = player.fastForward();
        if(hasReachedEnd){
            transportControls.stop();
        }else{
            PlaybackStateCompat state = new PlaybackStateCompat.Builder()
                    // Set supported actions in current state
                    .setActions(
                            PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_STOP |
                                    PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND)
                    // Set current state
                    .setState(PlaybackStateCompat.STATE_PLAYING, player.getCurrentPosition(), 1, SystemClock.elapsedRealtime())
                    .build();
            mediaSession.setPlaybackState(state);
            updateNotificationAndMediaButtons(true);
        }
    }

    /**
     * Helper class for managing audio focus related tasks.
     */
    private final class AudioFocusHelper
            implements AudioManager.OnAudioFocusChangeListener {

        private boolean mPlayOnAudioFocus = false;

        private boolean requestAudioFocus() {
            Log.d("TTSMEDIAPLAYER", "requestAudioFocus()...");
            playbackAttributes = new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                    .build();

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                        .setAudioAttributes(playbackAttributes)
                        .setAcceptsDelayedFocusGain(false)
                        .setOnAudioFocusChangeListener(mAudioFocusHelper, handler)
                        .build();
                int res = mAudioManager.requestAudioFocus(audioFocusRequest);
                if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                    Log.d("TTSMEDIAPLAYER", "audio focus failed...");
                    return false;
                } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                    Log.d("TTSMEDIAPLAYER", "audio focus granted...");
                    return true;
                } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
                    Log.d("TTSMEDIAPLAYER", "audio focus DELAYED...");
                    // use case for this is imagine being in a phone call that has focus,
                    // then the user opens a game. The game
                    // should start playing audio once the call finishes.
                    return false; // todo?
                }
            }else{
                final int result = mAudioManager.requestAudioFocus(this,
                        AudioManager.STREAM_MUSIC,
                        AudioManager.AUDIOFOCUS_GAIN);
                return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
            }
            Log.d("TTSMEDIAPLAYER", "audio focus returning default!?");
            return false;
        }

        private void abandonAudioFocus() {
            Log.d("TTSMEDIAPLAYER", "abandonAudioFocus()");
            mAudioManager.abandonAudioFocus(this);
        }

        @Override
        public void onAudioFocusChange(int focusChange) {
            Log.d("TTSMEDIAPLAYER", "Audio focus changed...");
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_GAIN:

                    Log.d("TTSMEDIAPLAYER", "Audio focus gained!");
                    if (mPlayOnAudioFocus && player.isPaused()) {
                        player.resume();
                        //} else if (isPlaying()) {
                        //    setVolume(MEDIA_VOLUME_DEFAULT);
                    }
                    mPlayOnAudioFocus = false;
                    break;

                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    Log.d("TTSMEDIAPLAYER", "Something about ducks!?");
                    // this might be for dropping the sound while something else happens (text notifications)
                    //setVolume(MEDIA_VOLUME_DUCK);
                    break;

                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    Log.d("TTSMEDIAPLAYER", "AUDIOFOCUS_LOSS_TRANSIENT!");
                    if (player.isPlaying()) {
                        // I think this is for temporary loss of focus e.g. calls/notificaitons
                        mPlayOnAudioFocus = true;
                        player.pause();
                    }
                    break;
                case AudioManager.AUDIOFOCUS_LOSS:
                    // Seems to be triggered when you press play in another media app (i.e. they requested focus)
                    Log.d("TTSMEDIAPLAYER", "AUDIOFOCUS_LOSS! abandoning focus, pausing speech");
                    mAudioManager.abandonAudioFocus(this);
                    if (player.isPlaying()) {
                        player.pause();
                        mPlayOnAudioFocus = false;
                    }
                    updateNotificationAndMediaButtons(false);
                    break;
                default:
                    Log.d("TTSMEDIAPLAYER", "AUDIOFOCUS_???");
            }
        }
    }
}

build.gradle

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.media:media:1.1.0'
...

I've been stuck on this for a while so any help would be greatly appreciated.

Logs:

? I/MusicController: [MediaSessionMonitor.java:153:onActiveSessionsChanged()] oooooo 
? I/MusicController: [MediaSessionMonitor.java:302:clear()] oooooo 
? D/MusicController: [MediaSessionMonitor.java:99:clearMediaContorllersMap()] oooooo Controller is already empty
? W/MusicController: [MediaSessionMonitor.java:171:updateMediaControllers()] oooooo Controller is empty
? I/MediaFocusControl:  AudioFocus  requestAudioFocus() from uid/pid 10345/8858 clientId=android.media.AudioManagerEx@63b6ee9fishpowered.bar.services.TextToSpeechMediaControllerService$AudioFocusHelper@44f676e req=1 flags=0x0
? I/MusicController: [MediaSessionMonitor.java:153:onActiveSessionsChanged()] oooooo 
? I/MusicController: [MediaSessionMonitor.java:174:updateMediaControllers()] oooooo List size :1
? I/MusicController: [MediaSessionMonitor.java:178:updateMediaControllers()] oooooo MediaController received packageName foo.bar
? D/MusicController: [MediaSessionMonitor.java:74:addToMediaContorllersMap()] oooooo Added = android.media.session.MediaSession$Token@986e7dd
? I/MusicController: [MediaSessionMonitor.java:78:addToMediaContorllersMap()] oooooo mMediaContorllersMap.size() = 1
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? I/MusicController: [MediaSessionMonitor.java:182:updateMediaControllers()] oooooo mMediaContorllersMap size = 1
? I/MediaFocusControl:  AudioFocus  requestAudioFocus() from uid/pid 10345/8858 clientId=android.media.AudioManagerEx@63b6ee9fishpowered.bar.services.TextToSpeechMediaControllerService$AudioFocusHelper@44f676e req=1 flags=0x0
? I/MusicController: [MediaControllerCallbackWrapper.java:55:onMetadataChanged()] oooooo This callback is from foo.bar
? I/MusicController: [MediaControllerCallbackWrapper.java:47:onPlaybackStateChanged()] oooooo This callback is from foo.bar
? D/MusicController: [MediaSessionMonitor.java:123:handleMessage()] oooooo MediaSessionMonitor.MsgHandler msg = 2
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? D/MusicController: [MediaSessionMonitor.java:123:handleMessage()] oooooo MediaSessionMonitor.MsgHandler msg = 1
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? I/MusicController: [MediaControllerCallbackWrapper.java:47:onPlaybackStateChanged()] oooooo This callback is from foo.bar
? D/MusicController: [MediaSessionMonitor.java:123:handleMessage()] oooooo MediaSessionMonitor.MsgHandler msg = 1
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? V/MediaRouter: Adding route: RouteInfo{ name=Phone, description=null, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO , presentationDisplay=null }
? V/MediaRouter: Adding route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Audio routes updated: AudioRoutesInfo{ type=SPEAKER, bluetoothName=DummyDevice }, a2dp=true
? W/MediaSessionCompat: Couldn't find a unique registered media button receiver in the given context.
? I/MusicController: [MediaSessionMonitor.java:153:onActiveSessionsChanged()] oooooo 
? I/MusicController: [MediaSessionMonitor.java:174:updateMediaControllers()] oooooo List size :1
? I/MusicController: [MediaSessionMonitor.java:178:updateMediaControllers()] oooooo MediaController received packageName foo.bar
? D/MusicController: [MediaSessionMonitor.java:76:addToMediaContorllersMap()] oooooo Already exist = android.media.session.MediaSession$Token@986e7dd
? I/MusicController: [MediaSessionMonitor.java:78:addToMediaContorllersMap()] oooooo mMediaContorllersMap.size() = 1
? D/MusicController: [MediaSessionMonitor.java:205:checkAndUpdateMusicController()] oooooo state = 3, action = 585
? I/MusicController: [MediaSessionMonitor.java:234:isValidStateToRegister()] oooooo isValidStateToRegister = true
? I/MusicController: [MediaSessionMonitor.java:257:isValidMetadataToRegister()] oooooo isValidMetadataToRegister = true
? I/MusicController: [MediaSessionMonitor.java:245:isValidActionToRegister()] oooooo isValidActionToRegister = false
? I/MusicController: [MediaSessionMonitor.java:222:checkAndUpdateMusicController()] oooooo is MusicController Updated = false
? I/MusicController: [MediaSessionMonitor.java:182:updateMediaControllers()] oooooo mMediaContorllersMap size = 1
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Adding route: RouteInfo{ name=Phone, description=null, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO , presentationDisplay=null }
? V/MediaRouter: Adding route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }
? V/MediaRouter: Audio routes updated: AudioRoutesInfo{ type=SPEAKER, bluetoothName=DummyDevice }, a2dp=true
? V/MediaRouter: Selecting route: RouteInfo{ name=DummyDevice, description=Bluetooth audio, status=null, category=RouteCategory{ name=System types=ROUTE_TYPE_LIVE_AUDIO ROUTE_TYPE_LIVE_VIDEO  groupable=false }, supportedTypes=ROUTE_TYPE_LIVE_AUDIO , presentationDisplay=null }

Also I'm seeing an audio permission error but I'm not sure if this is related:

W/MediaListnrAuthObsrvr: registration failed - not an approved notification listener yet?
    java.lang.SecurityException: Missing permission to control media.
        at android.os.Parcel.readException(Parcel.java:1951)
        at android.os.Parcel.readException(Parcel.java:1897)
        at android.media.session.ISessionManager$Stub$Proxy.addSessionsListener(ISessionManager.java:342)
        at android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener(MediaSessionManager.java:226)
        at android.media.session.MediaSessionManager.addOnActiveSessionsChangedListener(MediaSessionManager.java:189)
        at com.google.android.clockwork.common.media.DefaultMediaSessionManagerWrapper.addOnActiveSessionsChangedListener(AW771527612:8)
        at com.google.android.clockwork.companion.mediacontrols.api21.MediaSessionListenerAuthorizationObserver.register(AW771527612:12)
        at com.google.android.clockwork.companion.mediacontrols.api21.MediaSessionListenerAuthorizationObserver.<init>(AW771527612:5)
        at com.google.android.clockwork.companion.mediacontrols.api21.MediaRemoteControllerApi21.start(AW771527612:13)


Solution 1:[1]

As explained here you must call setActions() on your PlaybackStateCompat.Builder to tell exactly what actions you support - if you don't set that then you won't get any of the callbacks relating to media buttons. You are setting these actions in the callback which is why it's not working.

....

private Handler handler;
private PlaybackStateCompat.Builder mStateBuilder;
.....

Add the following in onCreate.

....
mediaSession = new MediaSessionCompat(this, "fpt2s");

mStateBuilder = new PlaybackStateCompat.Builder()
            .setActions(
                    PlaybackStateCompat.ACTION_PLAY |
                            PlaybackStateCompat.ACTION_PAUSE |
                            PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS |
                          PlaybackStateCompat.ACTION_SKIP_TO_NEXT |
                            PlaybackStateCompat.ACTION_PLAY_PAUSE);

mediaSession.setPlaybackState(mStateBuilder.build());
mediaSession.setCallback(callback);
mediaSession.setActive(true);

.......

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 longi