From 992fa2d9a9bc519215c0b352688691ba012ca04a Mon Sep 17 00:00:00 2001 From: Leonard Kugis Date: Wed, 3 Jan 2024 22:51:08 +0100 Subject: Fixed route interpolation --- .../domain/usecases/FakeLocationStateUseCase.kt | 13 ++++- .../features/location/FakeLocationFragment.kt | 33 +++++++++-- .../features/location/FakeLocationViewModel.kt | 28 ++++++---- .../domain/entities/FakeLocationCoordinate.kt | 1 + .../fakelocation/services/FakeLocationService.kt | 65 ++++++++++++++-------- 5 files changed, 97 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt index 27a2104..76a1e69 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber import kotlin.random.Random +import android.util.Log class FakeLocationStateUseCase( private val fakeLocationModule: FakeLocationModule, @@ -215,12 +216,14 @@ class FakeLocationStateUseCase( null, null, false, - route, - false, + route ?: localStateRepository.route, + localStateRepository.routeLoopEnabled, false ) localStateRepository.setLocationMode(LocationMode.ROUTE) + if(route != null) + localStateRepository.route = route } fun setRoute(route: List) { @@ -234,13 +237,16 @@ class FakeLocationStateUseCase( null, false, route, - false, + localStateRepository.routeLoopEnabled, false ) + + localStateRepository.route = route } fun routeStart() { if (hasAcquireMockLocationPermission()) { + fakeLocationModule.startFakeLocation() fakeLocationModule.routeStart(localStateRepository.route, localStateRepository.routeLoopEnabled) } else { useRealLocation() @@ -249,6 +255,7 @@ class FakeLocationStateUseCase( fun routeStop() { if (hasAcquireMockLocationPermission()) { + fakeLocationModule.stopFakeLocation() fakeLocationModule.routeStop() } else { useRealLocation() diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt index b70ae36..a155216 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt @@ -67,7 +67,13 @@ import timber.log.Timber import com.google.gson.Gson import com.google.gson.reflect.TypeToken import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate -import java.io.File +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import android.util.Log +import kotlin.math.sqrt +import kotlin.math.pow class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) { @@ -341,11 +347,26 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { uri -> - if(uri.path != null) { - var routeFile = File(uri.path ?: ".") - //val filePath = selectedFile?.uri?.path ?: "Path not found" - //binding.locationRoutePath.text = "Path: $filePath" - route = Gson().fromJson(routeFile.readText(Charsets.UTF_8), object : TypeToken>() {}.type) + var activity = getActivity() + if(uri.path != null && activity != null) { + var inputStream: InputStream? = null + var reader: BufferedReader? = null + var route_str: String? = null + try { + inputStream = activity.contentResolver.openInputStream(uri) + reader = BufferedReader(InputStreamReader(inputStream)) + route_str = reader.readLines().joinToString("") + } catch(e: IOException) { + Log.e("FakeLocationFragment", "Error reading JSON file", e) + } finally { + try { + reader?.close() + inputStream?.close() + } catch (e: IOException) { + Log.e("FakeLocationFragment", "Error closing streams", e) + } + } + route = Gson().fromJson(route_str, object : TypeToken>() {}.type) var route_buf = route route_buf?.let { viewModel.submitAction(Action.SetRoute(route_buf)) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt index c88c638..049c707 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.time.Duration.Companion.milliseconds import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate +import android.util.Log class FakeLocationViewModel( private val fakeLocationStateUseCase: FakeLocationStateUseCase @@ -58,8 +59,8 @@ class FakeLocationViewModel( private val specificLocationInputFlow = MutableSharedFlow() private val mockLocationParametersInputFlow = MutableSharedFlow() - private val setRouteLoopEnabledInputFlow = MutableSharedFlow() - private val setRouteInputFlow = MutableSharedFlow() + private val routeLoopEnabledInputFlow = MutableSharedFlow() + private val routeInputFlow = MutableSharedFlow() @OptIn(FlowPreview::class) suspend fun doOnStartedState() = withContext(Dispatchers.Main) { @@ -73,7 +74,9 @@ class FakeLocationViewModel( speed = ss.speed, jitter = ss.jitter, specificLatitude = ss.specificLatitude, - specificLongitude = ss.specificLongitude + specificLongitude = ss.specificLongitude, + route = ss.route, + loopRoute = ss.loopRoute ) } }, @@ -85,14 +88,14 @@ class FakeLocationViewModel( .debounce(SET_MOCK_LOCATION_PARAMETERS_DELAY).map { action -> fakeLocationStateUseCase.setFakeLocationParameters(action.altitude, action.speed, action.jitter) }, - setRouteLoopEnabledInputFlow - .debounce(SET_ROUTE_LOOP_ENABLED_DELAY).map { action -> - fakeLocationStateUseCase.setRouteLoopEnabled(action.isEnabled) - }, - setRouteInputFlow + routeInputFlow .debounce(SET_ROUTE_DELAY).map { action -> fakeLocationStateUseCase.setRoute(action.route) }, + routeLoopEnabledInputFlow + .debounce(SET_ROUTE_LOOP_ENABLED_DELAY).map { action -> + fakeLocationStateUseCase.setRouteLoopEnabled(action.isEnabled) + }, ).collect {} } } @@ -106,7 +109,7 @@ class FakeLocationViewModel( is Action.UseRealLocationAction -> fakeLocationStateUseCase.useRealLocation() is Action.UseRoute -> fakeLocationStateUseCase.useRoute() is Action.UpdateMockLocationParameters -> updateMockLocationParameters(action) - is Action.SetRoute -> setRouteInputFlow.emit(action) + is Action.SetRoute -> setRoute(action) is Action.SetRouteLoopEnabledAction -> setRouteLoopEnabled(action) is Action.RouteStartAction -> fakeLocationStateUseCase.routeStart() is Action.RouteStopAction -> fakeLocationStateUseCase.routeStop() @@ -128,8 +131,13 @@ class FakeLocationViewModel( mockLocationParametersInputFlow.emit(action) } + private suspend fun setRoute(action: Action.SetRoute) { + fakeLocationStateUseCase.setRoute(action.route) + routeInputFlow.emit(action) + } + private suspend fun setRouteLoopEnabled(action: Action.SetRouteLoopEnabledAction) { - setRouteLoopEnabledInputFlow.emit(action) + routeLoopEnabledInputFlow.emit(action) } sealed class SingleEvent { diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FakeLocationCoordinate.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FakeLocationCoordinate.kt index a7992a9..b497b13 100644 --- a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FakeLocationCoordinate.kt +++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FakeLocationCoordinate.kt @@ -24,5 +24,6 @@ val speed: Float, val jitter: Float, val bearing: Float, + val timestamp: Float ) \ No newline at end of file diff --git a/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt index c388afc..d13e263 100644 --- a/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt +++ b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt @@ -154,31 +154,49 @@ class FakeLocationService : Service() { }.start() } - private fun calculateRouteSegment(route: List, routeTime: Float): Pair>? { - if(route.size < 2) + private fun calculateRouteSegment(): Pair>? { + var route_buf: List = route ?: return null + if(route_buf.size < 2) return null - var prev = route.first() - var timeCurrent: Float = 0f - do { - var route_current = if(routeReversed) route.reversed() else route - for(coord in route_current) { - var direction = Pair((coord.latitude - prev.latitude) * 111139.0f, (coord.longitude - prev.longitude) * 111139.0f) - if(!(coord.latitude == prev.latitude && coord.longitude == prev.longitude)) { - var distance_target = sqrt((direction.first * direction.first) + (direction.second * direction.second)) - var direction_unit = Pair(direction.first / distance_target, direction.second / distance_target) - var location_meters = Pair(direction_unit.first * (routeTime - timeCurrent) * prev.speed, - direction_unit.second * (routeTime - timeCurrent) * prev.speed) - var distance_current = sqrt((location_meters.first * location_meters.first) + (location_meters.second - location_meters.second)) - var location = Pair(prev.latitude + (location_meters.first / 111139.0f), prev.longitude + (location_meters.second / 111139.0f)) - if(distance_current < distance_target) - return Pair>(prev, location) - timeCurrent += distance_target / prev.speed - prev = coord + var segment: Pair? = null + var factor: Float? = null + var iter = route_buf.iterator() + while(iter.hasNext()) { + segment = Pair( + if(segment == null) iter.next() else segment.second, + iter.next() + ) + if(routeTime >= segment.first.timestamp && routeTime < segment.second.timestamp) { + factor = (routeTime - segment.first.timestamp) / (segment.second.timestamp - segment.first.timestamp) + break + } + } + if(factor == null && loopRoute) { + iter = route_buf.reversed().iterator() + while(iter.hasNext()) { + segment = Pair( + if(segment == null) iter.next() else segment.second, + iter.next() + ) + if(routeTime >= ((2 * route_buf.last().timestamp) - segment.first.timestamp) && routeTime < ((2 * route_buf.last().timestamp) - segment.second.timestamp)) { + factor = (routeTime - ((2 * route_buf.last().timestamp) - segment.first.timestamp)) / (((2 * route_buf.last().timestamp) - segment.second.timestamp) - ((2 * route_buf.last().timestamp) - segment.first.timestamp)) + break } } - if(loopRoute) - routeReversed = !routeReversed - } while(loopRoute) + if(factor == null) { + routeTime -= (2 * route_buf.last().timestamp) + return calculateRouteSegment() + } + } + if(segment != null && factor != null) { + return Pair>( + segment.first, + Pair( + segment.first.latitude + ((segment.second.latitude - segment.first.latitude) * factor), + segment.first.longitude + ((segment.second.longitude - segment.first.longitude) * factor) + ) + ) + } return null } @@ -187,8 +205,7 @@ class FakeLocationService : Service() { routeTime = 0f cdtRoute = object : CountDownTimer(PERIOD_UPDATES_SERIE, PERIOD_LOCATION_UPDATE) { override fun onTick(millisUntilFinished: Long) { - var route_buf: List = route ?: return - var coord = calculateRouteSegment(route_buf, routeTime) + var coord = calculateRouteSegment() if(coord == null) { // done with route return -- cgit v1.2.1