kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
move location service to repository
rodzic
d50e9e1644
commit
6bda993851
|
@ -120,7 +120,7 @@ interface IMeshService {
|
||||||
void setRegion(int regionCode);
|
void setRegion(int regionCode);
|
||||||
|
|
||||||
/// Start providing location (from phone GPS) to mesh
|
/// Start providing location (from phone GPS) to mesh
|
||||||
void setupProvideLocation();
|
void startProvideLocation();
|
||||||
|
|
||||||
/// Stop providing location (from phone GPS) to mesh
|
/// Stop providing location (from phone GPS) to mesh
|
||||||
void stopProvideLocation();
|
void stopProvideLocation();
|
||||||
|
|
|
@ -683,7 +683,7 @@ class MainActivity : BaseActivity(), Logging,
|
||||||
}
|
}
|
||||||
// if provideLocation enabled: Start providing location (from phone GPS) to mesh
|
// if provideLocation enabled: Start providing location (from phone GPS) to mesh
|
||||||
if (model.provideLocation.value == true)
|
if (model.provideLocation.value == true)
|
||||||
service.setupProvideLocation()
|
service.startProvideLocation()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For other connection states, just slam them in
|
// For other connection states, just slam them in
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.geeksville.mesh.repository.location
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class LocationRepository @Inject constructor(
|
||||||
|
private val sharedLocationManager: SharedLocationManager
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Status of whether the app is actively subscribed to location changes.
|
||||||
|
*/
|
||||||
|
val receivingLocationUpdates: StateFlow<Boolean> = sharedLocationManager.receivingLocationUpdates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable flow for location updates
|
||||||
|
*/
|
||||||
|
fun getLocations() = sharedLocationManager.locationFlow()
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.geeksville.mesh.repository.location
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object LocationRepositoryModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSharedLocationManager(
|
||||||
|
@ApplicationContext context: Context
|
||||||
|
): SharedLocationManager =
|
||||||
|
SharedLocationManager(context, GlobalScope)
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.geeksville.mesh.repository.location
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.location.Location
|
||||||
|
import android.os.Looper
|
||||||
|
import com.geeksville.android.GeeksvilleApplication
|
||||||
|
import com.geeksville.android.Logging
|
||||||
|
import com.geeksville.mesh.android.hasBackgroundPermission
|
||||||
|
import com.google.android.gms.location.LocationCallback
|
||||||
|
import com.google.android.gms.location.LocationRequest
|
||||||
|
import com.google.android.gms.location.LocationResult
|
||||||
|
import com.google.android.gms.location.LocationServices
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.shareIn
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps LocationCallback() in callbackFlow
|
||||||
|
*
|
||||||
|
* Derived in part from https://github.com/android/location-samples/blob/main/LocationUpdatesBackgroundKotlin/app/src/main/java/com/google/android/gms/location/sample/locationupdatesbackgroundkotlin/data/MyLocationManager.kt
|
||||||
|
* and https://github.com/googlecodelabs/kotlin-coroutines/blob/master/ktx-library-codelab/step-06/myktxlibrary/src/main/java/com/example/android/myktxlibrary/LocationUtils.kt
|
||||||
|
*/
|
||||||
|
class SharedLocationManager constructor(
|
||||||
|
private val context: Context,
|
||||||
|
externalScope: CoroutineScope
|
||||||
|
) : Logging {
|
||||||
|
|
||||||
|
private val _receivingLocationUpdates: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
val receivingLocationUpdates: StateFlow<Boolean> get() = _receivingLocationUpdates
|
||||||
|
|
||||||
|
// TODO use positionBroadcastSecs / test locationRequest settings
|
||||||
|
private val desiredInterval = 1 * 60 * 1000L
|
||||||
|
// if unset, use positionBroadcastSecs default
|
||||||
|
// positionBroadcastSecs.takeIf { it != 0L }?.times(1000L) ?: (15 * 60 * 1000L)
|
||||||
|
|
||||||
|
// Set up the Fused Location Provider and LocationRequest
|
||||||
|
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
|
||||||
|
private val locationRequest = LocationRequest.create().apply {
|
||||||
|
interval = desiredInterval
|
||||||
|
fastestInterval = 30 * 1000L
|
||||||
|
// smallestDisplacement = 50F // 50 meters
|
||||||
|
priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private val _locationUpdates = callbackFlow {
|
||||||
|
val callback = object : LocationCallback() {
|
||||||
|
override fun onLocationResult(result: LocationResult) {
|
||||||
|
// info("New location: ${result.lastLocation}")
|
||||||
|
trySend(result.lastLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!context.hasBackgroundPermission()) close()
|
||||||
|
|
||||||
|
info("Starting location requests with interval=${desiredInterval}ms")
|
||||||
|
_receivingLocationUpdates.value = true
|
||||||
|
GeeksvilleApplication.analytics.track("location_start") // Figure out how many users needed to use the phone GPS
|
||||||
|
|
||||||
|
fusedLocationClient.requestLocationUpdates(
|
||||||
|
locationRequest,
|
||||||
|
callback,
|
||||||
|
Looper.getMainLooper()
|
||||||
|
).addOnFailureListener { ex ->
|
||||||
|
errormsg("Failed to listen to GPS error: ${ex.message}")
|
||||||
|
close(ex) // in case of exception, close the Flow
|
||||||
|
}
|
||||||
|
|
||||||
|
awaitClose {
|
||||||
|
info("Stopping location requests")
|
||||||
|
_receivingLocationUpdates.value = false
|
||||||
|
GeeksvilleApplication.analytics.track("location_stop")
|
||||||
|
fusedLocationClient.removeLocationUpdates(callback) // clean up when Flow collection ends
|
||||||
|
}
|
||||||
|
}.shareIn(
|
||||||
|
externalScope,
|
||||||
|
replay = 0,
|
||||||
|
started = SharingStarted.WhileSubscribed()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun locationFlow(): Flow<Location> {
|
||||||
|
return _locationUpdates
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,10 @@
|
||||||
package com.geeksville.mesh.service
|
package com.geeksville.mesh.service
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Looper
|
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.UiThread
|
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.geeksville.analytics.DataPair
|
import com.geeksville.analytics.DataPair
|
||||||
import com.geeksville.android.GeeksvilleApplication
|
import com.geeksville.android.GeeksvilleApplication
|
||||||
|
@ -22,22 +18,19 @@ import com.geeksville.mesh.android.hasBackgroundPermission
|
||||||
import com.geeksville.mesh.database.PacketRepository
|
import com.geeksville.mesh.database.PacketRepository
|
||||||
import com.geeksville.mesh.database.entity.Packet
|
import com.geeksville.mesh.database.entity.Packet
|
||||||
import com.geeksville.mesh.model.DeviceVersion
|
import com.geeksville.mesh.model.DeviceVersion
|
||||||
|
import com.geeksville.mesh.repository.location.LocationRepository
|
||||||
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
import com.geeksville.mesh.repository.radio.BluetoothInterface
|
||||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||||
import com.geeksville.mesh.repository.radio.RadioServiceConnectionState
|
import com.geeksville.mesh.repository.radio.RadioServiceConnectionState
|
||||||
import com.geeksville.mesh.repository.usb.UsbRepository
|
import com.geeksville.mesh.repository.usb.UsbRepository
|
||||||
import com.geeksville.util.*
|
import com.geeksville.util.*
|
||||||
import com.google.android.gms.common.api.ApiException
|
|
||||||
import com.google.android.gms.common.api.ResolvableApiException
|
|
||||||
import com.google.android.gms.location.FusedLocationProviderClient
|
|
||||||
import com.google.android.gms.location.LocationRequest
|
|
||||||
import com.google.android.gms.location.LocationServices
|
|
||||||
import com.google.android.gms.location.LocationSettingsRequest
|
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
import com.google.protobuf.InvalidProtocolBufferException
|
import com.google.protobuf.InvalidProtocolBufferException
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -65,6 +58,9 @@ class MeshService : Service(), Logging {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var usbRepository: Lazy<UsbRepository>
|
lateinit var usbRepository: Lazy<UsbRepository>
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var locationRepository: LocationRepository
|
||||||
|
|
||||||
companion object : Logging {
|
companion object : Logging {
|
||||||
|
|
||||||
/// Intents broadcast by MeshService
|
/// Intents broadcast by MeshService
|
||||||
|
@ -133,17 +129,11 @@ class MeshService : Service(), Logging {
|
||||||
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||||
private var connectionState = ConnectionState.DISCONNECTED
|
private var connectionState = ConnectionState.DISCONNECTED
|
||||||
|
|
||||||
private var fusedLocationClient: FusedLocationProviderClient? = null
|
private var locationFlow: Job? = null
|
||||||
|
|
||||||
// If we've ever read a valid region code from our device it will be here
|
// If we've ever read a valid region code from our device it will be here
|
||||||
var curRegionValue = RadioConfigProtos.RegionCode.Unset_VALUE
|
var curRegionValue = RadioConfigProtos.RegionCode.Unset_VALUE
|
||||||
|
|
||||||
private val locationCallback = MeshServiceLocationCallback(
|
|
||||||
::sendPositionScoped,
|
|
||||||
onSendPositionFailed = { onConnectionChanged(ConnectionState.DEVICE_SLEEP) },
|
|
||||||
getNodeNum = { myNodeNum }
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getSenderName(packet: DataPacket?): String {
|
private fun getSenderName(packet: DataPacket?): String {
|
||||||
val name = nodeDBbyID[packet?.from]?.user?.longName
|
val name = nodeDBbyID[packet?.from]?.user?.longName
|
||||||
return name ?: "Unknown username"
|
return name ?: "Unknown username"
|
||||||
|
@ -159,109 +149,32 @@ class MeshService : Service(), Logging {
|
||||||
ConnectionState.DEVICE_SLEEP -> getString(R.string.device_sleeping)
|
ConnectionState.DEVICE_SLEEP -> getString(R.string.device_sleeping)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun warnUserAboutLocation() {
|
|
||||||
Toast.makeText(
|
|
||||||
this,
|
|
||||||
getString(R.string.location_disabled),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var locationIntervalMsec = 0L
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a periodic callback that perhaps send our position to other nodes.
|
|
||||||
* We first check to see if our local device has already sent a position and if so, we punt until the next check.
|
|
||||||
* This allows us to only 'fill in' with GPS positions when the local device happens to have no good GPS sats.
|
|
||||||
*/
|
|
||||||
private fun sendPositionScoped(
|
|
||||||
lat: Double = 0.0,
|
|
||||||
lon: Double = 0.0,
|
|
||||||
alt: Int = 0,
|
|
||||||
destNum: Int = DataPacket.NODENUM_BROADCAST,
|
|
||||||
wantResponse: Boolean = false
|
|
||||||
) {
|
|
||||||
// This operation can take a while, so instead of staying in the callback (location services) context
|
|
||||||
// do most of the work in my service thread
|
|
||||||
serviceScope.handledLaunch {
|
|
||||||
// if android called us too soon, just ignore
|
|
||||||
sendPosition(lat, lon, alt, destNum, wantResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* start our location requests (if they weren't already running)
|
* start our location requests (if they weren't already running)
|
||||||
*
|
|
||||||
* per https://developer.android.com/training/location/change-location-settings
|
|
||||||
* & https://developer.android.com/training/location/request-updates
|
|
||||||
*/
|
*/
|
||||||
@SuppressLint("MissingPermission")
|
private fun startLocationRequests() {
|
||||||
@UiThread
|
// If we're already observing updates, don't register again
|
||||||
private fun startLocationRequests(requestInterval: Long) {
|
if (locationFlow?.isActive == true) return
|
||||||
// FIXME - currently we don't support location reading without google play
|
|
||||||
if (fusedLocationClient == null && hasBackgroundPermission() && isGooglePlayAvailable(this)) {
|
|
||||||
GeeksvilleApplication.analytics.track("location_start") // Figure out how many users needed to use the phone GPS
|
|
||||||
|
|
||||||
locationIntervalMsec = requestInterval
|
if (hasBackgroundPermission() && isGooglePlayAvailable(this)) {
|
||||||
val request = LocationRequest.create().apply {
|
locationFlow = locationRepository.getLocations()
|
||||||
interval = requestInterval
|
.onEach { location ->
|
||||||
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
|
sendPosition(
|
||||||
}
|
location.latitude,
|
||||||
val builder = LocationSettingsRequest.Builder().addLocationRequest(request)
|
location.longitude,
|
||||||
val locationClient = LocationServices.getSettingsClient(this)
|
location.altitude.toInt(),
|
||||||
val locationSettingsResponse = locationClient.checkLocationSettings(builder.build())
|
myNodeNum, // we just send to the local node
|
||||||
|
false // and we never want ACKs
|
||||||
locationSettingsResponse.addOnSuccessListener {
|
)
|
||||||
debug("We are now successfully listening to the GPS")
|
|
||||||
}
|
|
||||||
|
|
||||||
locationSettingsResponse.addOnFailureListener { exception ->
|
|
||||||
errormsg("Failed to listen to GPS")
|
|
||||||
|
|
||||||
when (exception) {
|
|
||||||
is ResolvableApiException ->
|
|
||||||
exceptionReporter {
|
|
||||||
// Location settings are not satisfied, but this can be fixed
|
|
||||||
// by showing the user a dialog.
|
|
||||||
|
|
||||||
// Show the dialog by calling startResolutionForResult(),
|
|
||||||
// and check the result in onActivityResult().
|
|
||||||
// exception.startResolutionForResult(this@MainActivity, REQUEST_CHECK_SETTINGS)
|
|
||||||
|
|
||||||
// For now just punt and show a dialog
|
|
||||||
warnUserAboutLocation()
|
|
||||||
}
|
|
||||||
is ApiException ->
|
|
||||||
when (exception.statusCode) {
|
|
||||||
17 ->
|
|
||||||
// error: cancelled by user
|
|
||||||
errormsg("User cancelled location access", exception)
|
|
||||||
8502 ->
|
|
||||||
// error: settings change unavailable
|
|
||||||
errormsg(
|
|
||||||
"Settings-change-unavailable, user disabled location access (globally?)",
|
|
||||||
exception
|
|
||||||
)
|
|
||||||
else ->
|
|
||||||
Exceptions.report(exception)
|
|
||||||
}
|
|
||||||
else ->
|
|
||||||
Exceptions.report(exception)
|
|
||||||
}
|
}
|
||||||
}
|
.launchIn(CoroutineScope(Dispatchers.Default))
|
||||||
|
|
||||||
val client = LocationServices.getFusedLocationProviderClient(this)
|
|
||||||
client.requestLocationUpdates(request, locationCallback, Looper.getMainLooper())
|
|
||||||
fusedLocationClient = client
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopLocationRequests() {
|
private fun stopLocationRequests() {
|
||||||
if (fusedLocationClient != null) {
|
if (locationFlow?.isActive == true) {
|
||||||
debug("Stopping location requests")
|
debug("Stopping location requests")
|
||||||
GeeksvilleApplication.analytics.track("location_stop")
|
locationFlow?.cancel()
|
||||||
fusedLocationClient?.removeLocationUpdates(locationCallback)
|
|
||||||
fusedLocationClient = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1022,39 +935,6 @@ class MeshService : Service(), Logging {
|
||||||
maybeUpdateServiceStatusNotification()
|
maybeUpdateServiceStatusNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupLocationRequests() {
|
|
||||||
stopLocationRequests()
|
|
||||||
val mi = myNodeInfo
|
|
||||||
val prefs = radioConfig?.preferences
|
|
||||||
if (mi != null && prefs != null) {
|
|
||||||
val broadcastSecs = prefs.positionBroadcastSecs
|
|
||||||
|
|
||||||
var desiredInterval = if (broadcastSecs == 0) // unset by device, use default
|
|
||||||
15 * 60 * 1000L
|
|
||||||
else
|
|
||||||
broadcastSecs * 1000L
|
|
||||||
|
|
||||||
if (prefs.locationShareDisabled) {
|
|
||||||
info("GPS location sharing is disabled")
|
|
||||||
desiredInterval = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (prefs.fixedPosition) {
|
|
||||||
// info("Node has fixed position, therefore not overriding position")
|
|
||||||
// desiredInterval = 0
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (desiredInterval != 0L) {
|
|
||||||
info("desired GPS assistance interval $desiredInterval")
|
|
||||||
startLocationRequests(desiredInterval)
|
|
||||||
} else {
|
|
||||||
info("No GPS assistance desired, but sending UTC time to mesh")
|
|
||||||
warnUserAboutLocation()
|
|
||||||
sendPosition()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send in analytics about mesh connection
|
* Send in analytics about mesh connection
|
||||||
*/
|
*/
|
||||||
|
@ -1126,6 +1006,9 @@ class MeshService : Service(), Logging {
|
||||||
// Just in case the user uncleanly reboots the phone, save now (we normally save in onDestroy)
|
// Just in case the user uncleanly reboots the phone, save now (we normally save in onDestroy)
|
||||||
saveSettings()
|
saveSettings()
|
||||||
|
|
||||||
|
// lost radio connection, therefore no need to keep listening to GPS
|
||||||
|
stopLocationRequests()
|
||||||
|
|
||||||
GeeksvilleApplication.analytics.track(
|
GeeksvilleApplication.analytics.track(
|
||||||
"mesh_disconnect",
|
"mesh_disconnect",
|
||||||
DataPair("num_nodes", numNodes),
|
DataPair("num_nodes", numNodes),
|
||||||
|
@ -1518,7 +1401,6 @@ class MeshService : Service(), Logging {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a position (typically from our built in GPS) into the mesh.
|
* Send a position (typically from our built in GPS) into the mesh.
|
||||||
* Must be called from serviceScope. Use sendPositionScoped() for direct calls.
|
|
||||||
*/
|
*/
|
||||||
private fun sendPosition(
|
private fun sendPosition(
|
||||||
lat: Double = 0.0,
|
lat: Double = 0.0,
|
||||||
|
@ -1843,14 +1725,13 @@ class MeshService : Service(), Logging {
|
||||||
r.toString()
|
r.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setupProvideLocation() = toRemoteExceptions {
|
override fun startProvideLocation() = toRemoteExceptions {
|
||||||
setupLocationRequests()
|
startLocationRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopProvideLocation() = toRemoteExceptions {
|
override fun stopProvideLocation() = toRemoteExceptions {
|
||||||
stopLocationRequests()
|
stopLocationRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
package com.geeksville.mesh.service
|
|
||||||
|
|
||||||
import android.location.Location
|
|
||||||
import android.os.RemoteException
|
|
||||||
import com.geeksville.mesh.DataPacket
|
|
||||||
import com.google.android.gms.location.LocationCallback
|
|
||||||
import com.google.android.gms.location.LocationResult
|
|
||||||
|
|
||||||
val Location.isAccurateForMesh: Boolean get() = !this.hasAccuracy() || this.accuracy < 200
|
|
||||||
|
|
||||||
private fun List<Location>.filterAccurateForMesh() = filter { it.isAccurateForMesh }
|
|
||||||
|
|
||||||
private fun LocationResult.lastLocationOrBestEffort(): Location? {
|
|
||||||
return lastLocation ?: locations.filterAccurateForMesh().lastOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
typealias SendPosition = (Double, Double, Int, Int, Boolean) -> Unit // Lat, Lon, alt, destNum, wantResponse
|
|
||||||
typealias OnSendFailure = () -> Unit
|
|
||||||
typealias GetNodeNum = () -> Int
|
|
||||||
|
|
||||||
class MeshServiceLocationCallback(
|
|
||||||
private val onSendPosition: SendPosition,
|
|
||||||
private val onSendPositionFailed: OnSendFailure,
|
|
||||||
private val getNodeNum: GetNodeNum
|
|
||||||
) : LocationCallback() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DEFAULT_SEND_RATE_LIMIT = 30
|
|
||||||
}
|
|
||||||
|
|
||||||
private var lastSendTimeMs: Long = 0L
|
|
||||||
|
|
||||||
override fun onLocationResult(locationResult: LocationResult) {
|
|
||||||
super.onLocationResult(locationResult)
|
|
||||||
|
|
||||||
locationResult.lastLocationOrBestEffort()?.let { location ->
|
|
||||||
MeshService.info("got phone location")
|
|
||||||
if (location.isAccurateForMesh) { // if within 200 meters, or accuracy is unknown
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Do we want to broadcast this position globally, or are we just telling the local node what its current position is
|
|
||||||
val shouldBroadcast =
|
|
||||||
false // no need to rate limit, because we are just sending to the local node
|
|
||||||
val destinationNumber =
|
|
||||||
if (shouldBroadcast) DataPacket.NODENUM_BROADCAST else getNodeNum()
|
|
||||||
|
|
||||||
// Note: we never want this message sent as a reliable message, because it is low value and we'll be sending one again later anyways
|
|
||||||
sendPosition(location, destinationNumber, wantResponse = false)
|
|
||||||
|
|
||||||
} catch (ex: RemoteException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting
|
|
||||||
MeshService.warn("Lost connection to radio, stopping location requests")
|
|
||||||
onSendPositionFailed()
|
|
||||||
} catch (ex: BLEException) { // Really a RadioNotConnected exception, but it has changed into this type via remoting
|
|
||||||
MeshService.warn("BLE exception, stopping location requests $ex")
|
|
||||||
onSendPositionFailed()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MeshService.warn("accuracy ${location.accuracy} is too poor to use")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendPosition(location: Location, destinationNumber: Int, wantResponse: Boolean) {
|
|
||||||
onSendPosition(
|
|
||||||
location.latitude,
|
|
||||||
location.longitude,
|
|
||||||
location.altitude.toInt(),
|
|
||||||
destinationNumber,
|
|
||||||
wantResponse // wantResponse?
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -826,7 +826,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
debug("User changed location tracking to $isChecked")
|
debug("User changed location tracking to $isChecked")
|
||||||
model.provideLocation.value = isChecked
|
model.provideLocation.value = isChecked
|
||||||
checkLocationEnabled(getString(R.string.location_disabled))
|
checkLocationEnabled(getString(R.string.location_disabled))
|
||||||
model.meshService?.setupProvideLocation()
|
model.meshService?.startProvideLocation()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
model.provideLocation.value = isChecked
|
model.provideLocation.value = isChecked
|
||||||
|
|
Ładowanie…
Reference in New Issue