App crash on headset button click
Asked Answered
S

2

5

I have build an audioplayer which is deployed in android google playstore. I'm using crashlytics to monitor crashes and ANRs. Recently I have been getting a lot of crashes MediaButtonReceiver. The headset clicks work fine in many devices. But some devices are giving this problem.

Crashlytics report -

Fatal Exception: java.lang.RuntimeException: Unable to start receiver android.support.v4.media.session.MediaButtonReceiver: java.lang.IllegalStateException: Could not find any Service that handles android.intent.action.MEDIA_BUTTON or implements a media browser service.
       at android.app.ActivityThread.handleReceiver(ActivityThread.java:2866)
       at android.app.ActivityThread.access$1700(ActivityThread.java:182)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1551)
       at android.os.Handler.dispatchMessage(Handler.java:111)
       at android.os.Looper.loop(Looper.java:194)
       at android.app.ActivityThread.main(ActivityThread.java:5706)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1033)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:828)

MediaSession code -

private void initMediaSession() throws RemoteException {
        if (mediaSessionManager != null) return; //mediaSessionManager exists

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mediaSessionManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
        }
        // Create a new MediaSession
        mediaSession = new MediaSessionCompat(this, "AudioPlayer");
        //Get MediaSessions transport controls
        transportControls = mediaSession.getController().getTransportControls();
        //set MediaSession -> ready to receive media commands
        mediaSession.setActive(true);
        //indicate that the MediaSession handles transport control commands
        // through its MediaSessionCompat.Callback.
        mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS|MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);


        //Set mediaSession's MetaData
        updateMetaData();


        mediaSession.setCallback(new MediaSessionCompat.Callback() {
            @Override
            public void onPlay() {
                super.onPlay();

                resumeMedia();
            }

            @Override
            public void onPause() {
                super.onPause();

                pauseMedia();
            }

            @Override
            public void onSkipToNext() {
                super.onSkipToNext();

            }

            @Override
            public void onSkipToPrevious() {
                super.onSkipToPrevious();

            }

            @Override
            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {

                if (su.getHeadsetEnableSwitch()) {

                    String intentAction = mediaButtonIntent.getAction();
                    if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
                        KeyEvent event = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);

                        if (event != null) {


                            int action = event.getAction();
                            Log.e("Headset key: ", String.valueOf(action));
                            if (action == KeyEvent.ACTION_DOWN) {

                                Log.e("Headset: ", "Action down");

                                headsetClickCount++;

                                new Handler().postDelayed(new Runnable() {
                                    @Override
                                    public void run() {

                                        if (headsetClickCount == 1) {

                                            if (isPng()) pauseMedia();
                                            else resumeMedia();

                                            headsetClickCount = 0;

                                        } else if (headsetClickCount == 2) {

                                            if (su.getDoubleClickAction() == 0) {
                                            } else if (su.getDoubleClickAction() == 1)
                                                skipToPrevious();
                                            else if (su.getDoubleClickAction() == 2) skipToNext();
                                            headsetClickCount = 0;
                                        } else if (headsetClickCount == 3) {

                                            if (su.getTripleClickAction() == 0) {
                                            } else if (su.getTripleClickAction() == 1)
                                                skipToPrevious();
                                            else if (su.getTripleClickAction() == 2) skipToNext();
                                            headsetClickCount = 0;
                                        }
                                    }
                                }, 750);
                            }

                            if (action == KeyEvent.FLAG_LONG_PRESS) {

                                if (su.getLongClickAction() == 0) {
                                } else if (su.getLongClickAction() == 1) skipToPrevious();
                                else if (su.getLongClickAction() == 2) skipToNext();

                            }


                            if (action == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {

                                Log.e("Headset: ", "headset sethook");
                                if (isPng()) pauseMedia();
                                else resumeMedia();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_NEXT) {

                                skipToNext();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {

                                skipToPrevious();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PAUSE) {

                                pauseMedia();
                            }

                            if (action == KeyEvent.KEYCODE_MEDIA_PLAY) {

                                resumeMedia();
                            }


                        }
                    }

                    return true;

                }

            return true;
            }
        });



    }

What could be the problem and how to solve this?

My thoughts - Maybe this happens because user opens other music apps that has this feature while my app is still playing.

Sultry answered 6/7, 2018 at 18:58 Comment(0)
A
7

You have to create your own media button receiver class, say MyMediaButtonReceiver.java, that extends MediaButtonReceiver, and it will be empty except for the onReceive method that you have to override, calling super.onReceive(...) between a try-catch that captures the IllegalStateException:

public class MyMediaButtonReceiver extends MediaButtonReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            super.onReceive(context, intent);
        } catch (IllegalStateException e) {
            Log.d(this.getClass().getName(), e.getMessage());
        }
    }
}

Then you have to declare that receiver class in your Manifest (or replace your previous MediaButtonReceiver class declaration, if you had one), like:

<receiver android:name=".MyMediaButtonReceiver" >
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>
Ashton answered 24/8, 2018 at 13:26 Comment(10)
So what is received in OnReceive() ? Intents from button clicks?Sultry
Yes, it will capture the clicks from physical buttons, and if you implement it as indicated, your app will no longer crash with that exception. Trutst me, it works, I had the very same problem in my own app ;) It is wonderful to see that problem gone from crashlytics console.Ashton
I'll try this solution and get back to you. Might take some time as the app is already on playstore.Sultry
Sure, I did the same, published a new version of my app with that fix. Do it and you will see your crashlytics crash-free statistics graph go really up!Ashton
No problem at all. I wish to add that this implementation will not interfere with your current handling of the buttons (the code you posted), because it just intercepts the exception that the original extended class (MediaButtonReceiver) throws, in a way that it only prevents the crash but then it calls super, thus not interfering with your intended buttons behaviour. The "secondary effect" wil be that once in a while (when the crash occured), the volume button won't work, just for that click, and that, from an user experience point of view, is better than an app total crash.Ashton
Okay. Got it. ThanksSultry
Sorry just uploaded the update and it looks like it worked. Any workaround for the volume control though? Some users are complaining that they cannot adjust the volume.Sultry
@Sultry Any update on controlling physical keys, as we catch the exception, it will not crash but keys may not work.Dogged
In my app volume keys work fine. Don´t work very few times, when exception is thrown. But most of the time they work. Don't know if it is related, but I'm using exoplayer, not the standard mediaplayer.Ashton
@Ramiro: I will try this to my app. I hope this crash will be gone. In addition do you have crash on "android.app.RemoteServiceException "? If yes please suggest appropriate solution. Thanks.Zionism
P
0
class MyMediaButtonReceiver : MediaButtonReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
            val event = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
            if (event.action == KeyEvent.ACTION_UP || event.action == 
KeyEvent.ACTION_DOWN) {
            when (event.keyCode) {
                // handle cancel button
                KeyEvent.KEYCODE_MEDIA_STOP -> context.sendIntent(ACTION_FINISH)
               // handle play button
                KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE -> context.sendIntent(ACTION_PLAY_PAUSE)
                }
            }
        }
    }
}

kotlin extension for send event to media service

fun Context.sendIntent(action: String) {
    Intent(this, MediaPlayerService::class.java).apply {
        this.action = action
        try {
            if (isOreoPlus()) {
                startForegroundService(this)
            } else {
                startService(this)
            }
        } catch (ignored: Exception) {
        }
    }
}

add Receiver in manifest

 <receiver android:name=".player.receivers.MyMediaButtonReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
        </intent-filter>
    </receiver>
Pitterpatter answered 27/10, 2020 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.