diff --git a/project/app/src/main/java/org/owntracks/android/model/messages/MessageLocation.kt b/project/app/src/main/java/org/owntracks/android/model/messages/MessageLocation.kt index e40475b38d..b3053bc897 100644 --- a/project/app/src/main/java/org/owntracks/android/model/messages/MessageLocation.kt +++ b/project/app/src/main/java/org/owntracks/android/model/messages/MessageLocation.kt @@ -85,6 +85,8 @@ open class MessageLocation( @SerialName("tid") var trackerId: String? = null + @SerialName("address") var address: String? = null + override fun isValidMessage(): Boolean { return timestamp > 0 } diff --git a/project/app/src/main/java/org/owntracks/android/services/BackgroundService.kt b/project/app/src/main/java/org/owntracks/android/services/BackgroundService.kt index fd17365b91..598475ecff 100644 --- a/project/app/src/main/java/org/owntracks/android/services/BackgroundService.kt +++ b/project/app/src/main/java/org/owntracks/android/services/BackgroundService.kt @@ -131,9 +131,7 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList private val ongoingNotification by lazy { OngoingNotification(this, preferences.monitoring) } private val notificationManagerCompat by lazy { NotificationManagerCompat.from(this) } - private val activityManager by lazy { - this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager - } + private val activityManager by lazy { this.getSystemService(ACTIVITY_SERVICE) as ActivityManager } private val powerStateLogger by lazy { PowerStateLogger(this.applicationContext) } private val powerBroadcastReceiver = object : BroadcastReceiver() { @@ -195,7 +193,8 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList } addAction(Intent.ACTION_SCREEN_ON) addAction(Intent.ACTION_SCREEN_OFF) - }) + }, + ) powerStateLogger.logPowerState("serviceOnCreate") lifecycleScope.launch { @@ -235,7 +234,8 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList ongoingNotification.setEndpointState( it, if (preferences.mode == ConnectionMode.MQTT) preferences.host - else preferences.url.toHttpUrlOrNull()?.host ?: "") + else preferences.url.toHttpUrlOrNull()?.host ?: "", + ) } } endpointStateRepo.setServiceStartedNow() @@ -276,7 +276,8 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList if (requirementsChecker.hasLocationPermissions()) { locationProviderClient.singleHighAccuracyLocation( callbackForReportType[MessageLocation.ReportType.USER]!!.value, - runThingsOnOtherThreads.getBackgroundLooper()) + runThingsOnOtherThreads.getBackgroundLooper(), + ) } } return @@ -343,18 +344,21 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList startForeground( NOTIFICATION_ID_ONGOING, ongoingNotification.getNotification(), - FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE) + FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE, + ) } catch (e: ForegroundServiceStartNotAllowedException) { Timber.e( e, - "Foreground service start not allowed. backgroundRestricted=${activityManager.isBackgroundRestricted}") + "Foreground service start not allowed. backgroundRestricted=${activityManager.isBackgroundRestricted}", + ) return } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground( NOTIFICATION_ID_ONGOING, ongoingNotification.getNotification(), - FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE) + FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE, + ) } else { startForeground(NOTIFICATION_ID_ONGOING, ongoingNotification.getNotification()) } @@ -390,7 +394,9 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList val notificationTitle = getString(R.string.backgroundLocationRestrictionNotificationTitle) val notification = NotificationCompat.Builder( - applicationContext, GeocoderProvider.ERROR_NOTIFICATION_CHANNEL_ID) + applicationContext, + GeocoderProvider.ERROR_NOTIFICATION_CHANNEL_ID, + ) .setContentTitle(notificationTitle) .setContentText(notificationText) .setAutoCancel(true) @@ -398,12 +404,19 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList .setStyle(NotificationCompat.BigTextStyle().bigText(notificationText)) .setContentIntent( PendingIntent.getActivity( - applicationContext, 0, activityLaunchIntent, UPDATE_CURRENT_INTENT_FLAGS)) + applicationContext, + 0, + activityLaunchIntent, + UPDATE_CURRENT_INTENT_FLAGS, + )) .setPriority(NotificationCompat.PRIORITY_LOW) .setSilent(true) .build() notificationManagerCompat.notify( - BACKGROUND_LOCATION_RESTRICTION_NOTIFICATION_TAG, 0, notification) + BACKGROUND_LOCATION_RESTRICTION_NOTIFICATION_TAG, + 0, + notification, + ) } fun sendEventNotification(message: MessageTransition) { @@ -435,14 +448,16 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList StyleSpan(Typeface.BOLD), 0, whenStr.length + 1, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE, + ) }) Timber.v("groupedNotifications: ${activeNotifications.size}") val summary = resources.getQuantityString( R.plurals.notificationEventsTitle, activeNotifications.size, - activeNotifications.size) + activeNotifications.size, + ) val inbox = NotificationCompat.InboxStyle().setSummaryText(summary) activeNotifications.forEach { inbox.addLine(it) } Pair(summary, inbox) @@ -468,14 +483,16 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList this, System.currentTimeMillis().toInt() / 1000, Intent(this, MapActivity::class.java), - UPDATE_CURRENT_INTENT_FLAGS)) + UPDATE_CURRENT_INTENT_FLAGS, + )) .setDeleteIntent( PendingIntent.getService( this, 1, Intent(this, BackgroundService::class.java) .setAction(INTENT_ACTION_CLEAR_NOTIFICATIONS), - UPDATE_CURRENT_INTENT_FLAGS)) + UPDATE_CURRENT_INTENT_FLAGS, + )) .build() .run { notificationManagerCompat @@ -508,7 +525,11 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList waypointsRepo.get(requestId.toLong())?.run { Timber.d("onWaypointTransition triggered by geofencing event") locationProcessor.onWaypointTransition( - this, event.triggeringLocation, transition, MessageTransition.TRIGGER_CIRCULAR) + this, + event.triggeringLocation, + transition, + MessageTransition.TRIGGER_CIRCULAR, + ) } ?: run { Timber.e("waypoint id $requestId not found for geofence event") } } catch (e: NumberFormatException) { Timber.e("$requestId from Geofencing event is not a valid request id") @@ -521,7 +542,9 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList if (requirementsChecker.hasLocationPermissions()) { Timber.d("On demand location request") locationProviderClient.singleHighAccuracyLocation( - callbackForReportType[reportType]!!.value, runThingsOnOtherThreads.getBackgroundLooper()) + callbackForReportType[reportType]!!.value, + runThingsOnOtherThreads.getBackgroundLooper(), + ) } else { Timber.e("missing location permission") } @@ -561,13 +584,21 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList } val request = LocationRequest( - fastestInterval, smallestDisplacement, null, null, priority, interval, null) + fastestInterval, + smallestDisplacement, + null, + null, + priority, + interval, + null, + ) Timber.d("location update request params: $request") locationProviderClient.flushLocations() locationProviderClient.requestLocationUpdates( request, callbackForReportType[MessageLocation.ReportType.DEFAULT]!!.value, - runThingsOnOtherThreads.getBackgroundLooper()) + runThingsOnOtherThreads.getBackgroundLooper(), + ) return Result.success(Unit) } else { return Result.failure(Exception("Missing location permission")) @@ -591,7 +622,8 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList it.geofenceLongitude, it.geofenceRadius.toFloat(), Geofence.NEVER_EXPIRE, - null) + null, + ) } .toList() geofencingClient.removeGeofences(this@BackgroundService) @@ -609,6 +641,11 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList if (latLng == lastLocation?.toLatLng()) { Timber.v("New reverse geocode for $latLng: $reverseGeocodedText") + if (lastLocation != null) { + reverseGeocodedText.ifBlank { lastLocation!!.toLatLng().toDisplayString() } + locationProcessor.lastAddress = reverseGeocodedText + } + if (lastLocation != null && preferences.notificationLocation) { reverseGeocodedText.ifBlank { lastLocation!!.toLatLng().toDisplayString() } } else { @@ -629,7 +666,8 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList Preferences::moveModeLocatorInterval.name, Preferences::pegLocatorFastestIntervalToInterval.name, Preferences::notificationHigherPriority.name, - Preferences::locatorPriority.name) + Preferences::locatorPriority.name, + ) if (propertiesWeCareAbout .stream() .filter { o: String -> properties.contains(o) } @@ -671,7 +709,8 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList } } }, - 0) + 0, + ) } private val localServiceBinder: IBinder = LocalBinder() @@ -707,7 +746,7 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList class LocationCallbackWithReportType( private val reportType: MessageLocation.ReportType, private val locationProcessor: LocationProcessor, - private val lifecycleCoroutineScope: LifecycleCoroutineScope + private val lifecycleCoroutineScope: LifecycleCoroutineScope, ) : LocationCallback { override fun onLocationAvailability(locationAvailability: LocationAvailability) { @@ -734,8 +773,7 @@ class BackgroundService : LifecycleService(), Preferences.OnPreferenceChangeList } class PowerStateLogger(private val applicationContext: Context) { - private val powerManager = - applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager + private val powerManager = applicationContext.getSystemService(POWER_SERVICE) as PowerManager fun logPowerState(action: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { diff --git a/project/app/src/main/java/org/owntracks/android/services/LocationProcessor.kt b/project/app/src/main/java/org/owntracks/android/services/LocationProcessor.kt index c0b4584fbf..8a722be2a3 100644 --- a/project/app/src/main/java/org/owntracks/android/services/LocationProcessor.kt +++ b/project/app/src/main/java/org/owntracks/android/services/LocationProcessor.kt @@ -49,8 +49,10 @@ constructor( @Named("publishResponseMessageIdlingResource") private val publishResponseMessageIdlingResource: SimpleIdlingResource, @Named("mockLocationIdlingResource") - private val mockLocationIdlingResource: SimpleIdlingResource + private val mockLocationIdlingResource: SimpleIdlingResource, ) { + var lastAddress: String? = null + private fun locationIsWithAccuracyThreshold(l: Location): Boolean = preferences.ignoreInaccurateLocations .run { preferences.ignoreInaccurateLocations == 0 || l.accuracy < this } @@ -68,7 +70,7 @@ constructor( private suspend fun publishLocationMessage( trigger: MessageLocation.ReportType, - location: Location + location: Location, ): Result { Timber.v("Maybe publishing $location with trigger $trigger") if (!locationIsWithAccuracyThreshold(location)) @@ -108,7 +110,8 @@ constructor( } else { Geofence.GEOFENCE_TRANSITION_EXIT }, - MessageTransition.TRIGGER_LOCATION) + MessageTransition.TRIGGER_LOCATION, + ) } } if (preferences.monitoring === MonitoringMode.Quiet && @@ -132,6 +135,7 @@ constructor( conn = deviceMetricsProvider.connectionType.value monitoringMode = preferences.monitoring source = location.provider + address = lastAddress } } else { fromLocation(location, Build.VERSION.SDK_INT) @@ -153,7 +157,8 @@ constructor( listOf( MessageLocation.ReportType.RESPONSE, MessageLocation.ReportType.USER, - MessageLocation.ReportType.CIRCULAR) + MessageLocation.ReportType.CIRCULAR, + ) private fun calculateInRegions(loadedWaypoints: List): List = loadedWaypoints @@ -191,7 +196,7 @@ constructor( waypointModel: WaypointModel, location: Location, transition: Int, - trigger: String + trigger: String, ) { if (!locationIsWithAccuracyThreshold(location)) { Timber.d( @@ -229,7 +234,7 @@ constructor( waypointModel: WaypointModel, triggeringLocation: Location, transition: Int, - trigger: String + trigger: String, ) { messageProcessor.queueMessageForSending( MessageTransition().apply { diff --git a/project/app/src/main/java/org/owntracks/android/services/MessageProcessor.kt b/project/app/src/main/java/org/owntracks/android/services/MessageProcessor.kt index fc16586ea8..e83d5cfec5 100644 --- a/project/app/src/main/java/org/owntracks/android/services/MessageProcessor.kt +++ b/project/app/src/main/java/org/owntracks/android/services/MessageProcessor.kt @@ -126,7 +126,8 @@ constructor( applicationContext.bindService( Intent(applicationContext, BackgroundService::class.java), serviceConnection, - Context.BIND_AUTO_CREATE) + Context.BIND_AUTO_CREATE, + ) endpointStateRepo.setState(EndpointState.INITIAL) queueInitJob.join() reconnect() @@ -196,7 +197,8 @@ constructor( scope, ioDispatcher, applicationContext, - mqttConnectionIdlingResource) + mqttConnectionIdlingResource, + ) ConnectionMode.HTTP -> HttpMessageProcessorEndpoint( this, @@ -206,7 +208,8 @@ constructor( endpointStateRepo, caKeyStore, scope, - ioDispatcher) + ioDispatcher, + ) } } @@ -275,7 +278,9 @@ constructor( resendDelayWait(SEND_FAILURE_NOT_READY_WAIT) lastMessageStatus = LastMessageStatus.RetryableFailure( - message.numberOfRetries, SEND_FAILURE_BACKOFF_INITIAL_WAIT) + message.numberOfRetries, + SEND_FAILURE_BACKOFF_INITIAL_WAIT, + ) } else { it.sendMessage(message).exceptionOrNull()?.run { when (this) { @@ -285,7 +290,9 @@ constructor( resendDelayWait(SEND_FAILURE_NOT_READY_WAIT) lastMessageStatus = LastMessageStatus.RetryableFailure( - message.numberOfRetries, SEND_FAILURE_BACKOFF_INITIAL_WAIT) + message.numberOfRetries, + SEND_FAILURE_BACKOFF_INITIAL_WAIT, + ) } is MessageProcessorEndpoint.OutgoingMessageSendingException, @@ -311,7 +318,8 @@ constructor( retriesToGo - 1, (retryWait * 2).coerceAtMost(SEND_FAILURE_BACKOFF_MAX_WAIT).also { Timber.v("Increasing failure retry wait to $it") - }) + }, + ) } } @@ -514,7 +522,8 @@ constructor( } else if (message.modeId !== ConnectionMode.HTTP && preferences.receivedCommandsTopic != message.topic && preferences.subTopic == - DEFAULT_SUB_TOPIC // If we're not using the default subtopic, we receive commands from + DEFAULT_SUB_TOPIC // If we're not using the default subtopic, we receive commands + // from // anywhere ) { Timber.e("cmd message received on wrong topic")