microg/GmsCore · error · SecurityException
Caller must hold $permission for location bypass
Error message
Caller must hold $permission for location bypass
What it means
LocationManager.getLastLocation throws this SecurityException when LastLocationRequest.isBypass is true but the calling process does not hold the required bypass permission: android.permission.LOCATION_BYPASS on Android 13+ (SDK 33+), or android.permission.WRITE_SECURE_SETTINGS on older versions. Bypass is a privileged escape hatch that skips location permission checks, so microG requires the caller to hold a system-level permission before honoring it. It is thrown synchronously inside the suspend function before any location is returned.
Source
Thrown at play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt:76
private var activePermissionRequest: Deferred<Boolean>? = null
private var lastGpsLocation: Location? = null
private var lastNetworkLocation: Location? = null
private var currentGpsInterval: Long = -1
private var currentNetworkInterval: Long = -1
val deviceOrientationManager = DeviceOrientationManager(context, lifecycle) { updateLocationRequests() }
var started: Boolean = false
private set
suspend fun getLastLocation(clientIdentity: ClientIdentity, request: LastLocationRequest): Location? {
if (request.maxUpdateAgeMillis < 0) throw IllegalArgumentException()
GranularityUtil.checkValidGranularity(request.granularity)
if (request.isBypass) {
val permission = if (SDK_INT >= 33) "android.permission.LOCATION_BYPASS" else Manifest.permission.WRITE_SECURE_SETTINGS
if (context.checkPermission(permission, clientIdentity.pid, clientIdentity.uid) != PackageManager.PERMISSION_GRANTED) {
throw SecurityException("Caller must hold $permission for location bypass")
}
}
if (request.impersonation != null) {
Log.w(TAG, "${clientIdentity.packageName} wants to impersonate ${request.impersonation!!.packageName}. Ignoring.")
}
val permissionGranularity = context.granularityFromPermission(clientIdentity)
var effectiveGranularity = getEffectiveGranularity(request.granularity, permissionGranularity)
if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(clientIdentity.packageName) && !clientIdentity.isSelfUser()) effectiveGranularity = GRANULARITY_COARSE
val returnedLocation = if (effectiveGranularity > permissionGranularity) {
// No last location available at requested granularity due to lack of permission
null
} else {
ensurePermissions()
val preLocation = lastLocationCapsule.getLocation(effectiveGranularity, request.maxUpdateAgeMillis)
val processedLocation = postProcessor.process(preLocation, effectiveGranularity, clientIdentity.isGoogle(context))
if (!context.noteAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) {
// App Op denied
nullView on GitHub (pinned to 157c9d86ac)
Solutions
- Remove setBypass(true) from the LastLocationRequest and request normal location permissions (ACCESS_FINE_LOCATION) instead.
- If bypass is genuinely needed (system app), grant the required permission: adb shell pm grant <pkg> android.permission.WRITE_SECURE_SETTINGS (SDK<33) or add android.permission.LOCATION_BYPASS to the privapp-permissions whitelist (SDK>=33).
- Runtime-check before calling: context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED, mirroring the library's own logic.
Example fix
// before val request = LastLocationRequest.Builder().setBypass(true).build() val location = fusedLocationClient.getLastLocation(request) // after val request = LastLocationRequest.Builder().build() // no bypass val location = fusedLocationClient.getLastLocation(request)
Defensive patterns
Strategy: try-catch
Validate before calling
val needed = if (Build.VERSION.SDK_INT >= 33) "android.permission.LOCATION_BYPASS" else android.Manifest.permission.WRITE_SECURE_SETTINGS val ok = context.checkPermission(needed, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED
Type guard
fun canBypass(ctx: Context): Boolean {
val p = if (Build.VERSION.SDK_INT >= 33) "android.permission.LOCATION_BYPASS" else android.Manifest.permission.WRITE_SECURE_SETTINGS
return ctx.checkPermission(p, android.os.Process.myPid(), android.os.Process.myUid()) == PackageManager.PERMISSION_GRANTED
} Try / catch
try {
client.getLastLocation(request)
} catch (e: SecurityException) {
if (e.message?.contains("bypass") == true) fallbackToNonBypassRequest()
else throw e
} Prevention
- Do not set bypass=true unless you are a privileged/system app
- Branch permission checks on Build.VERSION.SDK_INT (33+ needs LOCATION_BYPASS)
- Keep privileged permissions in the platform privapp-permissions whitelist
- Prefer normal ACCESS_FINE_LOCATION flow over bypass
When it happens
Trigger: Calling FusedLocationProviderApi.getCurrentLocation/LastLocation with a LastLocationRequest built via setBypass(true) (e.g. LocationRequest.Builder#setBypass) while the app's uid/pid lacks android.permission.LOCATION_BYPASS (SDK>=33) or android.permission.WRITE_SECURE_SETTINGS (SDK<33).
Common situations: Apps enabling bypass after copying sample code that sets isBypass; apps testing on Android 12 with WRITE_SECURE_SETTINGS granted via adb then moving to Android 13 where LOCATION_BYPASS is needed; system/privileged apps whose privileges were not declared in the platform's privapp-permissions whitelist.
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
- Device tag restricted to approved apps
- Neither android.permission.ACCESS_COARSE_LOCATION nor androi
- $packageName does not have any of $permissions
- Caller must hold $permission for location bypass
- Access denied, missing google package permission for
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/7fde216ba1eb6db9.
Report an issue: GitHub.