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:
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.
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.