mirror of
https://github.com/home-assistant/android
synced 2024-09-19 08:01:31 +00:00
Fix location Spam (#863)
* Reduce/eliminate the number of duplicate location updates. * Limit accurate location requests to once per minute max. * Remove some validation code. * Swap around statements. * Send location every 15 minutes even if it's the same.
This commit is contained in:
parent
44551543bd
commit
72888cc754
|
@ -20,6 +20,7 @@ import com.google.android.gms.location.LocationServices
|
||||||
import io.homeassistant.companion.android.R
|
import io.homeassistant.companion.android.R
|
||||||
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
|
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
|
||||||
import io.homeassistant.companion.android.database.AppDatabase
|
import io.homeassistant.companion.android.database.AppDatabase
|
||||||
|
import io.homeassistant.companion.android.database.sensor.Attribute
|
||||||
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
|
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
|
||||||
import io.homeassistant.companion.android.domain.integration.UpdateLocation
|
import io.homeassistant.companion.android.domain.integration.UpdateLocation
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -62,23 +63,29 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
|
|
||||||
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
lateinit var latestContext: Context
|
||||||
|
|
||||||
|
private var isBackgroundLocationSetup = false
|
||||||
|
private var isZoneLocationSetup = false
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
ensureInjected(context)
|
latestContext = context
|
||||||
|
ensureInjected()
|
||||||
|
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
Intent.ACTION_BOOT_COMPLETED,
|
Intent.ACTION_BOOT_COMPLETED,
|
||||||
ACTION_REQUEST_LOCATION_UPDATES -> setupLocationTracking(context)
|
ACTION_REQUEST_LOCATION_UPDATES -> setupLocationTracking()
|
||||||
ACTION_PROCESS_LOCATION -> handleLocationUpdate(intent)
|
ACTION_PROCESS_LOCATION -> handleLocationUpdate(intent)
|
||||||
ACTION_PROCESS_GEO -> handleGeoUpdate(context, intent)
|
ACTION_PROCESS_GEO -> handleGeoUpdate(intent)
|
||||||
ACTION_REQUEST_ACCURATE_LOCATION_UPDATE -> requestSingleAccurateLocation(context)
|
ACTION_REQUEST_ACCURATE_LOCATION_UPDATE -> requestSingleAccurateLocation()
|
||||||
else -> Log.w(TAG, "Unknown intent action: ${intent.action}!")
|
else -> Log.w(TAG, "Unknown intent action: ${intent.action}!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureInjected(context: Context) {
|
private fun ensureInjected() {
|
||||||
if (context.applicationContext is GraphComponentAccessor) {
|
if (latestContext.applicationContext is GraphComponentAccessor) {
|
||||||
DaggerSensorComponent.builder()
|
DaggerSensorComponent.builder()
|
||||||
.appComponent((context.applicationContext as GraphComponentAccessor).appComponent)
|
.appComponent((latestContext.applicationContext as GraphComponentAccessor).appComponent)
|
||||||
.build()
|
.build()
|
||||||
.inject(this)
|
.inject(this)
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,49 +93,57 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupLocationTracking(context: Context) {
|
private fun setupLocationTracking() {
|
||||||
if (!checkPermission(context)) {
|
if (!checkPermission(latestContext)) {
|
||||||
Log.w(TAG, "Not starting location reporting because of permissions.")
|
Log.w(TAG, "Not starting location reporting because of permissions.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val sensorDao = AppDatabase.getInstance(context).sensorDao()
|
val backgroundEnabled = isEnabled(latestContext, backgroundLocation.id)
|
||||||
|
val zoneEnabled = isEnabled(latestContext, zoneLocation.id)
|
||||||
|
|
||||||
ioScope.launch {
|
ioScope.launch {
|
||||||
try {
|
try {
|
||||||
removeAllLocationUpdateRequests(context)
|
if (!backgroundEnabled && !zoneEnabled) {
|
||||||
|
removeAllLocationUpdateRequests()
|
||||||
if (sensorDao.get(backgroundLocation.id)?.enabled == true)
|
isBackgroundLocationSetup = false
|
||||||
requestLocationUpdates(context)
|
isZoneLocationSetup = false
|
||||||
if (sensorDao.get(zoneLocation.id)?.enabled == true)
|
}
|
||||||
requestZoneUpdates(context)
|
if (backgroundEnabled && !isBackgroundLocationSetup) {
|
||||||
|
isBackgroundLocationSetup = true
|
||||||
|
requestLocationUpdates()
|
||||||
|
}
|
||||||
|
if (zoneEnabled && !isZoneLocationSetup) {
|
||||||
|
isZoneLocationSetup = true
|
||||||
|
requestZoneUpdates()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Issue setting up location tracking", e)
|
Log.e(TAG, "Issue setting up location tracking", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeAllLocationUpdateRequests(context: Context) {
|
private fun removeAllLocationUpdateRequests() {
|
||||||
Log.d(TAG, "Removing all location requests.")
|
Log.d(TAG, "Removing all location requests.")
|
||||||
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
|
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(latestContext)
|
||||||
val backgroundIntent = getLocationUpdateIntent(context, false)
|
val backgroundIntent = getLocationUpdateIntent(false)
|
||||||
|
|
||||||
fusedLocationProviderClient.removeLocationUpdates(backgroundIntent)
|
fusedLocationProviderClient.removeLocationUpdates(backgroundIntent)
|
||||||
|
|
||||||
val geofencingClient = LocationServices.getGeofencingClient(context)
|
val geofencingClient = LocationServices.getGeofencingClient(latestContext)
|
||||||
val zoneIntent = getLocationUpdateIntent(context, true)
|
val zoneIntent = getLocationUpdateIntent(true)
|
||||||
geofencingClient.removeGeofences(zoneIntent)
|
geofencingClient.removeGeofences(zoneIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestLocationUpdates(context: Context) {
|
private fun requestLocationUpdates() {
|
||||||
if (!checkPermission(context)) {
|
if (!checkPermission(latestContext)) {
|
||||||
Log.w(TAG, "Not registering for location updates because of permissions.")
|
Log.w(TAG, "Not registering for location updates because of permissions.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Registering for location updates.")
|
Log.d(TAG, "Registering for location updates.")
|
||||||
|
|
||||||
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
|
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(latestContext)
|
||||||
val intent = getLocationUpdateIntent(context, false)
|
val intent = getLocationUpdateIntent(false)
|
||||||
|
|
||||||
fusedLocationProviderClient.requestLocationUpdates(
|
fusedLocationProviderClient.requestLocationUpdates(
|
||||||
createLocationRequest(),
|
createLocationRequest(),
|
||||||
|
@ -136,8 +151,8 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun requestZoneUpdates(context: Context) {
|
private suspend fun requestZoneUpdates() {
|
||||||
if (!checkPermission(context)) {
|
if (!checkPermission(latestContext)) {
|
||||||
Log.w(TAG, "Not registering for zone based updates because of permissions.")
|
Log.w(TAG, "Not registering for zone based updates because of permissions.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -145,8 +160,8 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
Log.d(TAG, "Registering for zone based location updates")
|
Log.d(TAG, "Registering for zone based location updates")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val geofencingClient = LocationServices.getGeofencingClient(context)
|
val geofencingClient = LocationServices.getGeofencingClient(latestContext)
|
||||||
val intent = getLocationUpdateIntent(context, true)
|
val intent = getLocationUpdateIntent(true)
|
||||||
val geofencingRequest = createGeofencingRequest()
|
val geofencingRequest = createGeofencingRequest()
|
||||||
geofencingClient.addGeofences(
|
geofencingClient.addGeofences(
|
||||||
geofencingRequest,
|
geofencingRequest,
|
||||||
|
@ -168,7 +183,7 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGeoUpdate(context: Context, intent: Intent) {
|
private fun handleGeoUpdate(intent: Intent) {
|
||||||
Log.d(TAG, "Received geofence update.")
|
Log.d(TAG, "Received geofence update.")
|
||||||
val geofencingEvent = GeofencingEvent.fromIntent(intent)
|
val geofencingEvent = GeofencingEvent.fromIntent(intent)
|
||||||
if (geofencingEvent.hasError()) {
|
if (geofencingEvent.hasError()) {
|
||||||
|
@ -181,7 +196,7 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
TAG,
|
TAG,
|
||||||
"Geofence location accuracy didn't meet requirements, requesting new location."
|
"Geofence location accuracy didn't meet requirements, requesting new location."
|
||||||
)
|
)
|
||||||
requestSingleAccurateLocation(context)
|
requestSingleAccurateLocation()
|
||||||
} else {
|
} else {
|
||||||
sendLocationUpdate(geofencingEvent.triggeringLocation)
|
sendLocationUpdate(geofencingEvent.triggeringLocation)
|
||||||
}
|
}
|
||||||
|
@ -208,19 +223,43 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
if (Build.VERSION.SDK_INT >= 26) location.verticalAccuracyMeters.toInt() else 0
|
if (Build.VERSION.SDK_INT >= 26) location.verticalAccuracyMeters.toInt() else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val sensorDao = AppDatabase.getInstance(latestContext).sensorDao()
|
||||||
|
val fullSensor = sensorDao.getFull(backgroundLocation.id)
|
||||||
|
val locationEntity = fullSensor?.sensor
|
||||||
|
val lastLocationSend = fullSensor?.attributes?.firstOrNull { it.name == "lastLocationSent" }?.value?.toLongOrNull() ?: 0L
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
if (locationEntity?.state == updateLocation.gps.contentToString()) {
|
||||||
|
if (now >= lastLocationSend + 900000) {
|
||||||
|
Log.d(TAG, "Sending location since it's been more than 15 minutes")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Same location as last update, not sending to HA")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ioScope.launch {
|
ioScope.launch {
|
||||||
try {
|
try {
|
||||||
integrationUseCase.updateLocation(updateLocation)
|
integrationUseCase.updateLocation(updateLocation)
|
||||||
|
onSensorUpdated(
|
||||||
|
latestContext,
|
||||||
|
backgroundLocation,
|
||||||
|
updateLocation.gps.contentToString(),
|
||||||
|
"",
|
||||||
|
mapOf(
|
||||||
|
"lastLocationSent" to now.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Could not update location.", e)
|
Log.e(TAG, "Could not update location.", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLocationUpdateIntent(context: Context, isGeofence: Boolean): PendingIntent {
|
private fun getLocationUpdateIntent(isGeofence: Boolean): PendingIntent {
|
||||||
val intent = Intent(context, LocationSensorManager::class.java)
|
val intent = Intent(latestContext, LocationSensorManager::class.java)
|
||||||
intent.action = if (isGeofence) ACTION_PROCESS_GEO else ACTION_PROCESS_LOCATION
|
intent.action = if (isGeofence) ACTION_PROCESS_GEO else ACTION_PROCESS_LOCATION
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getBroadcast(latestContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLocationRequest(): LocationRequest {
|
private fun createLocationRequest(): LocationRequest {
|
||||||
|
@ -237,6 +276,7 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
|
|
||||||
private suspend fun createGeofencingRequest(): GeofencingRequest {
|
private suspend fun createGeofencingRequest(): GeofencingRequest {
|
||||||
val geofencingRequestBuilder = GeofencingRequest.Builder()
|
val geofencingRequestBuilder = GeofencingRequest.Builder()
|
||||||
|
// TODO cache the zones on device so we don't need to reach out each time
|
||||||
integrationUseCase.getZones().forEach {
|
integrationUseCase.getZones().forEach {
|
||||||
geofencingRequestBuilder.addGeofence(
|
geofencingRequestBuilder.addGeofence(
|
||||||
Geofence.Builder()
|
Geofence.Builder()
|
||||||
|
@ -254,21 +294,32 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
return geofencingRequestBuilder.build()
|
return geofencingRequestBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requestSingleAccurateLocation(context: Context) {
|
private fun requestSingleAccurateLocation() {
|
||||||
if (!checkPermission(context)) {
|
if (!checkPermission(latestContext)) {
|
||||||
Log.w(TAG, "Not getting single accurate location because of permissions.")
|
Log.w(TAG, "Not getting single accurate location because of permissions.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val sensorDao = AppDatabase.getInstance(latestContext).sensorDao()
|
||||||
|
val fullSensor = sensorDao.getFull(backgroundLocation.id)
|
||||||
|
val latestAccurateLocation = fullSensor?.attributes?.firstOrNull { it.name == "lastAccurateLocationRequest" }?.value?.toLongOrNull() ?: 0L
|
||||||
|
// Only update accurate location at most once a minute
|
||||||
|
if (now < latestAccurateLocation + 60000) {
|
||||||
|
Log.d(TAG, "Not requesting accurate location, last accurate location was too recent")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sensorDao.add(Attribute(backgroundLocation.id, "lastAccurateLocationRequest", now.toString()))
|
||||||
|
|
||||||
val maxRetries = 5
|
val maxRetries = 5
|
||||||
val request = createLocationRequest()
|
val request = createLocationRequest()
|
||||||
request.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
|
request.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
|
||||||
request.numUpdates = maxRetries
|
request.numUpdates = maxRetries
|
||||||
LocationServices.getFusedLocationProviderClient(context)
|
LocationServices.getFusedLocationProviderClient(latestContext)
|
||||||
.requestLocationUpdates(
|
.requestLocationUpdates(
|
||||||
request,
|
request,
|
||||||
object : LocationCallback() {
|
object : LocationCallback() {
|
||||||
val wakeLock: PowerManager.WakeLock? =
|
val wakeLock: PowerManager.WakeLock? =
|
||||||
getSystemService(context, PowerManager::class.java)
|
getSystemService(latestContext, PowerManager::class.java)
|
||||||
?.newWakeLock(
|
?.newWakeLock(
|
||||||
PowerManager.PARTIAL_WAKE_LOCK,
|
PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
"HomeAssistant::AccurateLocation"
|
"HomeAssistant::AccurateLocation"
|
||||||
|
@ -333,9 +384,10 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager {
|
||||||
override fun requestSensorUpdate(
|
override fun requestSensorUpdate(
|
||||||
context: Context
|
context: Context
|
||||||
) {
|
) {
|
||||||
ensureInjected(context)
|
latestContext = context
|
||||||
|
ensureInjected()
|
||||||
if (isEnabled(context, zoneLocation.id) || isEnabled(context, backgroundLocation.id))
|
if (isEnabled(context, zoneLocation.id) || isEnabled(context, backgroundLocation.id))
|
||||||
setupLocationTracking(context)
|
setupLocationTracking()
|
||||||
if (isEnabled(context, backgroundLocation.id)) {
|
if (isEnabled(context, backgroundLocation.id)) {
|
||||||
context.sendBroadcast(
|
context.sendBroadcast(
|
||||||
Intent(context, this.javaClass).apply {
|
Intent(context, this.javaClass).apply {
|
||||||
|
|
Loading…
Reference in a new issue