[Feature] Add fused-location skill for foreground FLP, Android 12 permissions, and update lifecycle
[!NOTE] Feature requests are accepted only if the proposed skill fills a verified gap in state-of-the-art LLMs. Evaluations below used a fast model (Cursor Composer 2.5 Fast) and a thinking model (Cursor Grok 4.6). Gemini 3 Flash / Gemini 3.1 Pro were not available in this environment; please re-run the same prompts on those models if needed.
What is the gap in the LLM's knowledge?
android/skills has no location category. Current models already know to reach for FusedLocationProviderClient and they do not grab ACCESS_BACKGROUND_LOCATION for on-screen features. The remaining, repeatable gaps are the decision tree and Android 12 permission semantics, not "does FLP exist".
Four prompts, no web/docs/skills, code from memory only:
| Prompt | Fast (Composer 2.5) | Thinking (Grok 4.6) |
|---|---|---|
| A. Compose screen, get location once, city-level, targetSdk 35 | Pass: ACCESS_COARSE_LOCATION only + getCurrentLocation(PRIORITY_BALANCED_POWER_ACCURACY) |
Pass: same + CurrentLocationRequest / GRANULARITY_COARSE |
| B. Track while screen visible (street-level), must stop when leaving | Pass: FINE+COARSE together, requestLocationUpdates, ON_START/ON_STOP |
Pass: same + location-enabled check |
| C. Vague: "home screen, show current lat/lng" | Fail: requests FINE+COARSE and results.values.all { it }, so an Android 12 approximate-only grant is treated as denied. Also PRIORITY_HIGH_ACCURACY for a one-shot home screen. |
Fail: requests FINE+COARSE, then starts unbounded requestLocationUpdates in a ViewModel (not tied to ON_STOP) for a one-shot display. getCurrentLocation(..., null) (no CancellationToken). |
| D. "I need GPS coordinates… keep them updated as the user moves" | Fail: FLP + HIGH_ACCURACY, but cleanup is only DisposableEffect.onDispose / ViewModel.onCleared. Home / recents does not stop updates (background leak without a background permission). |
Fail: same lifecycle hole (DisposableEffect(granted) only). Seeds UI from lastLocation with no age check. Starts PRIORITY_HIGH_ACCURACY even if the user granted only coarse. |
When the prompt spelled out "coarse / once / stop on leave", both models complied. When a developer speaks naturally ("show my coordinates", "GPS", "keep updating"), they over-request fine, start a stream they do not stop on ON_STOP, and mishandle Android 12 approximate grants.
None of the eight runs used SettingsClient.checkLocationSettings. Permission granted was treated as "location is on".
These match documented platform rules:
- Approximate vs precise and "request fine with coarse": https://developer.android.com/develop/sensors-and-location/location/permissions
- Prefer
getCurrentLocation()over managing updates / stalegetLastLocation(): https://developer.android.com/develop/sensors-and-location/location/retrieve-current
Proposed Skill
Path: location/fused-location/SKILL.md (same layout as camera/camerax).
Scope: foreground only — runtime permissions (coarse vs fine, Android 12+), getCurrentLocation vs requestLocationUpdates, bind updates to ON_START/ON_STOP, location settings. Not Maps, geofencing, or background location.
Full draft:
---
name: fused-location
description: >
Guide Android foreground location with FusedLocationProviderClient.
Use when implementing current location, location updates, runtime location
permissions, approximate vs precise location on Android 12+, or when an
app would otherwise use LocationManager GPS_PROVIDER.
license: Complete terms in LICENSE.txt
metadata:
author: community proposal
last-updated: '2026-08-29'
keywords:
- recipe
- Android
- location
- FusedLocationProvider
- FusedLocationProviderClient
- ACCESS_COARSE_LOCATION
- ACCESS_FINE_LOCATION
- getCurrentLocation
- requestLocationUpdates
- Compose
- permissions
- approximate location
- precise location
---
Procedural guidance for **foreground** location on Android using Play services
`FusedLocationProviderClient`. Grounded in
[Request location permissions](https://developer.android.com/develop/sensors-and-location/location/permissions)
and
[Get the last known location](https://developer.android.com/develop/sensors-and-location/location/retrieve-current).
**Out of scope:** Maps SDK, geofencing, activity recognition, and background
location (`ACCESS_BACKGROUND_LOCATION`, location foreground services).
## Step 1: pick accuracy and API
Ask what the feature actually needs. Do not default to GPS / fine / streaming.
| Need | Permission | API |
| --- | --- | --- |
| City / weather / "which area" **once** | `ACCESS_COARSE_LOCATION` only | `getCurrentLocation` + `PRIORITY_BALANCED_POWER_ACCURACY` or `PRIORITY_LOW_POWER` |
| Street-level **once** | `ACCESS_FINE_LOCATION` **and** `ACCESS_COARSE_LOCATION` | `getCurrentLocation` + `PRIORITY_HIGH_ACCURACY` |
| Street-level **while UI is visible** | Fine + coarse | `requestLocationUpdates` + `removeLocationUpdates` on `ON_STOP` |
`getLastLocation()` / `lastLocation` is a cache. It can be `null` or hours old.
Do **not** use it as the primary one-shot API.
[Prefer `getCurrentLocation()`](https://developer.android.com/develop/sensors-and-location/location/retrieve-current).
```kotlin
// WRONG — GPS provider, no fusion, easy to leak updates
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 2000L, 0f, listener
)
// WRONG — stale cache as the only source
fusedClient.lastLocation.addOnSuccessListener { loc -> show(loc) }
// CORRECT — one-shot, fused, cancellable
val cts = CancellationTokenSource()
fusedClient.getCurrentLocation(
Priority.PRIORITY_BALANCED_POWER_ACCURACY,
cts.token,
)
```
Dependency: `com.google.android.gms:play-services-location`.
## Step 2: declare and request permissions
### Manifest
- City-level: declare **only** `ACCESS_COARSE_LOCATION`.
- Precise: declare **both** `ACCESS_FINE_LOCATION` and `ACCESS_COARSE_LOCATION`.
On Android 12+ the system [ignores a fine-only request](https://developer.android.com/develop/sensors-and-location/location/permissions)
(`ACCESS_FINE_LOCATION must be requested with ACCESS_COARSE_LOCATION`).
- Do **not** declare `ACCESS_BACKGROUND_LOCATION` for on-screen features.
### Runtime
Use `ActivityResultContracts.RequestPermission` (coarse only) or
`RequestMultiplePermissions` (fine + coarse in **one** call).
Android 12+ users can grant **approximate** even when the app asked for precise.
That is a valid grant, not a denial.
```kotlin
// WRONG — approximate-only looks like "denied"
val granted = results.values.all { it }
// CORRECT — any location grant is success; inspect fine separately
val hasCoarse = results[Manifest.permission.ACCESS_COARSE_LOCATION] == true ||
context.hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
val hasFine = results[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
context.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
```
If the feature **requires** street-level accuracy and the user granted only
approximate, keep the feature working at coarse if possible; otherwise explain
why precise is needed and request `ACCESS_FINE_LOCATION` again (upgrade dialog).
Do not block the whole app on first approximate grant unless the UX truly cannot
function.
Do **not** treat the first denial as "permanently denied".
`shouldShowRequestPermissionRationale == false` right after the first dialog is
unreliable.
## Step 3: one-shot current location
```kotlin
@SuppressLint("MissingPermission")
suspend fun currentLocation(
client: FusedLocationProviderClient,
fineGranted: Boolean,
): Location? {
val cts = CancellationTokenSource()
val priority = if (fineGranted) {
Priority.PRIORITY_HIGH_ACCURACY
} else {
Priority.PRIORITY_BALANCED_POWER_ACCURACY
}
return suspendCancellableCoroutine { cont ->
cont.invokeOnCancellation { cts.cancel() }
client.getCurrentLocation(priority, cts.token)
.addOnSuccessListener { loc -> if (cont.isActive) cont.resume(loc) }
.addOnFailureListener { e ->
if (cont.isActive) cont.resumeWithException(e)
}
}
}
```
If the result is `null`, the usual causes are: permission missing, location
services off, or Play services unavailable — not "call `lastLocation` and hope".
## Step 4: updates only while the screen is visible
`ViewModel.onCleared()` and `DisposableEffect.onDispose` do **not** run when the
user presses Home or opens recents. Updates keep flowing in the background
without a background permission. Bind to `Lifecycle.Event.ON_START` / `ON_STOP`.
```kotlin
// WRONG — Home / recents leaves the callback registered
DisposableEffect(Unit) {
client.requestLocationUpdates(request, callback, Looper.getMainLooper())
onDispose { client.removeLocationUpdates(callback) }
}
// CORRECT
DisposableEffect(lifecycleOwner, client) {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { /* ui state */ }
}
}
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> client.requestLocationUpdates(
request, callback, Looper.getMainLooper()
)
Lifecycle.Event.ON_STOP -> client.removeLocationUpdates(callback)
else -> Unit
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
client.removeLocationUpdates(callback)
}
}
```
Use `LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, intervalMillis)`
(or balanced/low-power for coarse). Do not use the deprecated
`LocationRequest.create()`.
## Step 5: resolve location settings
Permission granted ≠ location enabled. If `getCurrentLocation` returns `null` or
updates never arrive, check settings with
[`SettingsClient.checkLocationSettings`](https://developers.google.com/android/reference/com/google/android/gms/location/SettingsClient)
and, on `ResolvableApiException`, launch the system resolution dialog. Also
handle Play services missing / updating.
## Common pitfalls
- Defaulting to `ACCESS_FINE_LOCATION` + `PRIORITY_HIGH_ACCURACY` for "show my
coordinates" on a home screen.
- Starting `requestLocationUpdates` when `getCurrentLocation` is enough.
- `results.values.all { it }` after `RequestMultiplePermissions` (Android 12
approximate grant).
- Requesting `ACCESS_FINE_LOCATION` **without** `ACCESS_COARSE_LOCATION`.
- Pairing `ACCESS_BACKGROUND_LOCATION` with the first foreground request.
- Using `LocationManager` / `GPS_PROVIDER` instead of fused location.
- Passing `null` as the `CancellationToken` to `getCurrentLocation` and never
cancelling.
- Seeding UI from `lastLocation` with no age check.
## Official docs
- [Request location permissions](https://developer.android.com/develop/sensors-and-location/location/permissions)
- [Get current / last location](https://developer.android.com/develop/sensors-and-location/location/retrieve-current)
- [Request location updates](https://developer.android.com/develop/sensors-and-location/location/request-updates)
- [Approximate location (Android 12+)](https://developer.android.com/develop/sensors-and-location/location/permissions)
Additional Context
- Repo currently has
camera,identity,system, and other categories, but nolocation/tree. - README says public PRs are not accepted, so this is an issue-only proposal rather than a PR.
- The skill is intentionally narrower than "maps + geofence + background" so it stays in the "LLM underperforms" zone (decision tree + Android 12), not a restatement of FLP existence.
- Happy to add Maps / background location as separate skills if this one lands.
Source: android/skills