fix(android): remove background location mode

This commit is contained in:
Ayaan Zaidi
2026-03-08 14:48:24 +05:30
committed by Ayaan Zaidi
parent 0f9566b0b5
commit 1230cefe25
9 changed files with 22 additions and 105 deletions

View File

@@ -11,7 +11,6 @@
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SEND_SMS" />

View File

@@ -3,12 +3,12 @@ package ai.openclaw.app
enum class LocationMode(val rawValue: String) {
Off("off"),
WhileUsing("whileUsing"),
Always("always"),
;
companion object {
fun fromRawValue(raw: String?): LocationMode {
val normalized = raw?.trim()?.lowercase()
if (normalized == "always") return WhileUsing
return entries.firstOrNull { it.rawValue.lowercase() == normalized } ?: Off
}
}

View File

@@ -82,7 +82,6 @@ class NodeRuntime(context: Context) {
location = location,
json = json,
isForeground = { _isForeground.value },
locationMode = { locationMode.value },
locationPreciseEnabled = { locationPreciseEnabled.value },
)

View File

@@ -170,13 +170,6 @@ class DeviceHandler(
promptableWhenDenied = true,
),
)
put(
"backgroundLocation",
permissionStateJson(
granted = hasPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
promptableWhenDenied = true,
),
)
put(
"sms",
permissionStateJson(

View File

@@ -5,7 +5,6 @@ import android.content.Context
import android.content.pm.PackageManager
import android.location.LocationManager
import androidx.core.content.ContextCompat
import ai.openclaw.app.LocationMode
import ai.openclaw.app.gateway.GatewaySession
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.serialization.json.Json
@@ -17,7 +16,6 @@ class LocationHandler(
private val location: LocationCaptureManager,
private val json: Json,
private val isForeground: () -> Boolean,
private val locationMode: () -> LocationMode,
private val locationPreciseEnabled: () -> Boolean,
) {
fun hasFineLocationPermission(): Boolean {
@@ -34,19 +32,11 @@ class LocationHandler(
)
}
fun hasBackgroundLocationPermission(): Boolean {
return (
ContextCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_BACKGROUND_LOCATION) ==
PackageManager.PERMISSION_GRANTED
)
}
suspend fun handleLocationGet(paramsJson: String?): GatewaySession.InvokeResult {
val mode = locationMode()
if (!isForeground() && mode != LocationMode.Always) {
if (!isForeground()) {
return GatewaySession.InvokeResult.error(
code = "LOCATION_BACKGROUND_UNAVAILABLE",
message = "LOCATION_BACKGROUND_UNAVAILABLE: background location requires Always",
message = "LOCATION_BACKGROUND_UNAVAILABLE: location requires OpenClaw to stay open",
)
}
if (!hasFineLocationPermission() && !hasCoarseLocationPermission()) {
@@ -55,12 +45,6 @@ class LocationHandler(
message = "LOCATION_PERMISSION_REQUIRED: grant Location permission",
)
}
if (!isForeground() && mode == LocationMode.Always && !hasBackgroundLocationPermission()) {
return GatewaySession.InvokeResult.error(
code = "LOCATION_PERMISSION_REQUIRED",
message = "LOCATION_PERMISSION_REQUIRED: enable Always in system Settings",
)
}
val (maxAgeMs, timeoutMs, desiredAccuracy) = parseLocationParams(paramsJson)
val preciseEnabled = locationPreciseEnabled()
val accuracy =

View File

@@ -1376,7 +1376,7 @@ private fun PermissionsStep(
InlineDivider()
PermissionToggleRow(
title = "Location",
subtitle = "location.get (while app is open unless set to Always later)",
subtitle = "location.get (while app is open)",
checked = enableLocation,
granted = locationGranted,
onCheckedChange = onLocationChange,

View File

@@ -114,7 +114,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
viewModel.setCameraEnabled(cameraOk)
}
var pendingLocationMode by remember { mutableStateOf<LocationMode?>(null) }
var pendingLocationRequest by remember { mutableStateOf(false) }
var pendingPreciseToggle by remember { mutableStateOf(false) }
val locationPermissionLauncher =
@@ -122,8 +122,6 @@ fun SettingsSheet(viewModel: MainViewModel) {
val fineOk = perms[Manifest.permission.ACCESS_FINE_LOCATION] == true
val coarseOk = perms[Manifest.permission.ACCESS_COARSE_LOCATION] == true
val granted = fineOk || coarseOk
val requestedMode = pendingLocationMode
pendingLocationMode = null
if (pendingPreciseToggle) {
pendingPreciseToggle = false
@@ -131,21 +129,9 @@ fun SettingsSheet(viewModel: MainViewModel) {
return@rememberLauncherForActivityResult
}
if (!granted) {
viewModel.setLocationMode(LocationMode.Off)
return@rememberLauncherForActivityResult
}
if (requestedMode != null) {
viewModel.setLocationMode(requestedMode)
if (requestedMode == LocationMode.Always) {
val backgroundOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (!backgroundOk) {
openAppSettings(context)
}
}
if (pendingLocationRequest) {
pendingLocationRequest = false
viewModel.setLocationMode(if (granted) LocationMode.WhileUsing else LocationMode.Off)
}
}
@@ -309,7 +295,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
}
}
fun requestLocationPermissions(targetMode: LocationMode) {
fun requestLocationPermissions() {
val fineOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
@@ -317,17 +303,9 @@ fun SettingsSheet(viewModel: MainViewModel) {
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (fineOk || coarseOk) {
viewModel.setLocationMode(targetMode)
if (targetMode == LocationMode.Always) {
val backgroundOk =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) ==
PackageManager.PERMISSION_GRANTED
if (!backgroundOk) {
openAppSettings(context)
}
}
viewModel.setLocationMode(LocationMode.WhileUsing)
} else {
pendingLocationMode = targetMode
pendingLocationRequest = true
locationPermissionLauncher.launch(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION),
)
@@ -783,20 +761,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
trailingContent = {
RadioButton(
selected = locationMode == LocationMode.WhileUsing,
onClick = { requestLocationPermissions(LocationMode.WhileUsing) },
)
},
)
HorizontalDivider(color = mobileBorder)
ListItem(
modifier = Modifier.fillMaxWidth(),
colors = listItemColors,
headlineContent = { Text("Always", style = mobileHeadline) },
supportingContent = { Text("Allow background location (requires system permission).", style = mobileCallout) },
trailingContent = {
RadioButton(
selected = locationMode == LocationMode.Always,
onClick = { requestLocationPermissions(LocationMode.Always) },
onClick = { requestLocationPermissions() },
)
},
)
@@ -816,13 +781,6 @@ fun SettingsSheet(viewModel: MainViewModel) {
)
}
}
item {
Text(
"Always may require Android Settings to allow background location.",
style = mobileCallout,
color = mobileTextSecondary,
)
}
item { HorizontalDivider(color = mobileBorder) }
// Screen

View File

@@ -87,7 +87,6 @@ class DeviceHandlerTest {
"camera",
"microphone",
"location",
"backgroundLocation",
"sms",
"notificationListener",
"notifications",

View File

@@ -1,8 +1,8 @@
---
summary: "Location command for nodes (location.get), permission modes, and background behavior"
summary: "Location command for nodes (location.get), permission modes, and Android foreground behavior"
read_when:
- Adding location node support or permissions UI
- Designing background location + push flows
- Designing Android location permissions or foreground behavior
title: "Location Command"
---
@@ -12,15 +12,15 @@ title: "Location Command"
- `location.get` is a node command (via `node.invoke`).
- Off by default.
- Settings use a selector: Off / While Using / Always.
- Android app settings use a selector: Off / While Using.
- Separate toggle: Precise Location.
## Why a selector (not just a switch)
OS permissions are multi-level. We can expose a selector in-app, but the OS still decides the actual grant.
- iOS/macOS: user can choose **While Using** or **Always** in system prompts/Settings. App can request upgrade, but OS may require Settings.
- Android: background location is a separate permission; on Android 10+ it often requires a Settings flow.
- iOS/macOS may expose **While Using** or **Always** in system prompts/Settings.
- Android app currently supports foreground location only.
- Precise location is a separate grant (iOS 14+ “Precise”, Android “fine” vs “coarse”).
Selector in UI drives our requested mode; actual grant lives in OS settings.
@@ -29,13 +29,12 @@ Selector in UI drives our requested mode; actual grant lives in OS settings.
Per node device:
- `location.enabledMode`: `off | whileUsing | always`
- `location.enabledMode`: `off | whileUsing`
- `location.preciseEnabled`: bool
UI behavior:
- Selecting `whileUsing` requests foreground permission.
- Selecting `always` first ensures `whileUsing`, then requests background (or sends user to Settings if required).
- If OS denies requested level, revert to the highest granted level and show status.
## Permissions mapping (node.permissions)
@@ -80,24 +79,11 @@ Errors (stable codes):
- `LOCATION_TIMEOUT`: no fix in time.
- `LOCATION_UNAVAILABLE`: system failure / no providers.
## Background behavior (future)
## Background behavior
Goal: model can request location even when node is backgrounded, but only when:
- User selected **Always**.
- OS grants background location.
- App is allowed to run in background for location (iOS background mode / Android foreground service or special allowance).
Push-triggered flow (future):
1. Gateway sends a push to the node (silent push or FCM data).
2. Node wakes briefly and requests location from the device.
3. Node forwards payload to Gateway.
Notes:
- iOS: Always permission + background location mode required. Silent push may be throttled; expect intermittent failures.
- Android: background location may require a foreground service; otherwise, expect denial.
- Android app denies `location.get` while backgrounded.
- Keep OpenClaw open when requesting location on Android.
- Other node platforms may differ.
## Model/tooling integration
@@ -109,5 +95,4 @@ Notes:
- Off: “Location sharing is disabled.”
- While Using: “Only when OpenClaw is open.”
- Always: “Allow background location. Requires system permission.”
- Precise: “Use precise GPS location. Toggle off to share approximate location.”