From 38fabb67a28bb4a63b4221ab4f157692f61e48ae Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:12:55 -0600 Subject: [PATCH 01/23] Don't use geofence for setting high accuracy mode. The geofence shape for a zone and the shape calculated by Home Assistant are not the same. Turn high accuracy mode on and off based on location updates instead of counting on the geofence. --- .../android/sensors/LocationSensorManager.kt | 82 +++++++++++-------- .../common/data/integration/ZoneAttributes.kt | 5 +- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 5f7c439319a..85b6b48bcaa 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -89,6 +89,7 @@ class LocationSensorManager @Inject constructor( private const val DEFAULT_LOCATION_MAX_WAIT_TIME: Long = 200000 private const val ZONE_NAME_NOT_HOME = "not_home" + private const val EXPANDED_GEOFENCE_SUFFIX = "_expanded" private const val HISTORY_DURATION = 60 * 60 * 48 * 1000L // 60(s) * 60(m) * 48(h) to millis @@ -179,8 +180,7 @@ class LocationSensorManager @Inject constructor( private var forceHighAccuracyModeOff = false private var highAccuracyModeEnabled = false - private var lastEnteredGeoZones: MutableList = ArrayList() - private var lastExitedGeoZones: MutableList = ArrayList() + private var needHighAccuracyMode = false private var lastHighAccuracyTriggerRange: Int = 0 private var lastHighAccuracyZones: List = ArrayList() @@ -544,7 +544,6 @@ class LocationSensorManager @Inject constructor( } var btDevConnected = false - var inZone = false var constraintsUsed = false if (highAccuracyModeBTDevices.isNotEmpty()) { @@ -595,19 +594,8 @@ class LocationSensorManager @Inject constructor( if (highAccuracyZones.isNotEmpty()) { constraintsUsed = true - // (Expanded) Zone entered - val zoneExpEntered = - lastEnteredGeoZones.isNotEmpty() && highAccuracyExpZones.containsAll(lastEnteredGeoZones) - - // Exits events are only used if expended zones are used. The exit events are used to determine the enter of the expanded zone from the original zone - // Zone exited - val zoneExited = - useTriggerRange && lastExitedGeoZones.isNotEmpty() && highAccuracyZones.containsAll(lastExitedGeoZones) - - inZone = zoneExpEntered || zoneExited - if (!forceHighAccuracyModeOn && !forceHighAccuracyModeOff) { - if (!inZone) { + if (!needHighAccuracyMode) { Timber.d("High accuracy mode disabled, because not in zone $highAccuracyExpZones") } else { Timber.d("High accuracy mode enabled, because in zone $highAccuracyExpZones") @@ -623,8 +611,8 @@ class LocationSensorManager @Inject constructor( // Else (NO BT dev connected and NOT in Zone), if min. one constraint is used -> High accuracy mode disabled (false) // if no constraint is used -> High accuracy mode enabled (true) return when { - highAccuracyBtZoneCombined && btDevConnected && inZone -> true - !highAccuracyBtZoneCombined && (btDevConnected || inZone) -> true + highAccuracyBtZoneCombined && btDevConnected && needHighAccuracyMode -> true + !highAccuracyBtZoneCombined && (btDevConnected || needHighAccuracyMode) -> true highAccuracyBtZoneCombined && !constraintsUsed -> false else -> !constraintsUsed } @@ -687,8 +675,6 @@ class LocationSensorManager @Inject constructor( val zoneIntent = getLocationUpdateIntent(true) geofencingClient?.removeGeofences(zoneIntent) geofenceRegistered.clear() - lastEnteredGeoZones.clear() - lastExitedGeoZones.clear() } else { Timber.d("Cannot remove geofence location requests. Geofence provider is not set.") } @@ -776,10 +762,12 @@ class LocationSensorManager @Inject constructor( logLocationUpdate(location, null, null, trigger, LocationHistoryItemResult.SKIPPED_ACCURACY) } else { HighAccuracyLocationService.updateNotificationAddress(applicationContext, location) + updateNeedHighAccuracyMode(location) // Send new location to Home Assistant serverIds.forEach { ioScope.launch { sendLocationUpdate(location, it, trigger) } } + setupBackgroundLocation() } } } @@ -828,18 +816,6 @@ class LocationSensorManager @Inject constructor( for (triggeringGeofence in geofencingEvent.triggeringGeofences!!) { val zone = triggeringGeofence.requestId - if (zoneStatusEvent == "android.zone_entered") { - lastEnteredGeoZones.add(zone) - } else { - lastEnteredGeoZones.remove(zone) - } - - if (zoneStatusEvent == "android.zone_exited") { - lastExitedGeoZones.add(zone) - } else { - lastExitedGeoZones.remove(zone) - } - val zoneAttr = mapOf( "accuracy" to geofencingEvent.triggeringLocation!!.accuracy, "altitude" to geofencingEvent.triggeringLocation!!.altitude, @@ -897,6 +873,7 @@ class LocationSensorManager @Inject constructor( ) requestSingleAccurateLocation() } else { + updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) getEnabledServers(zoneLocation).forEach { ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) } } @@ -1154,13 +1131,52 @@ class LocationSensorManager @Inject constructor( return results[0] } + /** + * Sets [needHighAccuracyMode] from GPS: true when the location is inside an expanded + * high-accuracy fence but not yet inside the inner zone. + */ + private suspend fun updateNeedHighAccuracyMode(location: Location) { + val triggerRange = getHighAccuracyModeTriggerRange() + val highAccuracyZones = getHighAccuracyModeZones(false) + val entered = mutableListOf() + val exited = mutableListOf() + needHighAccuracyMode = false + + getEnabledServers(zoneLocation).forEach { serverId -> + getZones(serverId).forEach { zone -> + val requestId = "${serverId}_${zone.entityId}" + val inZone = isLocationInZone(location, zone) + Timber.d("Zone $requestId is ${if (inZone) "in" else "out"}side the zone") + if (triggerRange > 0 && highAccuracyZones.contains(requestId)) { + val expandedRequestId = requestId + EXPANDED_GEOFENCE_SUFFIX + val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) + Timber.d("Expanded zone $expandedRequestId is ${if (inExpandedZone) "in" else "out"}side the zone") + if (inExpandedZone && !inZone) { + needHighAccuracyMode = true + } + } + } + } + Timber.d("Updated need high accuracy mode from GPS: $needHighAccuracyMode") + } + + private fun isLocationInZone( + location: Location, + zone: Entity, + extraRadiusMeters: Float = 0f, + ): Boolean { + if (zone.attributes["radius"] !is Number) return false + if (zone.attributes["latitude"] !is Number || zone.attributes["longitude"] !is Number) return false + return zone.containsWithAccuracy(location, extraRadiusMeters = extraRadiusMeters) + } + private fun addGeofenceToBuilder( geofencingRequestBuilder: GeofencingRequest.Builder, serverId: Int, zone: Entity, triggerRange: Int = 0, ) { - val postRequestId = if (triggerRange > 0) "_expanded" else "" + val postRequestId = if (triggerRange > 0) EXPANDED_GEOFENCE_SUFFIX else "" geofencingRequestBuilder .addGeofence( Geofence.Builder() @@ -1218,7 +1234,7 @@ class LocationSensorManager @Inject constructor( ) return if (highAccuracyZones.isNotEmpty()) { - val expanded = if (expandedZones) "_expanded" else "" + val expanded = if (expandedZones) EXPANDED_GEOFENCE_SUFFIX else "" highAccuracyZones.split(",").map { it.trim() + expanded } } else { emptyList() diff --git a/common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/ZoneAttributes.kt b/common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/ZoneAttributes.kt index f9fa4e869d5..971230429dc 100644 --- a/common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/ZoneAttributes.kt +++ b/common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/ZoneAttributes.kt @@ -6,16 +6,17 @@ import android.location.Location * Returns if the provided location is estimated to be in the zone. * This function will also consider accuracy, so if the GPS location is outside the zone but the * accuracy suggests that it could be in the zone, this function will still return `true`. + * [extraRadiusMeters] expands the zone (for example a high-accuracy trigger range). */ // TODO Introduce back ZoneAttribute class https://github.com/home-assistant/android/issues/5340 -fun Entity.containsWithAccuracy(location: Location): Boolean { +fun Entity.containsWithAccuracy(location: Location, extraRadiusMeters: Float = 0f): Boolean { val zoneCenter = Location("").apply { latitude = (attributes["latitude"] as Number).toDouble() longitude = (attributes["longitude"] as Number).toDouble() } return ( location.distanceTo(zoneCenter) - (attributes["radius"] as Number).toFloat() - - location.accuracy.coerceAtLeast(0f) + extraRadiusMeters - location.accuracy.coerceAtLeast(0f) ) <= 0 } From 22d5974b8d393f663e9ce6d549440167dd7fbc0e Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:04:20 -0600 Subject: [PATCH 02/23] Call updateNeedHighAccuracyMode to prevent conflicts in getZones --- .../companion/android/sensors/LocationSensorManager.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 85b6b48bcaa..148fb069b99 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -762,7 +762,9 @@ class LocationSensorManager @Inject constructor( logLocationUpdate(location, null, null, trigger, LocationHistoryItemResult.SKIPPED_ACCURACY) } else { HighAccuracyLocationService.updateNotificationAddress(applicationContext, location) - updateNeedHighAccuracyMode(location) + ioScope.launch { + updateNeedHighAccuracyMode(location) + } // Send new location to Home Assistant serverIds.forEach { ioScope.launch { sendLocationUpdate(location, it, trigger) } @@ -873,7 +875,9 @@ class LocationSensorManager @Inject constructor( ) requestSingleAccurateLocation() } else { - updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) + ioScope.launch { + updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) + } getEnabledServers(zoneLocation).forEach { ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) } } From 101d8448436f906ca0e55a8df710d23f190227e4 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:06:50 -0600 Subject: [PATCH 03/23] Fix log message --- .../companion/android/sensors/LocationSensorManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 148fb069b99..20f86299139 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -596,9 +596,9 @@ class LocationSensorManager @Inject constructor( if (!forceHighAccuracyModeOn && !forceHighAccuracyModeOff) { if (!needHighAccuracyMode) { - Timber.d("High accuracy mode disabled, because not in zone $highAccuracyExpZones") + Timber.d("High accuracy mode disabled, because not in high accuracy zone") } else { - Timber.d("High accuracy mode enabled, because in zone $highAccuracyExpZones") + Timber.d("High accuracy mode enabled, because in zone high accuracy zone") } } } From cd6cc48c9238dd0e099fdd1858ab5dfc9af39447 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:26:31 -0600 Subject: [PATCH 04/23] unused variables --- .../companion/android/sensors/LocationSensorManager.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 20f86299139..a660d4d0b3e 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -596,9 +596,9 @@ class LocationSensorManager @Inject constructor( if (!forceHighAccuracyModeOn && !forceHighAccuracyModeOff) { if (!needHighAccuracyMode) { - Timber.d("High accuracy mode disabled, because not in high accuracy zone") + Timber.d("High accuracy mode disabled, because not in zone") } else { - Timber.d("High accuracy mode enabled, because in zone high accuracy zone") + Timber.d("High accuracy mode enabled, because in zone") } } } @@ -1142,8 +1142,6 @@ class LocationSensorManager @Inject constructor( private suspend fun updateNeedHighAccuracyMode(location: Location) { val triggerRange = getHighAccuracyModeTriggerRange() val highAccuracyZones = getHighAccuracyModeZones(false) - val entered = mutableListOf() - val exited = mutableListOf() needHighAccuracyMode = false getEnabledServers(zoneLocation).forEach { serverId -> From 60196ef2aec7408aa22bebcef0dd5dc9b2d0a308 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:29:27 -0600 Subject: [PATCH 05/23] too many debug logs --- .../companion/android/sensors/LocationSensorManager.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index a660d4d0b3e..eef395a88bb 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -1152,7 +1152,6 @@ class LocationSensorManager @Inject constructor( if (triggerRange > 0 && highAccuracyZones.contains(requestId)) { val expandedRequestId = requestId + EXPANDED_GEOFENCE_SUFFIX val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) - Timber.d("Expanded zone $expandedRequestId is ${if (inExpandedZone) "in" else "out"}side the zone") if (inExpandedZone && !inZone) { needHighAccuracyMode = true } From 508c889fd8cb2c45bf4984f610f602349775202c Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:34:14 -0600 Subject: [PATCH 06/23] try again with the scopes --- .../companion/android/sensors/LocationSensorManager.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index eef395a88bb..9452d895f63 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -762,9 +762,7 @@ class LocationSensorManager @Inject constructor( logLocationUpdate(location, null, null, trigger, LocationHistoryItemResult.SKIPPED_ACCURACY) } else { HighAccuracyLocationService.updateNotificationAddress(applicationContext, location) - ioScope.launch { - updateNeedHighAccuracyMode(location) - } + updateNeedHighAccuracyMode(location) // Send new location to Home Assistant serverIds.forEach { ioScope.launch { sendLocationUpdate(location, it, trigger) } @@ -875,9 +873,7 @@ class LocationSensorManager @Inject constructor( ) requestSingleAccurateLocation() } else { - ioScope.launch { - updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) - } + updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) getEnabledServers(zoneLocation).forEach { ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) } } From 1fb60527cdff43a0ff52e5692ce8333b508b7f03 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:44:04 -0600 Subject: [PATCH 07/23] use volatile --- .../companion/android/sensors/LocationSensorManager.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 9452d895f63..fc0ce7d8c3a 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -180,6 +180,7 @@ class LocationSensorManager @Inject constructor( private var forceHighAccuracyModeOff = false private var highAccuracyModeEnabled = false + @Volatile private var needHighAccuracyMode = false private var lastHighAccuracyTriggerRange: Int = 0 @@ -1138,22 +1139,21 @@ class LocationSensorManager @Inject constructor( private suspend fun updateNeedHighAccuracyMode(location: Location) { val triggerRange = getHighAccuracyModeTriggerRange() val highAccuracyZones = getHighAccuracyModeZones(false) - needHighAccuracyMode = false + var inExpandedOnly = false getEnabledServers(zoneLocation).forEach { serverId -> getZones(serverId).forEach { zone -> val requestId = "${serverId}_${zone.entityId}" val inZone = isLocationInZone(location, zone) - Timber.d("Zone $requestId is ${if (inZone) "in" else "out"}side the zone") if (triggerRange > 0 && highAccuracyZones.contains(requestId)) { - val expandedRequestId = requestId + EXPANDED_GEOFENCE_SUFFIX val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) if (inExpandedZone && !inZone) { - needHighAccuracyMode = true + inExpandedOnly = true } } } } + needHighAccuracyMode = inExpandedOnly Timber.d("Updated need high accuracy mode from GPS: $needHighAccuracyMode") } From ebfabdf69f58cb84af7442db58e98e0f98da7ed0 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:37:34 -0600 Subject: [PATCH 08/23] refactor --- .../android/sensors/LocationSensorManager.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index fc0ce7d8c3a..d225d02f5cc 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -1140,15 +1140,17 @@ class LocationSensorManager @Inject constructor( val triggerRange = getHighAccuracyModeTriggerRange() val highAccuracyZones = getHighAccuracyModeZones(false) var inExpandedOnly = false - - getEnabledServers(zoneLocation).forEach { serverId -> - getZones(serverId).forEach { zone -> - val requestId = "${serverId}_${zone.entityId}" - val inZone = isLocationInZone(location, zone) - if (triggerRange > 0 && highAccuracyZones.contains(requestId)) { - val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) - if (inExpandedZone && !inZone) { - inExpandedOnly = true + + if (triggerRange > 0) { + getEnabledServers(zoneLocation).forEach { serverId -> + getZones(serverId).forEach { zone -> + val requestId = "${serverId}_${zone.entityId}" + val inZone = isLocationInZone(location, zone) + if (highAccuracyZones.contains(requestId)) { + val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) + if (inExpandedZone && !inZone) { + inExpandedOnly = true + } } } } From b9c5c426ad0ac6a776e6590788c274d8e7ea4c0a Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:20:07 -0600 Subject: [PATCH 09/23] Refactor to better order checks. --- .../companion/android/sensors/LocationSensorManager.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index d225d02f5cc..c0760dfcaec 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -1145,11 +1145,13 @@ class LocationSensorManager @Inject constructor( getEnabledServers(zoneLocation).forEach { serverId -> getZones(serverId).forEach { zone -> val requestId = "${serverId}_${zone.entityId}" - val inZone = isLocationInZone(location, zone) if (highAccuracyZones.contains(requestId)) { - val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) - if (inExpandedZone && !inZone) { - inExpandedOnly = true + val inZone = isLocationInZone(location, zone) + if (!inZone) { + val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) + if (inExpandedZone) { + inExpandedOnly = true + } } } } From d70ad173c9773dd53cce23e2a420274f324518cd Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 26 Aug 2026 20:27:03 -0600 Subject: [PATCH 10/23] use easier loops --- .../android/sensors/LocationSensorManager.kt | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index c0760dfcaec..302b8aff285 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -1139,26 +1139,24 @@ class LocationSensorManager @Inject constructor( private suspend fun updateNeedHighAccuracyMode(location: Location) { val triggerRange = getHighAccuracyModeTriggerRange() val highAccuracyZones = getHighAccuracyModeZones(false) - var inExpandedOnly = false if (triggerRange > 0) { - getEnabledServers(zoneLocation).forEach { serverId -> - getZones(serverId).forEach { zone -> + for (serverId in getEnabledServers(zoneLocation)) { + for (zone in getZones(serverId)) { val requestId = "${serverId}_${zone.entityId}" - if (highAccuracyZones.contains(requestId)) { - val inZone = isLocationInZone(location, zone) - if (!inZone) { - val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) - if (inExpandedZone) { - inExpandedOnly = true - } - } - } + if (!highAccuracyZones.contains(requestId)) continue + val inZone = isLocationInZone(location, zone) + if (inZone) continue + val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) + if (!inExpandedZone) continue + needHighAccuracyMode = true + Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") + return } } - } - needHighAccuracyMode = inExpandedOnly - Timber.d("Updated need high accuracy mode from GPS: $needHighAccuracyMode") + } + needHighAccuracyMode = false + Timber.d("Updated need high accuracy mode from GPS: false") } private fun isLocationInZone( From 84694c8a9455e9ef71eaaf3615aa3c526fbc66a6 Mon Sep 17 00:00:00 2001 From: Eyal <109809+eyal0@users.noreply.github.com> Date: Thu, 27 Aug 2026 08:42:32 -0600 Subject: [PATCH 11/23] Update need high accuracy in dispatchers.io scope Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../android/sensors/LocationSensorManager.kt | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 302b8aff285..13791540ec4 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -1136,28 +1136,36 @@ class LocationSensorManager @Inject constructor( * Sets [needHighAccuracyMode] from GPS: true when the location is inside an expanded * high-accuracy fence but not yet inside the inner zone. */ - private suspend fun updateNeedHighAccuracyMode(location: Location) { +private suspend fun updateNeedHighAccuracyMode(location: Location) { + kotlinx.coroutines.withContext(Dispatchers.IO) { + val previousNeedHighAccuracyMode = needHighAccuracyMode + val triggerRange = getHighAccuracyModeTriggerRange() val highAccuracyZones = getHighAccuracyModeZones(false) - + if (triggerRange > 0) { for (serverId in getEnabledServers(zoneLocation)) { - for (zone in getZones(serverId)) { + for (zone in getZones(serverId)) { val requestId = "${serverId}_${zone.entityId}" if (!highAccuracyZones.contains(requestId)) continue - val inZone = isLocationInZone(location, zone) - if (inZone) continue - val inExpandedZone = isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat()) - if (!inExpandedZone) continue + if (isLocationInZone(location, zone)) continue + if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue + needHighAccuracyMode = true - Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") - return + if (!previousNeedHighAccuracyMode) { + Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") + } + return@withContext } } - } + } + needHighAccuracyMode = false - Timber.d("Updated need high accuracy mode from GPS: false") + if (previousNeedHighAccuracyMode) { + Timber.d("Updated need high accuracy mode from GPS: false") + } } +} private fun isLocationInZone( location: Location, From 204de8ebaef3cc838e0b2c37a943bf9c8fd9b064 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:16:42 -0600 Subject: [PATCH 12/23] lint --- .../android/sensors/LocationSensorManager.kt | 50 +++++++++---------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 13791540ec4..626037d0550 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -1136,42 +1136,38 @@ class LocationSensorManager @Inject constructor( * Sets [needHighAccuracyMode] from GPS: true when the location is inside an expanded * high-accuracy fence but not yet inside the inner zone. */ -private suspend fun updateNeedHighAccuracyMode(location: Location) { - kotlinx.coroutines.withContext(Dispatchers.IO) { - val previousNeedHighAccuracyMode = needHighAccuracyMode + private suspend fun updateNeedHighAccuracyMode(location: Location) { + kotlinx.coroutines.withContext(Dispatchers.IO) { + val previousNeedHighAccuracyMode = needHighAccuracyMode - val triggerRange = getHighAccuracyModeTriggerRange() - val highAccuracyZones = getHighAccuracyModeZones(false) + val triggerRange = getHighAccuracyModeTriggerRange() + val highAccuracyZones = getHighAccuracyModeZones(false) - if (triggerRange > 0) { - for (serverId in getEnabledServers(zoneLocation)) { - for (zone in getZones(serverId)) { - val requestId = "${serverId}_${zone.entityId}" - if (!highAccuracyZones.contains(requestId)) continue - if (isLocationInZone(location, zone)) continue - if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue - - needHighAccuracyMode = true - if (!previousNeedHighAccuracyMode) { - Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") + if (triggerRange > 0) { + for (serverId in getEnabledServers(zoneLocation)) { + for (zone in getZones(serverId)) { + val requestId = "${serverId}_${zone.entityId}" + if (!highAccuracyZones.contains(requestId)) continue + if (isLocationInZone(location, zone)) continue + if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue + + needHighAccuracyMode = true + if (!previousNeedHighAccuracyMode) { + Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") + } + return@withContext } - return@withContext } } - } - needHighAccuracyMode = false - if (previousNeedHighAccuracyMode) { - Timber.d("Updated need high accuracy mode from GPS: false") + needHighAccuracyMode = false + if (previousNeedHighAccuracyMode) { + Timber.d("Updated need high accuracy mode from GPS: false") + } } } -} - private fun isLocationInZone( - location: Location, - zone: Entity, - extraRadiusMeters: Float = 0f, - ): Boolean { + private fun isLocationInZone(location: Location, zone: Entity, extraRadiusMeters: Float = 0f): Boolean { if (zone.attributes["radius"] !is Number) return false if (zone.attributes["latitude"] !is Number || zone.attributes["longitude"] !is Number) return false return zone.containsWithAccuracy(location, extraRadiusMeters = extraRadiusMeters) From 2548e5378a7327cef8897b2f36f763c42e3702ff Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:42:01 -0600 Subject: [PATCH 13/23] Add mutex for thread safety in zone retrieval Refactored the getZones function to use a Mutex for thread safety when accessing the zones map. This change ensures that concurrent access to zone data is properly synchronized, preventing potential race conditions. Additionally, cleaned up the logic for updating the need for high accuracy mode by removing unnecessary coroutine context switching. --- .../android/sensors/LocationSensorManager.kt | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 626037d0550..891ad5f5c14 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -55,6 +55,8 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber @Singleton @@ -169,6 +171,7 @@ class LocationSensorManager @Inject constructor( private var lastLocationReceived = mutableMapOf() private var lastUpdateLocation = mutableMapOf() + private val zonesMutex = Mutex() private var zones = mutableMapOf>() private var zonesLastReceived = mutableMapOf() @@ -1070,20 +1073,22 @@ class LocationSensorManager @Inject constructor( } private suspend fun getZones(serverId: Int, forceRefresh: Boolean = false): List { - if ( - forceRefresh || - zones[serverId].isNullOrEmpty() || - (zonesLastReceived[serverId] ?: 0) < (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(4)) - ) { - try { - zones[serverId] = serverManager.integrationRepository(serverId).getZones() - zonesLastReceived[serverId] = System.currentTimeMillis() - } catch (e: Exception) { - Timber.e(e, "Error receiving zones from Home Assistant") - if (forceRefresh) zones[serverId] = emptyList() + return zonesMutex.withLock { + if ( + forceRefresh || + zones[serverId].isNullOrEmpty() || + (zonesLastReceived[serverId] ?: 0) < (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(4)) + ) { + try { + zones[serverId] = serverManager.integrationRepository(serverId).getZones() + zonesLastReceived[serverId] = System.currentTimeMillis() + } catch (e: Exception) { + Timber.e(e, "Error receiving zones from Home Assistant") + if (forceRefresh) zones[serverId] = emptyList() + } } + zones[serverId] ?: emptyList() } - return zones[serverId] ?: emptyList() } private suspend fun createGeofencingRequest(): GeofencingRequest? { @@ -1137,33 +1142,31 @@ class LocationSensorManager @Inject constructor( * high-accuracy fence but not yet inside the inner zone. */ private suspend fun updateNeedHighAccuracyMode(location: Location) { - kotlinx.coroutines.withContext(Dispatchers.IO) { - val previousNeedHighAccuracyMode = needHighAccuracyMode + val previousNeedHighAccuracyMode = needHighAccuracyMode - val triggerRange = getHighAccuracyModeTriggerRange() - val highAccuracyZones = getHighAccuracyModeZones(false) + val triggerRange = getHighAccuracyModeTriggerRange() + val highAccuracyZones = getHighAccuracyModeZones(false) - if (triggerRange > 0) { - for (serverId in getEnabledServers(zoneLocation)) { - for (zone in getZones(serverId)) { - val requestId = "${serverId}_${zone.entityId}" - if (!highAccuracyZones.contains(requestId)) continue - if (isLocationInZone(location, zone)) continue - if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue - - needHighAccuracyMode = true - if (!previousNeedHighAccuracyMode) { - Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") - } - return@withContext + if (triggerRange > 0 && highAccuracyZones.isNotEmpty()) { + for (serverId in getEnabledServers(zoneLocation)) { + for (zone in getZones(serverId)) { + val requestId = "${serverId}_${zone.entityId}" + if (!highAccuracyZones.contains(requestId)) continue + if (isLocationInZone(location, zone)) continue + if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue + + needHighAccuracyMode = true + if (!previousNeedHighAccuracyMode) { + Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") } + return } } + } - needHighAccuracyMode = false - if (previousNeedHighAccuracyMode) { - Timber.d("Updated need high accuracy mode from GPS: false") - } + needHighAccuracyMode = false + if (previousNeedHighAccuracyMode) { + Timber.d("Updated need high accuracy mode from GPS: false") } } From 93da6deab93a32eabe1dd998452faf8fe51de540 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:55:36 -0600 Subject: [PATCH 14/23] better log message --- .../companion/android/sensors/LocationSensorManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 891ad5f5c14..f85427e1770 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -600,9 +600,9 @@ class LocationSensorManager @Inject constructor( if (!forceHighAccuracyModeOn && !forceHighAccuracyModeOff) { if (!needHighAccuracyMode) { - Timber.d("High accuracy mode disabled, because not in zone") + Timber.d("High accuracy mode disabled, because not in expanded zone") } else { - Timber.d("High accuracy mode enabled, because in zone") + Timber.d("High accuracy mode enabled, because in expanded zone") } } } From 6eadafee136e0eee08e77558fdd41a9840744586 Mon Sep 17 00:00:00 2001 From: Eyal <109809+eyal0@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:52:41 -0600 Subject: [PATCH 15/23] Only run setupBackgroundLocation if high accuracy mode changes. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../companion/android/sensors/LocationSensorManager.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index f85427e1770..dae0e0c47bc 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -766,13 +766,15 @@ class LocationSensorManager @Inject constructor( logLocationUpdate(location, null, null, trigger, LocationHistoryItemResult.SKIPPED_ACCURACY) } else { HighAccuracyLocationService.updateNotificationAddress(applicationContext, location) + val previousNeedHighAccuracyMode = needHighAccuracyMode updateNeedHighAccuracyMode(location) // Send new location to Home Assistant serverIds.forEach { ioScope.launch { sendLocationUpdate(location, it, trigger) } } - setupBackgroundLocation() - } + if (needHighAccuracyMode != previousNeedHighAccuracyMode) { + setupBackgroundLocation() + } } } From bcd0bd6721b8085df96e62783803ce0403d6bc3f Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:13:51 -0600 Subject: [PATCH 16/23] fixup! Only run setupBackgroundLocation if high accuracy mode changes. --- .../companion/android/sensors/LocationSensorManager.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index dae0e0c47bc..78e0ab8325d 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -775,6 +775,7 @@ class LocationSensorManager @Inject constructor( if (needHighAccuracyMode != previousNeedHighAccuracyMode) { setupBackgroundLocation() } + } } } From 1f149fdf3f758d78706ea3e09c12ca79d2e0fc04 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:46:02 -0600 Subject: [PATCH 17/23] remove unused variables --- .../companion/android/sensors/LocationSensorManager.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 78e0ab8325d..d2375bdb744 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -539,13 +539,7 @@ class LocationSensorManager @Inject constructor( .toMutableList() val highAccuracyBtZoneCombined = getHighAccuracyBTZoneCombinedSetting() - val useTriggerRange = getHighAccuracyModeTriggerRange() > 0 val highAccuracyZones = getHighAccuracyModeZones(false) - var highAccuracyExpZones = highAccuracyZones - if (useTriggerRange) { - // Use a trigger range, if defined - highAccuracyExpZones = getHighAccuracyModeZones(true) - } var btDevConnected = false var constraintsUsed = false From c629a4c798e969bc45ee6ae47e9b54ac9e9f6009 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Fri, 28 Aug 2026 21:47:48 -0600 Subject: [PATCH 18/23] Refactor LocationSensorManager to track changes in high accuracy mode before setting up background location. This ensures that setupBackgroundLocation is only called when necessary, improving efficiency. --- .../companion/android/sensors/LocationSensorManager.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index d2375bdb744..94e95c0ff41 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -863,6 +863,8 @@ class LocationSensorManager @Inject constructor( Geofence.GEOFENCE_TRANSITION_DWELL -> LocationUpdateTrigger.GEOFENCE_DWELL else -> null } + var previousNeedHighAccuracyMode = needHighAccuracyMode + updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) if (geofencingEvent.triggeringLocation!!.accuracy > minAccuracy) { Timber.w("Geofence location accuracy didn't meet requirements, requesting new location.") logLocationUpdate( @@ -874,13 +876,13 @@ class LocationSensorManager @Inject constructor( ) requestSingleAccurateLocation() } else { - updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) getEnabledServers(zoneLocation).forEach { ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) } } } - - setupBackgroundLocation() + if (needHighAccuracyMode != previousNeedHighAccuracyMode) { + setupBackgroundLocation() + } } private suspend fun sendLocationUpdate(location: Location, serverId: Int, trigger: LocationUpdateTrigger?) { From d8dabbc1d82e36283c1bba2d3d467c6aa057d4a4 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:43:35 -0600 Subject: [PATCH 19/23] WIP --- .../companion/android/sensors/LocationSensorManager.kt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 94e95c0ff41..818c9b8c1cf 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -760,15 +760,12 @@ class LocationSensorManager @Inject constructor( logLocationUpdate(location, null, null, trigger, LocationHistoryItemResult.SKIPPED_ACCURACY) } else { HighAccuracyLocationService.updateNotificationAddress(applicationContext, location) - val previousNeedHighAccuracyMode = needHighAccuracyMode updateNeedHighAccuracyMode(location) // Send new location to Home Assistant serverIds.forEach { ioScope.launch { sendLocationUpdate(location, it, trigger) } } - if (needHighAccuracyMode != previousNeedHighAccuracyMode) { - setupBackgroundLocation() - } + setupBackgroundLocation() } } } @@ -863,7 +860,6 @@ class LocationSensorManager @Inject constructor( Geofence.GEOFENCE_TRANSITION_DWELL -> LocationUpdateTrigger.GEOFENCE_DWELL else -> null } - var previousNeedHighAccuracyMode = needHighAccuracyMode updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) if (geofencingEvent.triggeringLocation!!.accuracy > minAccuracy) { Timber.w("Geofence location accuracy didn't meet requirements, requesting new location.") @@ -880,9 +876,7 @@ class LocationSensorManager @Inject constructor( ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) } } } - if (needHighAccuracyMode != previousNeedHighAccuracyMode) { - setupBackgroundLocation() - } + setupBackgroundLocation() } private suspend fun sendLocationUpdate(location: Location, serverId: Int, trigger: LocationUpdateTrigger?) { From 97a440b3c93ed3a9658255bc4998743ac020761c Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:13:33 -0600 Subject: [PATCH 20/23] whitespace --- .../companion/android/sensors/LocationSensorManager.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 818c9b8c1cf..13dd0b7ef8d 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -876,6 +876,7 @@ class LocationSensorManager @Inject constructor( ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) } } } + setupBackgroundLocation() } From 0f0075b094ec346898471ce557a488391ee7d9b4 Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:48:19 -0600 Subject: [PATCH 21/23] WIP --- .../android/sensors/LocationSensorManager.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 13dd0b7ef8d..57a1de3d18d 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -760,12 +760,15 @@ class LocationSensorManager @Inject constructor( logLocationUpdate(location, null, null, trigger, LocationHistoryItemResult.SKIPPED_ACCURACY) } else { HighAccuracyLocationService.updateNotificationAddress(applicationContext, location) - updateNeedHighAccuracyMode(location) + val newNeedHighAccuracyMode = isInHighAccuracyTriggerRange(location) + if (needHighAccuracyMode != newNeedHighAccuracyMode) { + needHighAccuracyMode = newNeedHighAccuracyMode + setupBackgroundLocation() + } // Send new location to Home Assistant serverIds.forEach { ioScope.launch { sendLocationUpdate(location, it, trigger) } } - setupBackgroundLocation() } } } @@ -860,7 +863,7 @@ class LocationSensorManager @Inject constructor( Geofence.GEOFENCE_TRANSITION_DWELL -> LocationUpdateTrigger.GEOFENCE_DWELL else -> null } - updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!) + needHighAccuracyMode = isInHighAccuracyTriggerRange(geofencingEvent.triggeringLocation!!) if (geofencingEvent.triggeringLocation!!.accuracy > minAccuracy) { Timber.w("Geofence location accuracy didn't meet requirements, requesting new location.") logLocationUpdate( @@ -1132,12 +1135,10 @@ class LocationSensorManager @Inject constructor( } /** - * Sets [needHighAccuracyMode] from GPS: true when the location is inside an expanded - * high-accuracy fence but not yet inside the inner zone. + * Returns true when [location] is inside an expanded high-accuracy fence but not inside the + * inner zone. */ - private suspend fun updateNeedHighAccuracyMode(location: Location) { - val previousNeedHighAccuracyMode = needHighAccuracyMode - + private suspend fun isInHighAccuracyTriggerRange(location: Location): Boolean { val triggerRange = getHighAccuracyModeTriggerRange() val highAccuracyZones = getHighAccuracyModeZones(false) @@ -1148,20 +1149,12 @@ class LocationSensorManager @Inject constructor( if (!highAccuracyZones.contains(requestId)) continue if (isLocationInZone(location, zone)) continue if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue - - needHighAccuracyMode = true - if (!previousNeedHighAccuracyMode) { - Timber.d("Updated need high accuracy mode from GPS: true, in $requestId") - } - return + return true } } } - needHighAccuracyMode = false - if (previousNeedHighAccuracyMode) { - Timber.d("Updated need high accuracy mode from GPS: false") - } + return false } private fun isLocationInZone(location: Location, zone: Entity, extraRadiusMeters: Float = 0f): Boolean { From 9bffbcc4ad681b7a58b571a75d0b2bee331a9dda Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:04:48 -0600 Subject: [PATCH 22/23] Refactor LocationSensorManager to remove unnecessary Mutex usage for zone management. Simplified getZones method by directly returning zones without locking, improving code readability and performance. --- .../android/sensors/LocationSensorManager.kt | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 57a1de3d18d..0fa5f532c67 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -55,8 +55,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import timber.log.Timber @Singleton @@ -171,7 +169,6 @@ class LocationSensorManager @Inject constructor( private var lastLocationReceived = mutableMapOf() private var lastUpdateLocation = mutableMapOf() - private val zonesMutex = Mutex() private var zones = mutableMapOf>() private var zonesLastReceived = mutableMapOf() @@ -1070,22 +1067,20 @@ class LocationSensorManager @Inject constructor( } private suspend fun getZones(serverId: Int, forceRefresh: Boolean = false): List { - return zonesMutex.withLock { - if ( - forceRefresh || - zones[serverId].isNullOrEmpty() || - (zonesLastReceived[serverId] ?: 0) < (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(4)) - ) { - try { - zones[serverId] = serverManager.integrationRepository(serverId).getZones() - zonesLastReceived[serverId] = System.currentTimeMillis() - } catch (e: Exception) { - Timber.e(e, "Error receiving zones from Home Assistant") - if (forceRefresh) zones[serverId] = emptyList() - } + if ( + forceRefresh || + zones[serverId].isNullOrEmpty() || + (zonesLastReceived[serverId] ?: 0) < (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(4)) + ) { + try { + zones[serverId] = serverManager.integrationRepository(serverId).getZones() + zonesLastReceived[serverId] = System.currentTimeMillis() + } catch (e: Exception) { + Timber.e(e, "Error receiving zones from Home Assistant") + if (forceRefresh) zones[serverId] = emptyList() } - zones[serverId] ?: emptyList() } + return zones[serverId] ?: emptyList() } private suspend fun createGeofencingRequest(): GeofencingRequest? { From 7e25c13f34854a060657cd7a745985e18d03895f Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:21:46 -0600 Subject: [PATCH 23/23] Enhance LocationSensorManager to track the start time of high accuracy mode. This allows for more accurate detection of when high accuracy mode has stopped, preventing unnecessary restarts and improving overall efficiency in location updates. --- .../android/sensors/LocationSensorManager.kt | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 0fa5f532c67..a803a94f1fe 100644 --- a/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -176,6 +176,7 @@ class LocationSensorManager @Inject constructor( private var lastHighAccuracyMode = false private var lastHighAccuracyUpdateInterval = DEFAULT_UPDATE_INTERVAL_HA_SECONDS + private var lastHighAccuracyModeStart = 0L private var forceHighAccuracyModeOn = false private var forceHighAccuracyModeOff = false private var highAccuracyModeEnabled = false @@ -325,19 +326,26 @@ class LocationSensorManager @Inject constructor( val now = System.currentTimeMillis() if ( (!highAccuracyModeEnabled && isBackgroundLocationSetup) && - (lastLocationReceived.all { (it.value + (DEFAULT_LOCATION_MAX_WAIT_TIME * 2L)) < now }) + lastLocationReceived.isNotEmpty() && + lastLocationReceived.all { (it.value + (DEFAULT_LOCATION_MAX_WAIT_TIME * 2L)) < now } ) { Timber.d("Background location updates appear to have stopped, restarting location updates") isBackgroundLocationSetup = false fusedLocationProviderClient?.flushLocations() removeBackgroundUpdateRequests() - } else if ( - highAccuracyModeEnabled && - (lastLocationReceived.all { (it.value + (getHighAccuracyModeUpdateInterval().toLong() * 2000L)) < now }) - ) { - Timber.d("High accuracy mode appears to have stopped, restarting high accuracy mode") - isBackgroundLocationSetup = false - stopHighAccuracyService() + } else if (highAccuracyModeEnabled) { + val timeoutMs = getHighAccuracyModeUpdateInterval().toLong() * 2000L + val lastProgress = maxOf( + lastHighAccuracyModeStart, + lastLocationReceived.values.maxOrNull() ?: 0L, + ) + // lastLocationReceived.all { stale } is true for an empty map, which previously + // restarted HAM on every startHighAccuracyService -> updateAllSensors cycle. + if (lastProgress > 0L && lastProgress + timeoutMs < now) { + Timber.d("High accuracy mode appears to have stopped, restarting high accuracy mode") + isBackgroundLocationSetup = false + stopHighAccuracyService() + } } setupBackgroundLocation(backgroundEnabled, zoneEnabled) @@ -443,6 +451,7 @@ class LocationSensorManager @Inject constructor( } private suspend fun startHighAccuracyService(intervalInSeconds: Int) { + lastHighAccuracyModeStart = System.currentTimeMillis() onSensorUpdated( highAccuracyMode, true,