summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeonard Kugis <leonard@kug.is>2024-01-02 17:53:12 +0100
committerLeonard Kugis <leonard@kug.is>2024-01-02 17:53:12 +0100
commit5db0bdfdf62ae0915b587399a0ff4ce53bca813b (patch)
tree538a18ce0adbf6e600ee77a48e51d8c67649b0c6
parent298dff2a877680e928b37e3a1336dc7d7aa52dfb (diff)
Implemented route mode
-rw-r--r--app/build.gradle3
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt46
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt211
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt9
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt3
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt1
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt84
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationState.kt4
-rw-r--r--app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationViewModel.kt41
-rw-r--r--app/src/main/res/layout/fragment_fake_location.xml40
-rw-r--r--app/src/main/res/values/strings.xml11
-rw-r--r--core/src/main/java/foundation/e/advancedprivacy/domain/entities/FakeLocationCoordinate.kt28
-rw-r--r--core/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt2
-rw-r--r--core/src/main/java/foundation/e/advancedprivacy/domain/repositories/LocalStateRepository.kt10
-rw-r--r--fakelocation/build.gradle3
-rw-r--r--fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt9
-rw-r--r--fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt108
17 files changed, 518 insertions, 95 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 816af83..7c7875b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -175,7 +175,8 @@ dependencies {
libs.mpandroidcharts,
libs.eos.telemetry,
- libs.timber
+ libs.timber,
+ libs.google.gson
)
debugImplementation libs.leakcanary
diff --git a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt
index 540d502..9643899 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt
@@ -30,6 +30,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
class LocalStateRepositoryImpl(context: Context) : LocalStateRepository {
companion object {
@@ -39,7 +42,9 @@ class LocalStateRepositoryImpl(context: Context) : LocalStateRepository {
private const val KEY_FAKE_ALTITUDE = "fakeAltitude"
private const val KEY_FAKE_SPEED = "fakeSpeed"
private const val KEY_FAKE_JITTER = "fakeJitter"
- private const val KEY_FAKE_LOCATION = "fakeLocation"
+ private const val KEY_LOCATION_MODE = "locationMode"
+ private const val KEY_LOCATION_ROUTE = "locationRoute"
+ private const val KEY_LOCATION_ROUTE_LOOP = "locationRouteLoop"
private const val KEY_FAKE_LATITUDE = "fakeLatitude"
private const val KEY_FAKE_LONGITUDE = "fakeLongitude"
private const val KEY_FIRST_BOOT = "firstBoot"
@@ -61,15 +66,6 @@ class LocalStateRepositoryImpl(context: Context) : LocalStateRepository {
override val areAllTrackersBlocked: MutableStateFlow<Boolean> = MutableStateFlow(false)
- private val _fakeLocationEnabled = MutableStateFlow(sharedPref.getBoolean(KEY_FAKE_LOCATION, false))
-
- override val fakeLocationEnabled = _fakeLocationEnabled.asStateFlow()
-
- override fun setFakeLocationEnabled(enabled: Boolean) {
- set(KEY_FAKE_LOCATION, enabled)
- _fakeLocationEnabled.update { enabled }
- }
-
override var fakeAltitude: Float
get() = sharedPref.getFloat(KEY_FAKE_ALTITUDE, 3.0f)
set(value) {
@@ -108,7 +104,35 @@ class LocalStateRepositoryImpl(context: Context) : LocalStateRepository {
.apply()
}
- override val locationMode: MutableStateFlow<LocationMode> = MutableStateFlow(LocationMode.REAL_LOCATION)
+ override var route: List<FakeLocationCoordinate>
+ get() {
+ return Gson().fromJson<List<FakeLocationCoordinate>>(sharedPref.getString(KEY_LOCATION_ROUTE, "[]"), object : TypeToken<List<FakeLocationCoordinate>>() {}.type)
+ }
+ set(value) {
+ sharedPref.edit()
+ .putString(KEY_LOCATION_ROUTE, Gson().toJson(value))
+ .apply()
+ }
+
+ override var routeLoopEnabled: Boolean
+ get() = sharedPref.getBoolean(KEY_LOCATION_ROUTE_LOOP, false)
+ set(value) {
+ sharedPref.edit()
+ .putBoolean(KEY_LOCATION_ROUTE_LOOP, value)
+ .apply()
+ }
+
+ private val _locationMode = MutableStateFlow(LocationMode.valueOf(sharedPref.getString(KEY_LOCATION_MODE, LocationMode.REAL_LOCATION.toString()) ?: "REAL_LOCATION"))
+
+ override val locationMode = _locationMode.asStateFlow()
+
+ override fun setLocationMode(mode: LocationMode) {
+ sharedPref.edit()
+ .putString(KEY_LOCATION_MODE, mode.toString())
+ .apply()
+ //set(KEY_LOCATION_MODE, mode.toString())
+ _locationMode.update { mode }
+ }
private val _ipScramblingSetting = MutableStateFlow(sharedPref.getBoolean(KEY_IP_SCRAMBLING, false))
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 114b5ca..27a2104 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
@@ -34,6 +34,7 @@ import foundation.e.advancedprivacy.dummy.CityDataSource
import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule
import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule
import foundation.e.advancedprivacy.features.location.FakeLocationState
+import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -52,15 +53,22 @@ class FakeLocationStateUseCase(
coroutineScope: CoroutineScope
) {
private val _configuredLocationMode = MutableStateFlow<FakeLocationState>(
- FakeLocationState(LocationMode.REAL_LOCATION, null, null, null, null, false, null, null, false)
+ FakeLocationState(LocationMode.REAL_LOCATION, null, null, null, null, null, null, false, null, false, false)
)
val configuredLocationMode: StateFlow<FakeLocationState> = _configuredLocationMode
init {
coroutineScope.launch {
- localStateRepository.fakeLocationEnabled.collect {
- applySettings(it, it)
+ localStateRepository.locationMode.collect {
+ if(it == LocationMode.REAL_LOCATION)
+ useRealLocation()
+ if(it == LocationMode.RANDOM_LOCATION)
+ useRandomLocation()
+ if(it == LocationMode.SPECIFIC_LOCATION)
+ useFakeLocation(localStateRepository.fakeLocation)
+ if(it == LocationMode.ROUTE)
+ useRoute(localStateRepository.route)
}
}
}
@@ -73,81 +81,178 @@ class FakeLocationStateUseCase(
permissionsModule.toggleDangerousPermission(appDesc, android.Manifest.permission.ACCESS_FINE_LOCATION, true)
}
- private fun applySettings(isEnabled: Boolean, isSpecificLocation: Boolean = false) {
- _configuredLocationMode.value = computeLocationMode(isEnabled, localStateRepository.fakeAltitude, localStateRepository.fakeSpeed, localStateRepository.fakeJitter, localStateRepository.fakeLocation, isSpecificLocation)
+ // private fun applySettings(isEnabled: Boolean, isSpecificLocation: Boolean = false) {
+ // _configuredLocationMode.value = computeLocationMode(isEnabled,
+ // localStateRepository.locationMode,
+ // localStateRepository.fakeAltitude,
+ // localStateRepository.fakeSpeed,
+ // localStateRepository.fakeJitter,
+ // localStateRepository.fakeLocation,
+ // localStateRepository.routeLoopEnabled,
+ // isSpecificLocation)
- if (isEnabled && hasAcquireMockLocationPermission()) {
- fakeLocationModule.startFakeLocation()
- fakeLocationModule.setFakeLocation(localStateRepository.fakeAltitude.toDouble(),
- localStateRepository.fakeSpeed,
- localStateRepository.fakeJitter,
- localStateRepository.fakeLocation.first.toDouble(),
- localStateRepository.fakeLocation.second.toDouble())
- localStateRepository.locationMode.value = configuredLocationMode.value.mode
- } else {
- fakeLocationModule.stopFakeLocation()
- localStateRepository.locationMode.value = LocationMode.REAL_LOCATION
- }
- }
+ // if (isEnabled && hasAcquireMockLocationPermission()) {
+ // fakeLocationModule.startFakeLocation()
+ // localStateRepository.setLocationMode(configuredLocationMode.value.mode)
+ // fakeLocationModule.setFakeLocation(localStateRepository.fakeAltitude.toDouble(),
+ // localStateRepository.fakeSpeed,
+ // localStateRepository.fakeJitter,
+ // localStateRepository.fakeLocation.first.toDouble(),
+ // localStateRepository.fakeLocation.second.toDouble())
+ // } else {
+ // fakeLocationModule.stopFakeLocation()
+ // localStateRepository.setLocationMode(LocationMode.REAL_LOCATION)
+ // }
+ // }
private fun hasAcquireMockLocationPermission(): Boolean {
return (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED) ||
permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED)
}
+ fun setRouteLoopEnabled(isEnabled: Boolean) {
+ _configuredLocationMode.value = FakeLocationState(
+ LocationMode.ROUTE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ false,
+ localStateRepository.route,
+ isEnabled,
+ false
+ )
+
+ localStateRepository.routeLoopEnabled = isEnabled
+ }
+
fun setFakeLocationParameters(altitude: Float, speed: Float, jitter: Float) {
+ _configuredLocationMode.value = FakeLocationState(
+ LocationMode.SPECIFIC_LOCATION,
+ null,
+ altitude,
+ speed,
+ jitter,
+ localStateRepository.fakeLocation.first,
+ localStateRepository.fakeLocation.second,
+ false,
+ null,
+ false,
+ false
+ )
+
localStateRepository.fakeAltitude = altitude
localStateRepository.fakeSpeed = speed
localStateRepository.fakeJitter = jitter
- applySettings(localStateRepository.fakeLocationEnabled.value, localStateRepository.fakeLocationEnabled.value)
}
- fun setSpecificLocation(latitude: Float, longitude: Float) {
- setFakeLocation(latitude to longitude, true)
+ fun useRealLocation() {
+ _configuredLocationMode.value = FakeLocationState(
+ LocationMode.REAL_LOCATION,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ false,
+ null,
+ false,
+ false
+ )
+
+ fakeLocationModule.stopFakeLocation()
+ localStateRepository.setLocationMode(LocationMode.REAL_LOCATION)
}
- fun setRandomLocation() {
+ fun useRandomLocation() {
val randomIndex = Random.nextInt(citiesRepository.citiesLocationsList.size)
val location = citiesRepository.citiesLocationsList[randomIndex]
- setFakeLocation(location)
+ useFakeLocation(location)
}
- private fun setFakeLocation(location: Pair<Float, Float>, isSpecificLocation: Boolean = false) {
+ fun useFakeLocation(location: Pair<Float,Float>) {
localStateRepository.fakeLocation = location
- localStateRepository.setFakeLocationEnabled(true)
- applySettings(true, isSpecificLocation)
- }
-
- fun stopFakeLocation() {
- localStateRepository.setFakeLocationEnabled(false)
- applySettings(false, false)
- }
-
- private fun computeLocationMode(
- isFakeLocationEnabled: Boolean,
- altitude: Float,
- speed: Float,
- jitter: Float,
- fakeLocation: Pair<Float, Float>,
- isSpecificLocation: Boolean = false,
- ): FakeLocationState {
- return FakeLocationState(
- when {
- !isFakeLocationEnabled -> LocationMode.REAL_LOCATION
- (fakeLocation in citiesRepository.citiesLocationsList && !isSpecificLocation) ->
- LocationMode.RANDOM_LOCATION
- else -> LocationMode.SPECIFIC_LOCATION
- },
+
+ _configuredLocationMode.value = FakeLocationState(
+ LocationMode.SPECIFIC_LOCATION,
null,
- altitude,
- speed,
- jitter,
+ localStateRepository.fakeAltitude,
+ localStateRepository.fakeSpeed,
+ localStateRepository.fakeJitter,
+ location.first,
+ location.second,
+ false,
+ null,
+ false,
+ false
+ )
+
+ if (hasAcquireMockLocationPermission()) {
+ fakeLocationModule.startFakeLocation()
+ localStateRepository.setLocationMode(LocationMode.SPECIFIC_LOCATION)
+ fakeLocationModule.setFakeLocation(localStateRepository.fakeAltitude.toDouble(),
+ localStateRepository.fakeSpeed,
+ localStateRepository.fakeJitter,
+ localStateRepository.fakeLocation.first.toDouble(),
+ localStateRepository.fakeLocation.second.toDouble())
+ } else {
+ fakeLocationModule.stopFakeLocation()
+ localStateRepository.setLocationMode(LocationMode.REAL_LOCATION)
+ }
+ }
+
+ fun useRoute(route: List<FakeLocationCoordinate>? = null) {
+ _configuredLocationMode.value = FakeLocationState(
+ LocationMode.ROUTE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ false,
+ route,
false,
- fakeLocation.first,
- fakeLocation.second,
false
)
+
+ localStateRepository.setLocationMode(LocationMode.ROUTE)
+ }
+
+ fun setRoute(route: List<FakeLocationCoordinate>) {
+ _configuredLocationMode.value = FakeLocationState(
+ LocationMode.ROUTE,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ false,
+ route,
+ false,
+ false
+ )
+ }
+
+ fun routeStart() {
+ if (hasAcquireMockLocationPermission()) {
+ fakeLocationModule.routeStart(localStateRepository.route, localStateRepository.routeLoopEnabled)
+ } else {
+ useRealLocation()
+ }
+ }
+
+ fun routeStop() {
+ if (hasAcquireMockLocationPermission()) {
+ fakeLocationModule.routeStop()
+ } else {
+ useRealLocation()
+ }
}
val currentLocation = MutableStateFlow<Location?>(null)
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt
index 3c37da9..c58f4f7 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt
@@ -83,11 +83,12 @@ class GetQuickPrivacyStateUseCase(
}
}
+ @Suppress("UNUSED_PARAMETER")
fun toggleLocation(enabled: Boolean?) {
- val value = enabled ?: !localStateRepository.fakeLocationEnabled.value
- if (value != localStateRepository.fakeLocationEnabled.value) {
- localStateRepository.setFakeLocationEnabled(value)
- }
+ // val value = enabled ?: !localStateRepository.fakeLocationEnabled.value
+ // if (value != localStateRepository.fakeLocationEnabled.value) {
+ // localStateRepository.setFakeLocationEnabled(value)
+ // }
}
fun toggleIpScrambling(enabled: Boolean?) {
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt
index 56b398a..177eab0 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt
@@ -37,9 +37,6 @@ class ShowFeaturesWarningUseCase(
fun showWarning(): Flow<MainFeatures> {
return merge(
- localStateRepository.fakeLocationEnabled.drop(1).dropWhile { !it }
- .filter { it && !localStateRepository.hideWarningLocation }
- .map { FakeLocation },
localStateRepository.startVpnDisclaimer.filter {
(it is IpScrambling && !localStateRepository.hideWarningIpScrambling) ||
(it is TrackersControl && !localStateRepository.hideWarningTrackers)
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
index b7ff5e0..559e13f 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
@@ -247,6 +247,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) {
LocationMode.REAL_LOCATION -> R.string.dashboard_location_subtitle_off
LocationMode.SPECIFIC_LOCATION -> R.string.dashboard_location_subtitle_specific
LocationMode.RANDOM_LOCATION -> R.string.dashboard_location_subtitle_random
+ LocationMode.ROUTE -> R.string.dashboard_location_subtitle_route
}
)
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 7b456d1..b70ae36 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
@@ -35,6 +35,10 @@ import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import android.app.Activity
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.documentfile.provider.DocumentFile
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
@@ -60,6 +64,10 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
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
class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) {
@@ -206,6 +214,25 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
}
}
+ private fun validateBounds(inputLayout: TextInputLayout, minValue: Float, maxValue: Float): Boolean {
+ return try {
+ val value = inputLayout.editText?.text?.toString()?.toFloat()!!
+
+ if (value > maxValue || value < minValue) {
+ throw NumberFormatException("value $value is out of bounds")
+ }
+ inputLayout.error = null
+
+ inputLayout.setEndIconDrawable(R.drawable.ic_valid)
+ inputLayout.endIconMode = END_ICON_CUSTOM
+ true
+ } catch (e: Exception) {
+ inputLayout.endIconMode = END_ICON_NONE
+ inputLayout.error = getString(R.string.location_error_bounds)
+ false
+ }
+ }
+
private fun validateCoordinate(
inputLayout: TextInputLayout,
maxValue: Float
@@ -261,16 +288,19 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
@Suppress("UNUSED_PARAMETER")
private fun onAltitudeTextChanged(editable: Editable?) {
+ if(!validateBounds(binding.textlayoutAltitude, -100000.0f, 100000.0f)) return
updateMockLocationParameters()
}
@Suppress("UNUSED_PARAMETER")
private fun onSpeedTextChanged(editable: Editable?) {
+ if(!validateBounds(binding.textlayoutSpeed, 0.0f, 299792458.0f)) return
updateMockLocationParameters()
}
@Suppress("UNUSED_PARAMETER")
private fun onJitterTextChanged(editable: Editable?) {
+ if(!validateBounds(binding.textlayoutJitter, 0.0f, 10000000.0f)) return
updateMockLocationParameters()
}
@@ -305,6 +335,35 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
}
}
+ private var route: List<FakeLocationCoordinate>? = null
+
+ private val filePickerLauncher =
+ 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 route_buf = route
+ route_buf?.let {
+ viewModel.submitAction(Action.SetRoute(route_buf))
+ }
+ }
+ }
+ }
+ }
+
+ private fun openFilePicker() {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "*/*"
+ }
+
+ filePickerLauncher.launch(intent)
+ }
+
@SuppressLint("ClickableViewAccessibility")
private fun bindClickListeners() {
binding.radioUseRealLocation.setOnClickListener {
@@ -320,6 +379,9 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
)
}
}
+ binding.radioUseRoute.setOnClickListener {
+ viewModel.submitAction(Action.UseRoute)
+ }
binding.edittextAltitude.addTextChangedListener(afterTextChanged = ::onAltitudeTextChanged)
binding.edittextSpeed.addTextChangedListener(afterTextChanged = ::onSpeedTextChanged)
@@ -331,6 +393,11 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
binding.edittextJitter.onFocusChangeListener = latLonOnFocusChangeListener
binding.edittextLatitude.onFocusChangeListener = latLonOnFocusChangeListener
binding.edittextLongitude.onFocusChangeListener = latLonOnFocusChangeListener
+
+ binding.buttonLocationRoutePathSelect.setOnClickListener { openFilePicker() }
+ binding.checkboxRouteLoop.setOnCheckedChangeListener { _, isChecked -> viewModel.submitAction(Action.SetRouteLoopEnabledAction(isChecked)) }
+ binding.buttonLocationRouteStart.setOnClickListener { viewModel.submitAction(Action.RouteStartAction) }
+ binding.buttonLocationRouteStop.setOnClickListener { viewModel.submitAction(Action.RouteStopAction) }
}
@SuppressLint("MissingPermission")
@@ -341,12 +408,23 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
binding.radioUseRealLocation.isChecked = state.mode == LocationMode.REAL_LOCATION
+ binding.radioUseRoute.isChecked = state.mode == LocationMode.ROUTE
+
binding.mapView.isEnabled = (state.mode == LocationMode.SPECIFIC_LOCATION)
binding.textlayoutAltitude.isVisible = state.mode == LocationMode.SPECIFIC_LOCATION
binding.textlayoutSpeed.isVisible = state.mode == LocationMode.SPECIFIC_LOCATION
binding.textlayoutJitter.isVisible = state.mode == LocationMode.SPECIFIC_LOCATION
+ binding.buttonLocationRoutePathSelect.isVisible = state.mode == LocationMode.ROUTE
+ binding.locationRoutePath.isVisible = state.mode == LocationMode.ROUTE
+ binding.checkboxRouteLoop.isVisible = state.mode == LocationMode.ROUTE
+ binding.buttonLocationRouteStart.isVisible = state.mode == LocationMode.ROUTE
+ binding.buttonLocationRouteStop.isVisible = state.mode == LocationMode.ROUTE
+
+ if(binding.checkboxRouteLoop.isVisible)
+ binding.checkboxRouteLoop.isChecked = state.loopRoute
+
if(!binding.edittextAltitude.isFocused)
binding.edittextAltitude.setText(state.altitude?.toString())
@@ -379,6 +457,12 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
binding.edittextLatitude.setText(state.specificLatitude?.toString())
binding.edittextLongitude.setText(state.specificLongitude?.toString())
}
+
+ if(route == null) {
+ binding.locationRoutePath.text = "No valid route selected"
+ } else {
+ binding.locationRoutePath.text = "Route valid"
+ }
}
@SuppressLint("MissingPermission")
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationState.kt
index cc16b1b..56acdfd 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationState.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationState.kt
@@ -20,6 +20,7 @@ package foundation.e.advancedprivacy.features.location
import android.location.Location
import foundation.e.advancedprivacy.domain.entities.LocationMode
+import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
data class FakeLocationState(
val mode: LocationMode = LocationMode.REAL_LOCATION,
@@ -30,4 +31,7 @@ data class FakeLocationState(
val specificLatitude: Float? = null,
val specificLongitude: Float? = null,
val forceRefresh: Boolean = false,
+ val route: List<FakeLocationCoordinate>? = null,
+ val loopRoute: Boolean = false,
+ val routeStarted: Boolean = false,
)
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 143612f..c88c638 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
@@ -36,12 +36,16 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.milliseconds
+import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
class FakeLocationViewModel(
private val fakeLocationStateUseCase: FakeLocationStateUseCase
) : ViewModel() {
companion object {
private val SET_SPECIFIC_LOCATION_DELAY = 200.milliseconds
+ private val SET_MOCK_LOCATION_PARAMETERS_DELAY = 1000.milliseconds
+ private val SET_ROUTE_LOOP_ENABLED_DELAY = 1000.milliseconds
+ private val SET_ROUTE_DELAY = 1000.milliseconds
}
private val _state = MutableStateFlow(FakeLocationState())
@@ -54,6 +58,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>()
@OptIn(FlowPreview::class)
suspend fun doOnStartedState() = withContext(Dispatchers.Main) {
@@ -73,12 +79,20 @@ class FakeLocationViewModel(
},
specificLocationInputFlow
.debounce(SET_SPECIFIC_LOCATION_DELAY).map { action ->
- fakeLocationStateUseCase.setSpecificLocation(action.latitude, action.longitude)
+ fakeLocationStateUseCase.useFakeLocation(Pair<Float,Float>(action.latitude, action.longitude))
},
mockLocationParametersInputFlow
- .debounce(SET_SPECIFIC_LOCATION_DELAY).map { action ->
+ .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
+ .debounce(SET_ROUTE_DELAY).map { action ->
+ fakeLocationStateUseCase.setRoute(action.route)
+ },
).collect {}
}
}
@@ -88,10 +102,14 @@ class FakeLocationViewModel(
is Action.StartListeningLocation -> actionStartListeningLocation()
is Action.StopListeningLocation -> fakeLocationStateUseCase.stopListeningLocation()
is Action.SetSpecificLocationAction -> setSpecificLocation(action)
- is Action.UseRandomLocationAction -> fakeLocationStateUseCase.setRandomLocation()
- is Action.UseRealLocationAction ->
- fakeLocationStateUseCase.stopFakeLocation()
+ is Action.UseRandomLocationAction -> fakeLocationStateUseCase.useRandomLocation()
+ is Action.UseRealLocationAction -> fakeLocationStateUseCase.useRealLocation()
+ is Action.UseRoute -> fakeLocationStateUseCase.useRoute()
is Action.UpdateMockLocationParameters -> updateMockLocationParameters(action)
+ is Action.SetRoute -> setRouteInputFlow.emit(action)
+ is Action.SetRouteLoopEnabledAction -> setRouteLoopEnabled(action)
+ is Action.RouteStartAction -> fakeLocationStateUseCase.routeStart()
+ is Action.RouteStopAction -> fakeLocationStateUseCase.routeStop()
}
}
@@ -110,6 +128,10 @@ class FakeLocationViewModel(
mockLocationParametersInputFlow.emit(action)
}
+ private suspend fun setRouteLoopEnabled(action: Action.SetRouteLoopEnabledAction) {
+ setRouteLoopEnabledInputFlow.emit(action)
+ }
+
sealed class SingleEvent {
object RequestLocationPermission : SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
@@ -120,6 +142,7 @@ class FakeLocationViewModel(
object StopListeningLocation : Action()
object UseRealLocationAction : Action()
object UseRandomLocationAction : Action()
+ object UseRoute : Action()
data class UpdateMockLocationParameters(
val altitude: Float,
val speed: Float,
@@ -129,5 +152,13 @@ class FakeLocationViewModel(
val latitude: Float,
val longitude: Float
) : Action()
+ data class SetRoute(
+ val route: List<FakeLocationCoordinate>
+ ) : Action()
+ data class SetRouteLoopEnabledAction(
+ val isEnabled: Boolean
+ ) : Action()
+ object RouteStartAction : Action()
+ object RouteStopAction : Action()
}
}
diff --git a/app/src/main/res/layout/fragment_fake_location.xml b/app/src/main/res/layout/fragment_fake_location.xml
index 841ee56..d9f8a08 100644
--- a/app/src/main/res/layout/fragment_fake_location.xml
+++ b/app/src/main/res/layout/fragment_fake_location.xml
@@ -82,6 +82,14 @@
android:text="@string/location_use_specific_location"
android:textSize="14sp"
/>
+
+ <foundation.e.advancedprivacy.common.RightRadioButton
+ android:id="@+id/radio_use_route"
+ android:layout_height="52dp"
+ android:layout_width="match_parent"
+ android:text="@string/location_use_route"
+ android:textSize="14sp"
+ />
</RadioGroup>
<com.google.android.material.textfield.TextInputLayout
@@ -141,6 +149,38 @@
/>
</com.google.android.material.textfield.TextInputLayout>
+ <Button
+ android:id="@+id/button_location_route_path_select"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/location_route_path_select" />
+
+ <TextView
+ android:id="@+id/location_route_path"
+ android:layout_gravity="center_horizontal"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:text="@string/location_route_path"
+ android:lineSpacingExtra="5sp"
+ />
+
+ <CheckBox android:id="@+id/checkbox_route_loop"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/location_route_loop" />
+
+ <Button
+ android:id="@+id/button_location_route_start"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/location_route_start" />
+
+ <Button
+ android:id="@+id/button_location_route_stop"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/location_route_stop" />
+
<FrameLayout
android:layout_marginTop="16dp"
android:layout_height="220dp"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fb64b9d..2379ab9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -61,6 +61,7 @@
<string name="dashboard_location_subtitle_off" weblate_ctx="home-2">Real geolocation</string>
<string name="dashboard_location_subtitle_specific">Specific fake geolocation</string>
<string name="dashboard_location_subtitle_random">Random fake geolocation</string>
+ <string name="dashboard_location_subtitle_route">Walking fake route</string>
<string name="dashboard_internet_activity_privacy_title" weblate_ctx="home-2 ">Manage my Internet address</string>
<string name="dashboard_internet_activity_privacy_subtitle_off" weblate_ctx="home-2">Real IP address exposed</string>
<string name="dashboard_internet_activity_privacy_subtitle_on">Real IP address hidden</string>
@@ -84,14 +85,22 @@
<string name="location_title" weblate_ctx="location-1">Manage my location</string>
<string name="location_info" weblate_ctx="location-1">Your location can reveal a lot about yourself or your activities.\n\nManage my location enables you to use a fake location instead of your real position. This way, your real location isn\'t shared with applications that might be snooping too much.</string>
<string name="location_altitude">Altitude [m]</string>
- <string name="location_speed">Speed [km/h]</string>
+ <string name="location_speed">Speed [m/s]</string>
<string name="location_jitter">Jitter [m]</string>
<string name="location_use_real_location" weblate_ctx="location-1">Use my real location</string>
<string name="location_use_random_location" weblate_ctx="location-1">Use a random plausible location</string>
<string name="location_use_specific_location" weblate_ctx="location-1">Use a specific location</string>
+ <string name="location_use_route" weblate_ctx="location-1">Walk specific route</string>
<string name="location_hint_longitude" weblate_ctx="location-2">Longitude</string>
<string name="location_hint_latitude" weblate_ctx="location-2">Latitude</string>
+ <string name="location_error_bounds">Value out of bounds</string>
<string name="location_input_error">Invalid coordinates</string>
+ <string name="location_route_path_select">Choose route...</string>
+ <string name="location_route_path_select_picker">Select route JSON</string>
+ <string name="location_route_path">No route selected</string>
+ <string name="location_route_loop">Loop route</string>
+ <string name="location_route_start">Start route</string>
+ <string name="location_route_stop">Stop route</string>
<!-- Trackers -->
<string name="trackers_title" weblate_ctx="trackers-1">Manage apps\' trackers</string>
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
new file mode 100644
index 0000000..a7992a9
--- /dev/null
+++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/FakeLocationCoordinate.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 Leonard Kugis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+ package foundation.e.advancedprivacy.domain.entities
+
+ data class FakeLocationCoordinate(
+ val latitude: Float,
+ val longitude: Float,
+ val altitude: Float,
+ val speed: Float,
+ val jitter: Float,
+ val bearing: Float,
+ )
+ \ No newline at end of file
diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt
index 3642fcc..a82e7d0 100644
--- a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt
+++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt
@@ -19,5 +19,5 @@
package foundation.e.advancedprivacy.domain.entities
enum class LocationMode {
- REAL_LOCATION, RANDOM_LOCATION, SPECIFIC_LOCATION
+ REAL_LOCATION, RANDOM_LOCATION, SPECIFIC_LOCATION, ROUTE
}
diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/repositories/LocalStateRepository.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/repositories/LocalStateRepository.kt
index 641f6da..34bc096 100644
--- a/core/src/main/java/foundation/e/advancedprivacy/domain/repositories/LocalStateRepository.kt
+++ b/core/src/main/java/foundation/e/advancedprivacy/domain/repositories/LocalStateRepository.kt
@@ -21,6 +21,7 @@ import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.domain.entities.FeatureState
import foundation.e.advancedprivacy.domain.entities.LocationMode
import foundation.e.advancedprivacy.domain.entities.MainFeatures
+import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
@@ -31,16 +32,17 @@ interface LocalStateRepository {
val areAllTrackersBlocked: MutableStateFlow<Boolean>
- val fakeLocationEnabled: StateFlow<Boolean>
- fun setFakeLocationEnabled(enabled: Boolean)
-
var fakeAltitude: Float
var fakeSpeed: Float
var fakeJitter: Float
var fakeLocation: Pair<Float, Float>
- val locationMode: MutableStateFlow<LocationMode>
+ var routeLoopEnabled: Boolean
+ var route: List<FakeLocationCoordinate>
+
+ val locationMode: StateFlow<LocationMode>
+ fun setLocationMode(mode: LocationMode)
fun setIpScramblingSetting(enabled: Boolean)
val ipScramblingSetting: StateFlow<Boolean>
diff --git a/fakelocation/build.gradle b/fakelocation/build.gradle
index 02f6de5..58099d3 100644
--- a/fakelocation/build.gradle
+++ b/fakelocation/build.gradle
@@ -49,7 +49,8 @@ android {
dependencies {
implementation(
libs.bundles.koin,
- libs.bundles.kotlin.android.coroutines
+ libs.bundles.kotlin.android.coroutines,
+ libs.google.gson
)
implementation project(':core')
diff --git a/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt
index 89d5628..7424f38 100644
--- a/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt
+++ b/fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt
@@ -29,6 +29,7 @@ import android.os.Build
import android.os.SystemClock
import android.util.Log
import foundation.e.advancedprivacy.fakelocation.services.FakeLocationService
+import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
/**
* Implementation of the functionality of fake location.
@@ -86,6 +87,14 @@ class FakeLocationModule(private val context: Context) {
}
}
+ fun routeStart(route: List<FakeLocationCoordinate>, loopEnabled: Boolean) {
+ context.startService(FakeLocationService.buildRouteIntent(context, route, loopEnabled))
+ }
+
+ fun routeStop() {
+ context.stopService(FakeLocationService.buildStopIntent(context))
+ }
+
fun setFakeLocation(altitude: Double, speed: Float, jitter: Float, latitude: Double, longitude: Double) {
context.startService(FakeLocationService.buildFakeLocationIntent(context, altitude, speed, jitter, latitude, longitude))
}
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 2d85e6c..c388afc 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
@@ -25,13 +25,18 @@ import android.os.CountDownTimer
import android.os.IBinder
import android.util.Log
import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule
+import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import kotlin.math.sqrt
import kotlin.random.Random
class FakeLocationService : Service() {
enum class Actions {
- START_FAKE_LOCATION
+ START_FAKE_LOCATION,
+ START_ROUTE
}
companion object {
@@ -43,6 +48,8 @@ class FakeLocationService : Service() {
private const val PARAM_JITTER = "PARAM_JITTER"
private const val PARAM_LATITUDE = "PARAM_LATITUDE"
private const val PARAM_LONGITUDE = "PARAM_LONGITUDE"
+ private const val PARAM_ROUTE = "PARAM_ROUTE"
+ private const val PARAM_ROUTE_LOOP = "PARAM_ROUTE_LOOP"
fun buildFakeLocationIntent(context: Context, altitude: Double, speed: Float, jitter: Float, latitude: Double, longitude: Double): Intent {
return Intent(context, FakeLocationService::class.java).apply {
@@ -55,12 +62,22 @@ class FakeLocationService : Service() {
}
}
+ fun buildRouteIntent(context: Context, route: List<FakeLocationCoordinate>, loopRoute: Boolean): Intent {
+ return Intent(context, FakeLocationService::class.java).apply {
+ action = Actions.START_ROUTE.name
+ putExtra(PARAM_ROUTE, Gson().toJson(route))
+ putExtra(PARAM_ROUTE_LOOP, loopRoute)
+ }
+ }
+
fun buildStopIntent(context: Context) = Intent(context, FakeLocationService::class.java)
}
private lateinit var fakeLocationModule: FakeLocationModule
- private var countDownTimer: CountDownTimer? = null
+ private var cdtFakeLocation: CountDownTimer? = null
+ private var cdtRoute: CountDownTimer? = null
+ private var routeTime: Float = 0f
private var altitude: Double? = null
private var speed: Float? = null
@@ -68,6 +85,10 @@ class FakeLocationService : Service() {
private var fakeLocation: Pair<Double, Double>? = null
+ private var route: List<FakeLocationCoordinate>? = null
+ private var loopRoute: Boolean = false
+ private var routeReversed: Boolean = false
+
override fun onCreate() {
super.onCreate()
fakeLocationModule = FakeLocationModule(applicationContext)
@@ -84,7 +105,12 @@ class FakeLocationService : Service() {
it.getDoubleExtra(PARAM_LATITUDE, 0.0),
it.getDoubleExtra(PARAM_LONGITUDE, 0.0)
)
- initTimer()
+ initTimerFakeLocation()
+ }
+ Actions.START_ROUTE -> {
+ route = Gson().fromJson(it.getStringExtra(PARAM_ROUTE) ?: "[]", object : TypeToken<List<FakeLocationCoordinate>>() {}.type)
+ loopRoute = it.getBooleanExtra(PARAM_ROUTE_LOOP, false)
+ initTimerRoute()
}
else -> {}
}
@@ -94,13 +120,14 @@ class FakeLocationService : Service() {
}
override fun onDestroy() {
- countDownTimer?.cancel()
+ cdtFakeLocation?.cancel()
+ cdtRoute?.cancel()
super.onDestroy()
}
- private fun initTimer() {
- countDownTimer?.cancel()
- countDownTimer = object : CountDownTimer(PERIOD_UPDATES_SERIE, PERIOD_LOCATION_UPDATE) {
+ private fun initTimerFakeLocation() {
+ cdtFakeLocation?.cancel()
+ cdtFakeLocation = object : CountDownTimer(PERIOD_UPDATES_SERIE, PERIOD_LOCATION_UPDATE) {
override fun onTick(millisUntilFinished: Long) {
var altitude_buf: Double = altitude ?: return
var speed_buf: Float = speed ?: return
@@ -109,11 +136,11 @@ class FakeLocationService : Service() {
if(fakeLocation != null && altitude != null && speed != null && jitter != null) {
try {
fakeLocationModule.setTestProviderLocation(
- altitude_buf + ((Random.nextFloat() * jitter_buf) - (jitter_buf * 0.5f)),
+ (altitude_buf + ((Random.nextFloat() * jitter_buf) - (jitter_buf * 0.5f))).toDouble(),
speed_buf + ((Random.nextFloat() * jitter_buf) - (jitter_buf * 0.5f)),
jitter_buf,
- fakeLocation_buf.first + (((Random.nextFloat() * jitter_buf) - (jitter_buf * 0.5f)) / 111139.0f),
- fakeLocation_buf.second + (((Random.nextFloat() * jitter_buf) - (jitter_buf * 0.5f)) / 111139.0f)
+ (fakeLocation_buf.first + (((Random.nextFloat() * jitter_buf) - (jitter_buf * 0.5f)) / 111139.0f)).toDouble(),
+ (fakeLocation_buf.second + (((Random.nextFloat() * jitter_buf) - (jitter_buf * 0.5f)) / 111139.0f)).toDouble()
)
} catch (e: Exception) {
Log.d("FakeLocationService", "setting fake location", e)
@@ -122,7 +149,66 @@ class FakeLocationService : Service() {
}
override fun onFinish() {
- initTimer()
+ initTimerFakeLocation()
+ }
+ }.start()
+ }
+
+ private fun calculateRouteSegment(route: List<FakeLocationCoordinate>, routeTime: Float): Pair<FakeLocationCoordinate,Pair<Float,Float>>? {
+ if(route.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
+ }
+ }
+ if(loopRoute)
+ routeReversed = !routeReversed
+ } while(loopRoute)
+ return null
+ }
+
+ private fun initTimerRoute() {
+ cdtRoute?.cancel()
+ 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)
+ if(coord == null) {
+ // done with route
+ return
+ }
+ try {
+ fakeLocationModule.setTestProviderLocation(
+ (coord.first.altitude + ((Random.nextFloat() * coord.first.jitter) - (coord.first.jitter * 0.5f))).toDouble(),
+ coord.first.speed + ((Random.nextFloat() * coord.first.jitter) - (coord.first.jitter * 0.5f)),
+ coord.first.jitter,
+ (coord.second.first + (((Random.nextFloat() * coord.first.jitter) - (coord.first.jitter * 0.5f)) / 111139.0f)).toDouble(),
+ (coord.second.second + (((Random.nextFloat() * coord.first.jitter) - (coord.first.jitter * 0.5f)) / 111139.0f)).toDouble()
+ )
+ } catch (e: Exception) {
+ Log.d("FakeLocationService", "setting fake location", e)
+ }
+ routeTime += (PERIOD_LOCATION_UPDATE / 1000f)
+ }
+
+ override fun onFinish() {
+ initTimerRoute()
}
}.start()
}