/* * 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") } } }