Skip to content

Permissions & background location

Background location on Android is not one permission — it is three grants collected in a fixed order, and asking for them the obvious way fails silently. The SDK drives the whole sequence, but it needs one object from your Activity and it cannot create that object itself.

Give it an ActivityResultCaller

MainActivity.kt
class MainActivity : ComponentActivity() {
private lateinit var permissionRequester: PermissionRequester
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Must be constructed BEFORE this Activity reaches STARTED:
// registerForActivityResult (which this calls internally) requires it.
permissionRequester = PermissionRequester(this)
}
}

Construct it in onCreate, not lazily on a button tap. PermissionRequester registers an ActivityResultLauncher in its constructor, and the Activity Result API rejects a registration made after the host has started — the crash message names registerForActivityResult, which is easy to misread as a problem with the SDK rather than with when you called it.

Then hand it to the request:

lifecycleScope.launch {
val status = BackgroundGeolocation.requestPermission(permissionRequester)
Log.d("BGeo", "authorization: $status")
}

What it actually asks for, and in what order

1. FOREGROUND ACCESS_FINE_LOCATION + ACCESS_COARSE_LOCATION
2. BACKGROUND ACCESS_BACKGROUND_LOCATION (API 29+)
3. ACTIVITY_RECOGNITION ACTIVITY_RECOGNITION (API 29+)

One stage per request, never bundled. From API 30 onward Android silently denies a request that asks for foreground and background location together: no dialog, no error, an immediate “denied” for both. The system requires foreground location to be granted first, and background location to be asked for separately afterwards — which is what the SDK does.

Stage 2 is skipped when your config asks for foreground-only tracking (locationAuthorizationRequest = "WhenInUse"), and stages 2 and 3 do not exist below API 29.

A denial does not stop the chain. Each stage is attempted once and the escalation moves on regardless of outcome. A user who refuses “Allow all the time” still gets asked for activity recognition, because that grant meaningfully improves tracking on its own. Bailing out at the first “no” would silently cost you a permission the user would have granted.

What you get back

requestPermission() resolves to an AuthorizationStatus:

ValueMeaning
ALWAYSBackground location granted — tracking works with the app closed.
WHEN_IN_USEForeground only — see the degradation below.
DENIEDNo location grant; tracking cannot produce fixes.
NOT_DETERMINEDNothing asked yet.

After the escalation finishes the SDK resumes tracking if a start() had earlier bailed for want of permission, and emits a onProviderChange event so subscribers see the final grant state without polling.

Degradation, stage by stage

Without background location (WHEN_IN_USE), Android delivers location only while your app is in the foreground. The engine keeps working — the queue, the filter, the motion machine are all unaffected — but fixes stop arriving when the user leaves your app, and resume when they return. Nothing announces this; the stream simply goes quiet.

Without activity recognition, the motion state machine falls back to speed-based detection and the stationary geofence. The practical cost is a slower, less certain transition out of “stationary”: instead of the OS telling the engine that the user started walking, the engine waits for either GPS speed or a ~200 m displacement from the stationary anchor.

Without any location grant, start() still succeeds and the engine still runs — it just never receives a fix. Check getState().authorization (or the onProviderChange event) rather than assuming the SDK is broken.

Foreground service and notifications

Tracking runs in a foreground service, and on API 33+ the user must also grant POST_NOTIFICATIONS for its notification to be visible. The service runs either way; the notification is what the user sees. The SDK does not request this permission — it is your app’s notification channel to explain, and most apps already ask for it elsewhere.

Configure the notification through the notification.* config keys.

Asking at the right moment

Android shows the background-location dialog exactly once per app install in practice: refuse it, and later requests go straight to “denied” without any UI. Ask when the user has just done something that explains why you need it, not on first launch. If they have already refused, requestPermission() returns without a dialog — send them to system settings instead, which is why the SDK does not pop a “go to settings” alert of its own.