mirror of
https://github.com/home-assistant/android
synced 2024-07-22 10:54:12 +00:00
Support zone name only location tracking (#2969)
* Support zone name only location tracking * Update strings to match iOS * Only add Sent location setting on supported versions * String updates * Simplify code as the setting is always on
This commit is contained in:
parent
bd26435eb8
commit
e637b423f2
|
@ -27,6 +27,7 @@ import io.homeassistant.companion.android.common.bluetooth.BluetoothUtils
|
|||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.integration.UpdateLocation
|
||||
import io.homeassistant.companion.android.common.data.integration.ZoneAttributes
|
||||
import io.homeassistant.companion.android.common.data.integration.containsWithAccuracy
|
||||
import io.homeassistant.companion.android.common.sensors.LocationSensorManagerBase
|
||||
import io.homeassistant.companion.android.common.sensors.SensorManager
|
||||
import io.homeassistant.companion.android.common.sensors.SensorReceiverBase
|
||||
|
@ -41,12 +42,14 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.concurrent.TimeUnit
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LocationSensorManager : LocationSensorManagerBase() {
|
||||
|
||||
companion object {
|
||||
private const val SETTING_SEND_LOCATION_AS = "location_send_as"
|
||||
private const val SETTING_ACCURACY = "location_minimum_accuracy"
|
||||
private const val SETTING_ACCURATE_UPDATE_TIME = "location_minimum_time_updates"
|
||||
private const val SETTING_INCLUDE_SENSOR_UPDATE = "location_include_sensor_update"
|
||||
|
@ -57,6 +60,8 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
private const val SETTING_HIGH_ACCURACY_BT_ZONE_COMBINED = "location_ham_zone_bt_combined"
|
||||
private const val SETTING_HIGH_ACCURACY_MODE_TRIGGER_RANGE_ZONE = "location_ham_trigger_range"
|
||||
|
||||
private const val SEND_LOCATION_AS_EXACT = "exact"
|
||||
private const val SEND_LOCATION_AS_ZONE_ONLY = "zone_only"
|
||||
private const val DEFAULT_MINIMUM_ACCURACY = 200
|
||||
private const val DEFAULT_UPDATE_INTERVAL_HA_SECONDS = 5
|
||||
private const val DEFAULT_TRIGGER_RANGE_METERS = 300
|
||||
|
@ -65,6 +70,8 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
private const val DEFAULT_LOCATION_FAST_INTERVAL: Long = 30000
|
||||
private const val DEFAULT_LOCATION_MAX_WAIT_TIME: Long = 200000
|
||||
|
||||
private const val ZONE_NAME_NOT_HOME = "not_home"
|
||||
|
||||
const val ACTION_REQUEST_LOCATION_UPDATES =
|
||||
"io.homeassistant.companion.android.background.REQUEST_UPDATES"
|
||||
const val ACTION_REQUEST_ACCURATE_LOCATION_UPDATE =
|
||||
|
@ -135,6 +142,9 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
private var lastLocationReceived = 0L
|
||||
private var lastUpdateLocation = ""
|
||||
|
||||
private var zones: Array<Entity<ZoneAttributes>> = emptyArray()
|
||||
private var zonesLastReceived = 0L
|
||||
|
||||
private var geofenceRegistered = false
|
||||
|
||||
private var lastHighAccuracyMode = false
|
||||
|
@ -327,6 +337,8 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
lastHighAccuracyTriggerRange = highAccuracyTriggerRange
|
||||
lastHighAccuracyMode = highAccuracyModeEnabled
|
||||
lastHighAccuracyUpdateInterval = updateIntervalHighAccuracySeconds
|
||||
|
||||
getSendLocationAsSetting() // Sets up the setting, value isn't used right now
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,6 +552,21 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
).toBoolean()
|
||||
}
|
||||
|
||||
private suspend fun getSendLocationAsSetting(): String {
|
||||
return if (integrationUseCase.isHomeAssistantVersionAtLeast(2022, 2, 0)) {
|
||||
getSetting(
|
||||
context = latestContext,
|
||||
sensor = backgroundLocation,
|
||||
settingName = SETTING_SEND_LOCATION_AS,
|
||||
settingType = SensorSettingType.LIST,
|
||||
entries = listOf(
|
||||
SEND_LOCATION_AS_EXACT, SEND_LOCATION_AS_ZONE_ONLY
|
||||
),
|
||||
default = SEND_LOCATION_AS_EXACT
|
||||
)
|
||||
} else SEND_LOCATION_AS_EXACT
|
||||
}
|
||||
|
||||
private fun removeAllLocationUpdateRequests() {
|
||||
Log.d(TAG, "Removing all location requests.")
|
||||
removeBackgroundUpdateRequests()
|
||||
|
@ -735,14 +762,39 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
if (location.accuracy.toInt() >= 0) {
|
||||
accuracy = location.accuracy.toInt()
|
||||
}
|
||||
val updateLocation = UpdateLocation(
|
||||
arrayOf(location.latitude, location.longitude),
|
||||
accuracy,
|
||||
location.speed.toInt(),
|
||||
location.altitude.toInt(),
|
||||
location.bearing.toInt(),
|
||||
if (Build.VERSION.SDK_INT >= 26) location.verticalAccuracyMeters.toInt() else 0
|
||||
)
|
||||
val updateLocation: UpdateLocation
|
||||
val updateLocationAs: String
|
||||
val updateLocationString: String
|
||||
runBlocking {
|
||||
updateLocationAs = getSendLocationAsSetting()
|
||||
if (updateLocationAs == SEND_LOCATION_AS_ZONE_ONLY) {
|
||||
val zones = getZones()
|
||||
val locationZone = zones
|
||||
.filter { !it.attributes.passive && it.containsWithAccuracy(location) }
|
||||
.minByOrNull { it.attributes.radius }
|
||||
updateLocation = UpdateLocation(
|
||||
gps = null,
|
||||
gpsAccuracy = null,
|
||||
locationName = locationZone?.entityId?.split(".")?.get(1) ?: ZONE_NAME_NOT_HOME,
|
||||
speed = null,
|
||||
altitude = null,
|
||||
course = null,
|
||||
verticalAccuracy = null
|
||||
)
|
||||
updateLocationString = updateLocation.locationName!!
|
||||
} else {
|
||||
updateLocation = UpdateLocation(
|
||||
gps = arrayOf(location.latitude, location.longitude),
|
||||
gpsAccuracy = accuracy,
|
||||
locationName = null,
|
||||
speed = location.speed.toInt(),
|
||||
altitude = location.altitude.toInt(),
|
||||
course = location.bearing.toInt(),
|
||||
verticalAccuracy = if (Build.VERSION.SDK_INT >= 26) location.verticalAccuracyMeters.toInt() else 0
|
||||
)
|
||||
updateLocationString = updateLocation.gps.contentToString()
|
||||
}
|
||||
}
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
|
@ -765,7 +817,7 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
TAG,
|
||||
"Received location that is ${now - location.time} milliseconds old, ${location.time} compared to $now with source ${location.provider}"
|
||||
)
|
||||
if (lastUpdateLocation == updateLocation.gps.contentToString()) {
|
||||
if (lastUpdateLocation == updateLocationString) {
|
||||
if (now < lastLocationSend + 900000) {
|
||||
Log.d(TAG, "Duplicate location received, not sending to HA")
|
||||
return
|
||||
|
@ -784,7 +836,7 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
return
|
||||
}
|
||||
lastLocationSend = now
|
||||
lastUpdateLocation = updateLocation.gps.contentToString()
|
||||
lastUpdateLocation = updateLocationString
|
||||
|
||||
val geocodeIncludeLocation = getSetting(
|
||||
latestContext,
|
||||
|
@ -797,7 +849,7 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
ioScope.launch {
|
||||
try {
|
||||
integrationUseCase.updateLocation(updateLocation)
|
||||
Log.d(TAG, "Location update sent successfully")
|
||||
Log.d(TAG, "Location update sent successfully as $updateLocationAs")
|
||||
|
||||
// Update Geocoded Location Sensor
|
||||
if (geocodeIncludeLocation) {
|
||||
|
@ -833,17 +885,28 @@ class LocationSensorManager : LocationSensorManagerBase() {
|
|||
return locationRequest
|
||||
}
|
||||
|
||||
private suspend fun getZones(forceRefresh: Boolean = false): Array<Entity<ZoneAttributes>> {
|
||||
if (
|
||||
forceRefresh || zones.isEmpty() ||
|
||||
zonesLastReceived < (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(4))
|
||||
) {
|
||||
try {
|
||||
zones = integrationUseCase.getZones()
|
||||
zonesLastReceived = System.currentTimeMillis()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error receiving zones from Home Assistant", e)
|
||||
if (forceRefresh) zones = emptyArray()
|
||||
}
|
||||
}
|
||||
return zones
|
||||
}
|
||||
|
||||
private suspend fun createGeofencingRequest(): GeofencingRequest {
|
||||
val geofencingRequestBuilder = GeofencingRequest.Builder()
|
||||
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
|
||||
|
||||
// TODO cache the zones on device so we don't need to reach out each time
|
||||
var configuredZones: Array<Entity<ZoneAttributes>> = emptyArray()
|
||||
try {
|
||||
configuredZones = integrationUseCase.getZones()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error receiving zones from Home Assistant", e)
|
||||
}
|
||||
val configuredZones = getZones(forceRefresh = true)
|
||||
|
||||
val highAccuracyTriggerRange = getHighAccuracyModeTriggerRange()
|
||||
val highAccuracyZones = getHighAccuracyModeZones(false)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package io.homeassistant.companion.android.common.data.integration
|
||||
|
||||
data class UpdateLocation(
|
||||
val gps: Array<Double>,
|
||||
val gpsAccuracy: Int,
|
||||
val speed: Int,
|
||||
val altitude: Int,
|
||||
val course: Int,
|
||||
val verticalAccuracy: Int
|
||||
val gps: Array<Double>?,
|
||||
val gpsAccuracy: Int?,
|
||||
val locationName: String?,
|
||||
val speed: Int?,
|
||||
val altitude: Int?,
|
||||
val course: Int?,
|
||||
val verticalAccuracy: Int?
|
||||
)
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
package io.homeassistant.companion.android.common.data.integration
|
||||
|
||||
import android.location.Location
|
||||
|
||||
data class ZoneAttributes(
|
||||
val hidden: Boolean,
|
||||
val passive: Boolean,
|
||||
val latitude: Double,
|
||||
val longitude: Double,
|
||||
val radius: Float,
|
||||
val friendlyName: String,
|
||||
val icon: String?
|
||||
)
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
fun Entity<ZoneAttributes>.containsWithAccuracy(location: Location): Boolean {
|
||||
val zoneCenter = Location("").apply {
|
||||
latitude = attributes.latitude
|
||||
longitude = attributes.longitude
|
||||
}
|
||||
return (location.distanceTo(zoneCenter) - attributes.radius - location.accuracy.coerceAtLeast(0f)) <= 0
|
||||
}
|
||||
|
|
|
@ -766,6 +766,7 @@ class IntegrationRepositoryImpl @Inject constructor(
|
|||
UpdateLocationRequest(
|
||||
updateLocation.gps,
|
||||
updateLocation.gpsAccuracy,
|
||||
updateLocation.locationName,
|
||||
updateLocation.speed,
|
||||
updateLocation.altitude,
|
||||
updateLocation.course,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package io.homeassistant.companion.android.common.data.integration.impl.entities
|
||||
|
||||
data class UpdateLocationRequest(
|
||||
val gps: Array<Double>,
|
||||
val gpsAccuracy: Int,
|
||||
val speed: Int,
|
||||
val altitude: Int,
|
||||
val course: Int,
|
||||
val verticalAccuracy: Int
|
||||
val gps: Array<Double>?,
|
||||
val gpsAccuracy: Int?,
|
||||
val locationName: String?,
|
||||
val speed: Int?,
|
||||
val altitude: Int?,
|
||||
val course: Int?,
|
||||
val verticalAccuracy: Int?
|
||||
)
|
||||
|
|
|
@ -499,7 +499,7 @@
|
|||
<string name="sensor_description_last_update">The intent action for the last update that was sent, periodic updates will show as \"SensorWorker\".\n\nEnabling the \"Add New Intent\" toggle will create 1 setting to allow you to register for a intent action. The toggle will switch back to off once a new setting is created so you will need to turn it back on to save more intent actions. You can also clear out the setting value to remove the setting in the next update.\n\nIf you are not receiving all intents then you will need to add categories that the intent expects. To do this you will need to add each category after the intent separated by a \",\" repeating until there are no more categories. For example an intent with 2 categories will be: \"intent,category1,category2\" as the setting value.\n\nYou must restart the application after making changes to these settings to take effect.</string>
|
||||
<string name="sensor_description_light_sensor">The current level of illuminance</string>
|
||||
<string name="sensor_description_location_accurate">Allow Home Assistant to send a notification to request an accurate location along with the application periodically requesting an accurate location. The Minimum Accuracy setting will allow you to decide how accurate the device location (in meters) has to be in order to send to Home Assistant. The Minimum time between updates (in milliseconds) keeps the device from sending the accurate location too often. The Include in sensor update setting will make a location request with each sensor update.</string>
|
||||
<string name="sensor_description_location_background">Update your location behind the scenes, periodically. The Minimum Accuracy setting will allow you to decide how accurate the device location (in meters) has to be in order to send to Home Assistant.</string>
|
||||
<string name="sensor_description_location_background">Update your location behind the scenes, periodically.\n\nThe Minimum Accuracy setting will allow you to decide how accurate the device location (in meters) has to be in order to send to Home Assistant.\nThe Location Sent setting will allow you to change how detailed the location information is that is sent to your server.</string>
|
||||
<string name="sensor_description_location_zone">Import existing Home Assistant zones as geofences for zone based tracking. The Minimum Accuracy setting will allow you to decide how accurate the device location (in meters) has to be in order to send to Home Assistant.</string>
|
||||
<string name="sensor_description_lullaby">If a lullaby is playing</string>
|
||||
<string name="sensor_description_media_session">The state will either be the playback state of the primary media session or it will be unavailable when you have no active sessions. Attributes will include data from all active sessions including total count.</string>
|
||||
|
@ -599,6 +599,9 @@
|
|||
<string name="sensor_setting_lastreboot_deadband_title">Deadband</string>
|
||||
<string name="sensor_setting_lastupdate_add_new_intent_title">Add New Intent</string>
|
||||
<string name="sensor_setting_lastupdate_intent_title" translatable="false">Intent %1$1s</string>
|
||||
<string name="sensor_setting_location_send_as_title">Location Sent</string>
|
||||
<string name="sensor_setting_location_send_as_exact_label">Exact</string>
|
||||
<string name="sensor_setting_location_send_as_zone_only_label">Zone name only</string>
|
||||
<string name="sensor_setting_location_ham_enabled_title">High accuracy mode (May drain battery fast)</string>
|
||||
<string name="sensor_setting_location_ham_zone_bt_combined_title">High accuracy mode enabled only when in zone and BT device connected</string>
|
||||
<string name="sensor_setting_location_ham_only_bt_dev_title">High accuracy mode only when connected to BT devices</string>
|
||||
|
|
Loading…
Reference in a new issue