From 992fa2d9a9bc519215c0b352688691ba012ca04a Mon Sep 17 00:00:00 2001
From: Leonard Kugis <leonard@kug.is>
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<FakeLocationCoordinate>) {
@@ -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<List<FakeLocationCoordinate>>() {}.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<List<FakeLocationCoordinate>>() {}.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<Action.SetSpecificLocationAction>()
     private val mockLocationParametersInputFlow = MutableSharedFlow<Action.UpdateMockLocationParameters>()
-    private val setRouteLoopEnabledInputFlow = MutableSharedFlow<Action.SetRouteLoopEnabledAction>()
-    private val setRouteInputFlow = MutableSharedFlow<Action.SetRoute>()
+    private val routeLoopEnabledInputFlow = MutableSharedFlow<Action.SetRouteLoopEnabledAction>()
+    private val routeInputFlow = MutableSharedFlow<Action.SetRoute>()
 
     @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<FakeLocationCoordinate>, routeTime: Float): Pair<FakeLocationCoordinate,Pair<Float,Float>>? {
-        if(route.size < 2)
+    private fun calculateRouteSegment(): Pair<FakeLocationCoordinate,Pair<Float,Float>>? {
+        var route_buf: List<FakeLocationCoordinate> = 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<Float,Float>((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<Float,Float>(direction.first / distance_target, direction.second / distance_target)
-                    var location_meters = Pair<Float,Float>(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<Float,Float>(prev.latitude + (location_meters.first / 111139.0f), prev.longitude + (location_meters.second / 111139.0f))
-                    if(distance_current < distance_target)
-                        return Pair<FakeLocationCoordinate,Pair<Float,Float>>(prev, location)
-                    timeCurrent += distance_target / prev.speed
-                    prev = coord
+        var segment: Pair<FakeLocationCoordinate,FakeLocationCoordinate>? = null
+        var factor: Float? = null
+        var iter = route_buf.iterator()
+        while(iter.hasNext()) {
+            segment = Pair<FakeLocationCoordinate,FakeLocationCoordinate>(
+                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<FakeLocationCoordinate,FakeLocationCoordinate>(
+                    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<FakeLocationCoordinate,Pair<Float,Float>>(
+                segment.first,
+                Pair<Float,Float>(
+                    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<FakeLocationCoordinate> = route ?: return
-                var coord = calculateRouteSegment(route_buf, routeTime)
+                var coord = calculateRouteSegment()
                 if(coord == null) {
                     // done with route
                     return
-- 
cgit v1.2.1