mirror of https://github.com/tasks/tasks
Add AndroidLocationProvider
parent
a6ff285a43
commit
beeb6b0250
@ -0,0 +1,16 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.location.Location
|
||||
|
||||
class MockLocationManager : LocationManager {
|
||||
private val mockLocations = ArrayList<Location>()
|
||||
|
||||
fun addLocations(vararg locations: Location) {
|
||||
mockLocations.addAll(locations)
|
||||
}
|
||||
|
||||
fun clearLocations() = mockLocations.clear()
|
||||
|
||||
override val lastKnownLocations: List<Location>
|
||||
get() = mockLocations
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.location.Location
|
||||
import android.location.LocationManager.GPS_PROVIDER
|
||||
import android.location.LocationManager.NETWORK_PROVIDER
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.tasks.injection.InjectingTestCase
|
||||
import org.tasks.injection.ProductionModule
|
||||
import org.tasks.time.DateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
@UninstallModules(ProductionModule::class)
|
||||
@HiltAndroidTest
|
||||
class AndroidLocationProviderTest : InjectingTestCase() {
|
||||
@Inject lateinit var provider: AndroidLocationProvider
|
||||
@Inject lateinit var locationManager: MockLocationManager
|
||||
|
||||
@Test
|
||||
fun sortByAccuracy() = runBlocking {
|
||||
newLocation(NETWORK_PROVIDER, 45.0, 46.0, 50f, DateTime(2021, 2, 4, 13, 35, 45, 121))
|
||||
newLocation(GPS_PROVIDER, 45.1, 46.1, 30f, DateTime(2021, 2, 4, 13, 33, 45, 121))
|
||||
|
||||
assertEquals(MapPosition(45.1, 46.1), provider.currentLocation())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sortWithStaleLocation() = runBlocking {
|
||||
newLocation(GPS_PROVIDER, 45.1, 46.1, 30f, DateTime(2021, 2, 4, 13, 33, 44, 121))
|
||||
newLocation(NETWORK_PROVIDER, 45.0, 46.0, 50f, DateTime(2021, 2, 4, 13, 35, 45, 121))
|
||||
|
||||
assertEquals(MapPosition(45.0, 46.0), provider.currentLocation())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun useNewerUpdateWhenAccuracySame() = runBlocking {
|
||||
newLocation(GPS_PROVIDER, 45.1, 46.1, 50f, DateTime(2021, 2, 4, 13, 35, 45, 100))
|
||||
newLocation(NETWORK_PROVIDER, 45.0, 46.0, 50f, DateTime(2021, 2, 4, 13, 35, 45, 121))
|
||||
|
||||
assertEquals(MapPosition(45.0, 46.0), provider.currentLocation())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun returnCachedLocation() = runBlocking {
|
||||
newLocation(GPS_PROVIDER, 45.1, 46.1, 50f, DateTime(2021, 2, 4, 13, 35, 45, 100))
|
||||
|
||||
provider.currentLocation()
|
||||
|
||||
locationManager.clearLocations()
|
||||
|
||||
assertEquals(MapPosition(45.1, 46.1), provider.currentLocation())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nullWhenNoPosition() = runBlocking {
|
||||
assertNull(provider.currentLocation())
|
||||
}
|
||||
|
||||
private fun newLocation(
|
||||
provider: String,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
accuracy: Float,
|
||||
time: DateTime) {
|
||||
locationManager.addLocations(Location(provider).apply {
|
||||
this.latitude = latitude
|
||||
this.longitude = longitude
|
||||
this.accuracy = accuracy
|
||||
this.time = time.millis
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.location.Location
|
||||
import org.tasks.preferences.PermissionChecker
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class AndroidLocationProvider @Inject constructor(
|
||||
internal val locationManager: LocationManager,
|
||||
private val permissionChecker: PermissionChecker,
|
||||
) : LocationProvider {
|
||||
|
||||
private var cached: Location? = null
|
||||
|
||||
override suspend fun currentLocation() = if (permissionChecker.canAccessForegroundLocation()) {
|
||||
locationManager
|
||||
.lastKnownLocations
|
||||
.plus(cached)
|
||||
.filterNotNull()
|
||||
.sortedWith(COMPARATOR)
|
||||
.firstOrNull()
|
||||
?.let {
|
||||
cached = it
|
||||
MapPosition(it.latitude, it.longitude)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TWO_MINUTES = TimeUnit.MINUTES.toMillis(2)
|
||||
|
||||
internal val COMPARATOR = Comparator<Location> { l1, l2 ->
|
||||
val timeDelta = l1.time - l2.time
|
||||
val accuracyDelta = l1.accuracy - l2.accuracy
|
||||
when {
|
||||
timeDelta > TWO_MINUTES -> -1
|
||||
timeDelta < -TWO_MINUTES -> 1
|
||||
accuracyDelta < 0 -> -1
|
||||
accuracyDelta > 0 -> 1
|
||||
timeDelta > 0 -> -1
|
||||
timeDelta < 0 -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import com.mapbox.android.core.location.LocationEngineCallback
|
||||
import com.mapbox.android.core.location.LocationEngineProvider
|
||||
import com.mapbox.android.core.location.LocationEngineResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class MapboxLocationProvider(private val context: Context) : LocationProvider {
|
||||
@SuppressLint("MissingPermission")
|
||||
override suspend fun currentLocation(): MapPosition = withContext(Dispatchers.IO) {
|
||||
suspendCoroutine { cont ->
|
||||
LocationEngineProvider.getBestLocationEngine(context)
|
||||
.getLastLocation(
|
||||
object : LocationEngineCallback<LocationEngineResult> {
|
||||
override fun onSuccess(result: LocationEngineResult) {
|
||||
val location = result.lastLocation!!
|
||||
cont.resumeWith(Result.success(MapPosition(location.latitude, location.longitude)))
|
||||
}
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
cont.resumeWith(Result.failure(exception))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AndroidLocationManager @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
) : LocationManager {
|
||||
|
||||
private val locationManager =
|
||||
context.getSystemService(Context.LOCATION_SERVICE) as android.location.LocationManager
|
||||
|
||||
override val lastKnownLocations: List<Location>
|
||||
get() = locationManager.allProviders.mapNotNull {
|
||||
locationManager.getLastKnownLocationOrNull(it)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun android.location.LocationManager.getLastKnownLocationOrNull(provider: String) =
|
||||
try {
|
||||
getLastKnownLocation(provider)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.location.Location
|
||||
|
||||
interface LocationManager {
|
||||
val lastKnownLocations: List<Location>
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
package org.tasks.location
|
||||
|
||||
interface LocationProvider {
|
||||
suspend fun currentLocation(): MapPosition
|
||||
suspend fun currentLocation(): MapPosition?
|
||||
}
|
Loading…
Reference in New Issue