Start foreground service after notification permission was disabled causes crash (Android 13)
Asked Answered
S

2

21

So, I've been experimenting with the new Android emulators (namely Android 13 or Android Tiramisu, API 33) and on an app, I need a foreground service.

The app's target SDK is currently 33.

Due to the notification requirement, I've added the android.permission.POST_NOTIFICATIONS to the manifest. And, because it is also a runtime permission, I'm asking the permission after the app is opened.

If, user denies the permission, but tries to perform a task that involves a foreground service after starting it with startForegroundService, upon calling startForeground from my service, I get a crash:

android.app.RemoteServiceException$CannotPostForegroundServiceNotificationException: Bad notification for startForeground
        at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:1983)
        at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2242)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

Notice the exception name: CannotPostForegroundServiceNotificationException. So apparently the system effectively prevents from me to post a foreground service notification. This happens after the startForeground call with a valid notification (up until API 32 anyway, and I did not see a change between API 32 and API 33 for constructing the notification itself, besides setOngoing(true) which is something I'm already doing).

So, I checked if I can post notifications, using NotificationManager.areNotificationsEnabled(). This returns false if user denies the permission, as expected. And the code now looks like this:

if (mNotificationManager.areNotificationsEnabled())
    startForeground(123, mNotificationBuilder.build())

And, as expected, startForeground does not get called. However, the task that needs to be executed might be long (about 2 minutes, maybe) and has to be in background, which cannot be performed in a job or through WorkManager, and without calling the startForeground, the app throws an exception after about 20 seconds with the following:

android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{ecbbdfb u0 com.example.android/.service.FgService}
        at android.app.ActivityThread.generateForegroundServiceDidNotStartInTimeException(ActivityThread.java:2006)
        at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:1977)
        at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2242)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
     Caused by: android.app.StackTrace: Last startServiceCommon() call for this service was made here
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1915)
        at android.app.ContextImpl.startForegroundService(ContextImpl.java:1870)
        at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:822)
        at com.example.android.MainActivity.startTaskWithFgService(MainActivity.kt:30)
        at com.example.kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

I should note, the notification becomes visible normally if user accepts the notification permission, so this looks like a permission problem, and no crashes are observed.

Edit: The notification channel created should post silent notifications. So, this is how the notification channel gets created (and is also included while trying to post the notification):

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
    withNotificationManager {
        if (notificationChannels?.map { it.id }?.contains(SILENT_NOTIF_CHANNEL_ID) == true)
            return@withNotificationManager

        val channel = NotificationChannel(
            SILENT_NOTIF_CHANNEL_ID,
            getString(R.string.silent_notif_channel_name),
            NotificationManager.IMPORTANCE_MIN).apply {
                enableLights(false)
                setShowBadge(false)
                setSound(null, null)
                description = getString(R.string.silent_notif_channel_desc)
                vibrationPattern = null
                lockscreenVisibility = Notification.VISIBILITY_SECRET
            }

        createNotificationChannel(channel)
    }
}

From what I'm understanding, two things are happening:

  • I can't start a foreground service using a notification because the permission was denied,
  • I can't start a foreground service without a notification either because a notification is necessary.

These two conditions effectively removes foreground services functionality. This looks like an oversight to me.

Quote from Android 13 behavior changes notification permission (link):

"Apps don't need to request the POST_NOTIFICATIONS permission in order to launch a foreground service. However, apps must include a notification when they start a foreground service, just as they do on previous versions of Android."

So, my question is:

What should I do to execute a long task in background without a foreground service if the user denies the permission?

Thanks for reading the question and I appreciate any help, answer or a discussion.

Submersed answered 21/7, 2022 at 14:23 Comment(2)
Hey! Interesting question. Could you also post a piece of code where you're creating your notification channel? According to docs, it should work even with the denied permissions - it only affects the visibility of your notification. >If the user denies the notification permission, they still see notices related to these foreground services in the Foreground Services (FGS) Task Manager developer.android.com/about/versions/13/…Catherine
@Catherine Edited the question accordingly. The channel gets created right before the notification is posted. I can say that I did not encounter any issues regarding to creating channels between application startup and right before posting a notification. Plus, the app just straight-up crashes so no indicators are present in the FGS manager.Submersed
S
14

Okay, I've found the problem.

Apparently, it was due to the fact that when the notification channel was created. So far, from my observations, creating a channel right before posting a notification was fine and we observed no crashes / bugs. In this case, the channel was also created right before the notification was posted, a.k.a after the service started and before startForeground was called.

If the user denies the permission beforehand, attempting to creating a notification channel silently fails, without crashing. Then, after trying to post a notification using startForeground, it fails with the exception that was posted in the question, and causing an unsolvable problem.

Since we do not expect the users to deny the notification permission at the first app start, I've moved creating the notification channel tasks to Application.onCreate() and the problem is resolved. The foreground service works, and it appears in FGS (Foreground Service Manager).

The key point to the solution is: Create the notification channel at Application.onCreate(), then on your foreground service, call startForeground without checking NotificationManager.areNotificationsEnabled(). This should allow the system to show the service in FGS (Foreground Service Manager).

This was not noted in the documentation, though as far as I'm aware.

Submersed answered 22/7, 2022 at 7:56 Comment(8)
So creating all the needed notifications channels on Application.onCreate() solved it for you? But what would happen if the user doesn't grant the permission? The foreground service will still run fine, right?Shirr
@androiddeveloper The only thing I can think of when it wouldn't work is that after the user installs the app they explicitly disables the notification permission from the settings app without opening the app itself first. It would be a major coincidence if somebody actually does that. The permission is not given by default but unless the user explicitly disables the permission, you can create notification channels when it's in default state. And the application class seems to be the best option for it.Submersed
I see. I hope you are right. It seems I always see some weird crashes of various Android versions related to Foreground services over the past few years, ever since Google started restricting them and also provide a weird API that requires you to start them only in the foreground.Shirr
Even though I've used this solution, it seems there are users that have this crash on my app (here: play.google.com/store/apps/details?id=com.lb.app_manager ). I have no idea how they do it. Seems very rare. As for the scenario you've described, I don't understand how can a user disable the permission before, as it's already disabled by default. Can you please think if there are more possible reasons?Shirr
The behavior I observed was on an API 33 emulator. I have no idea about the real devices, as they might differ in behavior. But, I'm editing the answer accordingly in case there is some misunderstanding: You should call startForeground without checking areNotificationsEnabled. As long as you have a channel, even if user denies the permission manually from the app, you should be able to call startForeground. I also believe this is something to be fixed or documented in the future by the way.Submersed
I don't know. I will prepare a new version that will help me investigate it further.Shirr
I still see this crash, no matter what.Shirr
Hi, we are using WorkManager's ForegroundInfo to start foreground service. ForegroundInfo requires a Notification object. My understand is, if areNotificationsEnabled is false, user will not able to see the on-going notification UI of the foreground service. But, will the long running foreground service allowed to complete its time-consuming operation? Without areNotificationsEnabled, will OS kills the foreground service after a period of time?Vertigo
C
2

Here are my 2 cents on this, should it help someone. I was also getting this exception on startForeground. Turned out that I was checking NotificationManager.areNotificationsEnabled() when creating the notification channel (thus not creating it when the return value was false). Definitely my mistake and took me a while to discover.

Carse answered 19/5, 2023 at 11:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.