summaryrefslogtreecommitdiff
path: root/app
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 /app
parent298dff2a877680e928b37e3a1336dc7d7aa52dfb (diff)
Implemented route mode
Diffstat (limited to 'app')
-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
11 files changed, 375 insertions, 78 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>