microg/GmsCore · error · RuntimeException

app op denied

Error message

app op denied

What it means

When delivering each location update, the manager calls noteAppOpForEffectiveGranularity to record the corresponding app op (e.g._FINE_LOCATION/_COARSE_LOCATION) for the client. If the app op is denied (user restricted location for the app, e.g. 'Allow only while in use', or app op set to ignore/errored), the update cannot be attributed and a RuntimeException 'app op denied' is thrown.

Source

Thrown at play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt:471

            fun check() {
                if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException("Lack of permission")
                if (effectiveGranularity > permissionGranularity) throw RuntimeException("Lack of permission")
                if (timePendingMillis < 0) throw RuntimeException("duration limit reached (active for ${(SystemClock.elapsedRealtime() - start).formatDuration()}, duration ${request.durationMillis.formatDuration()})")
                if (updatesPending <= 0) throw RuntimeException("max updates reached")
                if (callback?.asBinder()?.isBinderAlive == false) throw RuntimeException("Binder died")
            }

            fun processNewLocation(location: Location): Boolean {
                check()
                if (lastLocation != null && location.elapsedMillis - lastLocation!!.elapsedMillis < request.minUpdateIntervalMillis) return false
                if (lastLocation != null && location.distanceTo(lastLocation!!) < request.minUpdateDistanceMeters) return false
                if (lastLocation == location) return false
                val returnedLocation = if (effectiveGranularity > permissionGranularity) {
                    throw RuntimeException("Lack of permission")
                } else {
                    if (!context.noteAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) {
                        throw RuntimeException("app op denied")
                    } else if (clientIdentity.isSelfProcess()) {
                        Location(location)
                    } else {
                        Location(location).apply { provider = "fused" }
                    }
                }
                val result = LocationResult.create(listOf(returnedLocation))
                callback?.onLocationResult(result)
                pendingIntent?.send(context, 0, Intent().apply { putExtra(LocationResult.EXTRA_LOCATION_RESULT, result) })
                if (request.maxUpdates != Int.MAX_VALUE) updates++
                check()
                return true
            }

            init {
                require(callback != null || pendingIntent != null)
            }
        }

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Re-request location permission / ask the user to re-enable location for the app in Settings
  2. Remove and re-register the location request after permission state changes so the app op is re-evaluated
  3. Check permission and app-op state (ContextCompat.checkSelfPermission + AppOpsManager) before and during updates
  4. Fall back to coarse updates or stop requesting updates when the app op is denied

Example fix

// before
client.requestLocationUpdates(request, listener, looper) // never re-checked after denial
// after
if (ContextCompat.checkSelfPermission(ctx, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED &&
    appOpAllowed(AppOpsManager.OPSTR_FINE_LOCATION)) {
    client.requestLocationUpdates(request, listener, looper)
}
Defensive patterns

Strategy: validation

Validate before calling

fun locationAppOpAllowed(ctx: Context): Boolean {
    val ops = ctx.getSystemService(AppOpsManager::class.java)
    val mode = ops.noteOpNoThrow(AppOpsManager.OPSTR_FINE_LOCATION, android.os.Process.myUid(), ctx.packageName)
    return mode == AppOpsManager.MODE_ALLOWED
}

Type guard

fun canReceiveUpdates(ctx: Context) =
    ContextCompat.checkSelfPermission(ctx, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
    locationAppOpAllowed(ctx)

Try / catch

try {
    client.requestLocationUpdates(request, listener, looper)
} catch (e: RuntimeException) {
    if (e.message == "app op denied") {
        stopLocationUpdates(); showPermissionRationale()
    } else throw e
}

Prevention

When it happens

Trigger: A location update arrives for a client whose location app op is currently denied in AppOps (user turned off location permission for the app in Settings, or a device-owner/profile-owner restriction denies it) while the request is still registered.

Common situations: User switches the app's location permission to 'Deny' or battery-restricted mode without the app removing its listener; work-profile or enterprise policy blocking location; OEM-level app op restrictions.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/c84867ca57c5c299. Report an issue: GitHub.