/*
* Copyright (C) 2024 Leonard Kugis
* Copyright (C) 2023 MURENA SAS
* Copyright (C) 2021 E FOUNDATION
*
* 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 .
*/
package foundation.e.advancedprivacy.domain.usecases
import android.app.AppOpsManager
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import foundation.e.advancedprivacy.domain.entities.AppOpModes
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.domain.entities.LocationMode
import foundation.e.advancedprivacy.domain.repositories.LocalStateRepository
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
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.random.Random
class FakeLocationStateUseCase(
private val fakeLocationModule: FakeLocationModule,
private val permissionsModule: IPermissionsPrivacyModule,
private val localStateRepository: LocalStateRepository,
private val citiesRepository: CityDataSource,
private val appDesc: ApplicationDescription,
private val appContext: Context,
coroutineScope: CoroutineScope
) {
private val _configuredLocationMode = MutableStateFlow(
FakeLocationState(LocationMode.REAL_LOCATION, null, null, null, null, null, null, false, null, false, false)
)
val configuredLocationMode: StateFlow = _configuredLocationMode
init {
coroutineScope.launch {
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)
}
}
}
private val locationManager: LocationManager
get() = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private fun hasAcquireLocationPermission(): Boolean {
return (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) ||
permissionsModule.toggleDangerousPermission(appDesc, android.Manifest.permission.ACCESS_FINE_LOCATION, true)
}
// 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()
// 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
}
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 useRandomLocation() {
val randomIndex = Random.nextInt(citiesRepository.citiesLocationsList.size)
val location = citiesRepository.citiesLocationsList[randomIndex]
useFakeLocation(location)
}
fun useFakeLocation(location: Pair) {
localStateRepository.fakeLocation = location
_configuredLocationMode.value = FakeLocationState(
LocationMode.SPECIFIC_LOCATION,
null,
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? = null) {
_configuredLocationMode.value = FakeLocationState(
LocationMode.ROUTE,
null,
null,
null,
null,
null,
null,
false,
route,
false,
false
)
localStateRepository.setLocationMode(LocationMode.ROUTE)
}
fun setRoute(route: List) {
_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(null)
private var localListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
currentLocation.update { location }
}
@Deprecated("Deprecated since API 29, never called.")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {
reset()
}
override fun onProviderDisabled(provider: String) {
reset()
}
private fun reset() {
stopListeningLocation()
currentLocation.value = null
startListeningLocation()
}
}
fun startListeningLocation(): Boolean {
return if (hasAcquireLocationPermission()) {
requestLocationUpdates()
true
} else false
}
fun stopListeningLocation() {
locationManager.removeUpdates(localListener)
}
private fun requestLocationUpdates() {
val networkProvider = LocationManager.NETWORK_PROVIDER
.takeIf { it in locationManager.allProviders }
val gpsProvider = LocationManager.GPS_PROVIDER
.takeIf { it in locationManager.allProviders }
try {
networkProvider?.let {
locationManager.requestLocationUpdates(
it,
1000L,
0f,
localListener
)
}
gpsProvider?.let {
locationManager.requestLocationUpdates(
it,
1000L,
0f,
localListener
)
}
var lastKnownLocation = networkProvider?.let {
locationManager.getLastKnownLocation(it)
}
if (lastKnownLocation == null) {
lastKnownLocation = gpsProvider?.let {
locationManager.getLastKnownLocation(it)
}
}
lastKnownLocation?.let { localListener.onLocationChanged(it) }
} catch (se: SecurityException) {
Timber.e(se, "Missing permission")
}
}
}