Skip to content

ForegroundServiceStartNotAllowedException not caught when Player uses non-default ApplicationLooper #2499

Closed

Description

@rugged-bl

Version

Media3 main branch

More version details

Bug exists at least since version 1.4.1.

Devices that reproduce the issue

Any Android 12+

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

When a Player is configured with a non-default ApplicationLooper, the ForegroundServiceStartNotAllowedException handling in MediaSessionService fails because the exception is thrown on a different thread than where the try-catch block executes.

  1. Create a Player with non-default Player.getApplicplicationLooper().
  2. (optional) Override MediaSesssionService.onUpdateNotification with startInForegroundRequired == true (which seems legal according to the documentation provided and library API).
  3. MediaSesssionService.onUpdateNotificationInternal is the only place in stack that catches the ForegroundServiceStartNotAllowedException exception. Documentation says "Triggers notification update and handles {@code ForegroundServiceStartNotAllowedException}".
  4. onUpdateNotificationInternal directly calls onUpdateNotification, onUpdateNotification directly calls getMediaNotificationManager().updateNotification. Calls happen on main thread according to documentation.
  5. In MediaNotificationManager.updateNotification there is a call to Util.postOrRun with session.getPlayer().getApplicationLooper().
  6. Lambda that creates (createNotification) and shows (updateNotificationInternal) the notification is posted to Player's Looper (which is not main thread's Looper), therefore the catch expression is no more in the needed stack and catches nothing.

Expected result

The ForegroundServiceStartNotAllowedException should be caught and passed to MediaSessionService.Listener.onForegroundServiceStartNotAllowedException() regardless of which looper the Player uses.

If main thread's Looper is used the exception is caught because both Util.postOrRun and mainExecutor (which is just a proxy to Util.postOrRun) are going through immediate OrRun branch and therefore are in the same call stack with catch.

Actual result

Fatal exception:

Exception android.app.ForegroundServiceStartNotAllowedException:
  at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:54)
  at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:50)
  at android.os.Parcel.readParcelableInternal (Parcel.java:4828)
  at android.os.Parcel.readParcelable (Parcel.java:4790)
  at android.os.Parcel.createExceptionOrNull (Parcel.java:3018)
  at android.os.Parcel.createException (Parcel.java:3007)
  at android.os.Parcel.readException (Parcel.java:2990)
  at android.os.Parcel.readException (Parcel.java:2932)
  at android.app.IActivityManager$Stub$Proxy.startService (IActivityManager.java:5426)
  at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1899)
  at android.app.ContextImpl.startForegroundService (ContextImpl.java:1875)
  at android.content.ContextWrapper.startForegroundService (ContextWrapper.java:827)
  at androidx.core.content.ContextCompat$Api26Impl.startForegroundService (ContextCompat.java:1128)
  at androidx.core.content.ContextCompat.startForegroundService (ContextCompat.java:700)
  at androidx.media3.session.MediaNotificationManager.startForeground (MediaNotificationManager.java:366)
  at androidx.media3.session.MediaNotificationManager.updateNotificationInternal (MediaNotificationManager.java:223)
  at androidx.media3.session.MediaNotificationManager.lambda$updateNotification$6 (MediaNotificationManager.java:179)
  at android.os.Handler.handleCallback (Handler.java:942)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:201)
  at android.os.Looper.loop (Looper.java:288)
  at android.app.ActivityThread.main (ActivityThread.java:7962)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:550)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:952)

Media

Any media with media notification.

Bug Report

Activity

  1. marcbaechinger commented on Jun 4, 2025

    @marcbaechinger
    Contributor

    Thanks for your report and the explanation. I look into a fix for that.

    Out of interest: By what action was this ForegroundServiceStartNotAllowedException triggered in your case. Is this something produced by the Media3 library, or is this caused because you start the service yourself in some ways not anticipated by the library?

    I'm asking because I'm working on a test fixture to later test the fix that I apply. When using the common ways to start the service, I'm not able to get the stack trace where you get it. I have an exception earlier and not inside MediaNotificationManager.

    I have some guesses how this could be produced, but it would be great to hear what you are trying to achieve when you have noticed this problem.

    Basically, I'd guess this is caused by an Intent being sent to the service. An anticipated Intent should have an exemption from the system to not trigger this exception, so I wonder how comes you see this stack trace.


    Sorry for being curious. This is not related to providing a fix for that or not. I think your reasoning is correct and we should fix that in any case. Knowing what developers do, allows us to understand use cases that we haven't seen yet.

  2. marcbaechinger commented on Jun 4, 2025

    @marcbaechinger
    Contributor

    I'm failing to repro with this issue I'm afraid.

    Can you please give me some repro steps how I can produce the exception above. When I try this I never get into the service itself. Instead the exception is thrown earlier like in the component that tries to start the service from the background.

    I crafted a broadcast receiver to send and Intent, I get the exception but that's happening earlier. The intent
    never goes through to where it throws in you case.

    If you provide some repro steps, that would speed up the process on our end.

    Process: androidx.media3.demo.session, PID: 7599
    java.lang.RuntimeException: Unable to start receiver androidx.media3.demo.session.DebugReceiver: android.app.ForegroundServiceStartNotAllowedException: startForegroundService() not allowed due to mAllowStartForeground false: service androidx.media3.demo.session/.PlaybackService
    	at android.app.ActivityThread.handleReceiver(ActivityThread.java:4716)
    	at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0)
    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2399)
    	at android.os.Handler.dispatchMessage(Handler.java:107)
    	at android.os.Looper.loopOnce(Looper.java:232)
    	at android.os.Looper.loop(Looper.java:317)
    	at android.app.ActivityThread.main(ActivityThread.java:8592)
    	at java.lang.reflect.Method.invoke(Native Method)
    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
    Caused by: android.app.ForegroundServiceStartNotAllowedException: startForegroundService() not allowed due to mAllowStartForeground false: service androidx.media3.demo.session/.PlaybackService
    	at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:54)
    	at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:50)
    	at android.os.Parcel.readParcelableInternal(Parcel.java:5016)
    	at android.os.Parcel.readParcelable(Parcel.java:4998)
    	at android.os.Parcel.createExceptionOrNull(Parcel.java:3178)
    	at android.os.Parcel.createException(Parcel.java:3167)
    	at android.os.Parcel.readException(Parcel.java:3150)
    	at android.os.Parcel.readException(Parcel.java:3092)
    	at android.app.IActivityManager$Stub$Proxy.startService(IActivityManager.java:6160)
    	at java.lang.reflect.Method.invoke(Native Method)
    	at leakcanary.ServiceWatcher$install$4$2.invoke(ServiceWatcher.kt:93)
    	at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
    	at $Proxy3.startService(Unknown Source)
    	at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1932)
    	at android.app.ContextImpl.startForegroundService(ContextImpl.java:1907)
    	at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:832)
    	at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:832)
    	at androidx.core.content.ContextCompat$Api26Impl.startForegroundService(ContextCompat.java:1091)
    	at androidx.core.content.ContextCompat.startForegroundService(ContextCompat.java:749)
    	at androidx.media3.demo.session.DebugReceiver.onReceive(DebugReceiver.java:30)
    	at android.app.ActivityThread.handleReceiver(ActivityThread.java:4707)
    	at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0) 
    	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2399) 
    	at android.os.Handler.dispatchMessage(Handler.java:107) 
    	at android.os.Looper.loopOnce(Looper.java:232) 
    	at android.os.Looper.loop(Looper.java:317) 
    	at android.app.ActivityThread.main(ActivityThread.java:8592) 
    	at java.lang.reflect.Method.invoke(Native Method) 
    	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) 
    	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878) 
    
  3. rugged-bl commented on Jun 5, 2025

    @rugged-bl
    Author

    @marcbaechinger Thank you for looking into this issue and for your detailed questions!

    Regarding real-world reproduction: unfortunately, I don't have a clear understanding of the exact real-world scenario that triggers this issue yet. Our users have somehow encountered this crash in production 🙂 , but the circumstances aren't fully clear to us at this point. To identify the specific use case, we'll need to roll out a new release with additional diagnostic logging, which will take some time.

    My hypothesis: I suspect this occurs when the service gets demoted from foreground status (either through system logic or Media3's internal logic) but remains running, and then a notification update is triggered while the app is in the background.

    I can reproduce the issue only in synthetic conditions:

    1. Ensure the Player uses a custom ApplicationLooper.
    2. Write a code like this (I think it will do the trick in tests or demo-app, but I tried this in our production app):
    class Media3SessionService : MediaSessionService() {
        override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
            stopForeground(STOP_FOREGROUND_DETACH) // Artificially demote the service from foreground to simulate the suspected condition
            super.onUpdateNotification(session, true) // Force startInForegroundRequired=true for easier reproduction
        }
    }
    
    1. Move the app to background (press Home button).
    2. Trigger any notification update through normal app logic (e.g., track change, playback state change). To ensure the update occurs, you could add test logic like: delay(10000); player.seekToNextMediaItem().

    You raise a good point about how we start our service. We do use a somewhat unconventional approach, though I believe it's internally pretty similar to a MediaController connection/binding. We chose this method primarily just to avoid having an extra controller instance connected. There's quite a bit of reasoning behind our media integration architecture... but this particular approach seems to be working for us (or not). Here's how we handle it:

    // By some triggers we call:
    context.startService(provideIntent())
    
    private fun provideIntent(): Intent {
        return Intent(context, Media3SessionService::class.java)
            .apply { action = "android.intent.action.MEDIA_BUTTON" }
    }
    

    I really appreciate you taking the time to investigate this. If you need any additional details or have more questions, please don't hesitate to ask. And of course, if I gather more concrete information about the real-world triggers, I'll share it with you.

  4. Sabelnikova commented on Feb 24, 2026

    @Sabelnikova

    @marcbaechinger Do you plan to address this issue? I’ve put together a reproducer for the crash: in the demo-session app, start playback, send the activity to the background, pause playback, then press the Shuffle button (it’s not actually shuffling in my setup).
    I realize this isn’t a typical user flow, but we’re seeing the same crash in our app with a Spotify Connect–style feature—e.g., when a user starts playback from another device after foregroundServiceTimeoutMs.

  5. marcbaechinger commented on Mar 30, 2026

    @marcbaechinger
    Contributor

    We've pushed the commit above to the 'main' branch. This propagates the exception across the thread boundaries in case an app is using a different thread than the main thread as the application thread.

    In practice this means that a ForegroundServiceStartNotAllowedException now triggers Listener.onForegroundServiceStartNotAllowedException.

    I'm closing this issue. Please let us know if this isn't sufficient by filing a new issue.

  6. locked and limited conversation to collaborators on May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions