androidserviceworkerforeground-servicesecurityexception

SecurityException on a running foreground service when permission revoked


I have a long-running worker that gets the user's location in the background. Before starting the worker, I check all the needed location permissions.

I also added these lines of code to the Manifest. As per this documentation for Workers w/ foreground service/notification.

<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <service
        android:name="androidx.work.impl.foreground.SystemForegroundService"
        android:foregroundServiceType="location"
        tools:node="merge" />

Here is the worker w/ setForeground() method.

override suspend fun doWork(): Result {
    return try {
        notificationHelper.showNotification(createNotification(), notificationId)
        setForeground(createForegroundInfo())
            
        withContext(dispatcher) {
            while (kotlin.coroutines.coroutineContext.isActive) {
                val location = getLocation()
                notificationHelper.showNotification(createNotification(), notificationId)
                delay(MINUTES_30)
            }
        }
        
        Result.success()
    } catch (e: Exception) {
        if (e is CancellationException) {
            Result.failure()
        } else {
            Timber.e(e)
            notificationHelper.hideNotification(notificationId)
            Result.failure()
        }
    }
}

I know that I have to check permission before starting the worker/service. And thats okay. But my question is how do I stop the worker/service if the user revokes the permission via settings?

Question: If I start the worker and then revoke the permission my app crashes. And then when I try to open the app, it crashes with the same SecurityException immediately. Does the system try to restore the service, but fail? How do I stop it from doing it? Where/when/how do I stop the service from restoring?

I am cancelling the worker on the app start, just before the exception happens. Maybe I need to cancel it some other way?

WorkManager.getInstance(context).cancelAllWorkByTag(WORK_NAME)
notificationHelper.hideNotification(notificationId)

The exception I get:

Fatal Exception: java.lang.SecurityException
    Starting FGS with type location callerApp=ProcessRecord{9bdda4 21846:com.example.app/u0a765} targetSDK=34 requires permissions: 
    all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_LOCATION] 
    any of the permissions allOf=false [android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATION] 
    and the app must be in the eligible state/exemptions to access the foreground only permission
    at android.os.Parcel.createExceptionOrNull(Parcel.java:3057)
    at android.os.Parcel.createException(Parcel.java:3041)
    at android.os.Parcel.readException(Parcel.java:3024)
    at android.os.Parcel.readException(Parcel.java:2966)
    at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:6761)
    at android.app.Service.startForeground(Service.java:862)
    at androidx.work.impl.foreground.SystemForegroundService$Api31Impl.startForeground(SystemForegroundService.java:193)
    at androidx.work.impl.foreground.SystemForegroundService$1.run(SystemForegroundService.java:129)
    at android.os.Handler.handleCallback(Handler.java:958)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8177)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.am.ActiveServices.validateForegroundServiceType(ActiveServices.java:2611)
    at com.android.server.am.ActiveServices.setServiceForegroundInnerLocked(ActiveServices.java:2322)
    at com.android.server.am.ActiveServices.setServiceForegroundLocked(ActiveServices.java:1679)
    at com.android.server.am.ActivityManagerService.setServiceForeground(ActivityManagerService.java:13281)
    at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:3385)

Solution

  • I found the answer! You need to stop the service, on the app start, if there is no permission.

    val stopIntent = Intent(context, SystemForegroundService::class.java)
    context.stopService(stopIntent)