diff options
17 files changed, 307 insertions, 450 deletions
diff --git a/app/build.gradle b/app/build.gradle index bc07919..77e1113 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,14 +95,14 @@ dependencies { compileOnly files('libs/e-ui-sdk-1.0.1-q.jar') implementation files('libs/lineage-sdk.jar') implementation files('libs/trackerfilter.aar') - + //implementation project(":privacymodulesapi") // include the google specific version of the modules, just for the google flavor - // TODO: google version is broken for now. googleImplementation project(":privacymodulesgoogle") + googleImplementation project(":privacymodulesgoogle") // include the e specific version of the modules, just for the e flavor - eImplementation 'foundation.e:privacymodule.e-29:0.3.2' + eImplementation project(":privacymodulese") - implementation 'foundation.e:privacymodule.api:0.3.2' + implementation 'foundation.e:privacymodule.api:0.4.1' implementation 'foundation.e:privacymodule.tor:0.1.1' implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index b73a79d..d8c3047 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -22,21 +22,22 @@ import android.content.Context import android.os.Process import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.usecases.AppListUseCase +import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase +import foundation.e.privacycentralapp.dummy.CityDataSource import foundation.e.privacycentralapp.dummy.TrackTrackersPrivacyMock import foundation.e.privacycentralapp.features.dashboard.DashBoardViewModelFactory import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyViewModelFactory import foundation.e.privacycentralapp.features.location.FakeLocationViewModelFactory -import foundation.e.privacycentralapp.features.location.LocationApiDelegate import foundation.e.privacycentralapp.features.trackers.TrackersViewModelFactory import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersViewModelFactory import foundation.e.privacymodules.ipscrambler.IpScramblerModule import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule -import foundation.e.privacymodules.location.FakeLocation -import foundation.e.privacymodules.location.IFakeLocation +import foundation.e.privacymodules.location.FakeLocationModule +import foundation.e.privacymodules.location.IFakeLocationModule import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription import foundation.e.trackerfilter.api.BlockTrackersPrivacyModule @@ -54,7 +55,7 @@ class DependencyContainer constructor(val app: Application) { val context: Context by lazy { app.applicationContext } // Drivers - private val fakeLocationModule: IFakeLocation by lazy { FakeLocation(app.applicationContext) } + private val fakeLocationModule: IFakeLocationModule by lazy { FakeLocationModule(app.applicationContext) } private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) } private val ipScramblerModule: IIpScramblerModule by lazy { IpScramblerModule(app.applicationContext) } @@ -67,10 +68,6 @@ class DependencyContainer constructor(val app: Application) { ) } - private val locationApi by lazy { - LocationApiDelegate(fakeLocationModule, permissionsModule, appDesc) - } - private val blockTrackersPrivacyModule by lazy { BlockTrackersPrivacyModule.getInstance(context) } // Repositories @@ -85,9 +82,8 @@ class DependencyContainer constructor(val app: Application) { private val ipScramblingStateUseCase by lazy { IpScramblingStateUseCase(ipScramblerModule, localStateRepository, GlobalScope) } - private val appListUseCase by lazy { - AppListUseCase(permissionsModule, GlobalScope) - } + private val appListUseCase = AppListUseCase(permissionsModule, GlobalScope) + private val trackersStatisticsUseCase by lazy { TrackersStatisticsUseCase(trackTrackersPrivacyModule) } @@ -96,13 +92,20 @@ class DependencyContainer constructor(val app: Application) { TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, permissionsModule, localStateRepository, GlobalScope) } + private val fakeLocationStateUseCase by lazy { + FakeLocationStateUseCase( + fakeLocationModule, permissionsModule, localStateRepository, CityDataSource, appDesc, GlobalScope) + } + // ViewModelFactories val dashBoardViewModelFactory by lazy { DashBoardViewModelFactory(getQuickPrivacyStateUseCase, ipScramblingStateUseCase, trackersStatisticsUseCase, trackersStateUseCase) } val fakeLocationViewModelFactory by lazy { - FakeLocationViewModelFactory(locationApi) + FakeLocationViewModelFactory( + getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase, + fakeLocationStateUseCase = fakeLocationStateUseCase) } val blockerService = BlockerInterface.getInstance(context) diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/CityDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/CityDataSource.kt new file mode 100644 index 0000000..5fab9b7 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/CityDataSource.kt @@ -0,0 +1,46 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.privacycentralapp.dummy + +object CityDataSource { + private val BARCELONA = Pair(41.3851f, 2.1734f) + private val BUDAPEST = Pair(47.4979f, 19.0402f) + private val ABU_DHABI = Pair(24.4539f, 54.3773f) + private val HYDERABAD = Pair(17.3850f, 78.4867f) + private val QUEZON_CITY = Pair(14.6760f, 121.0437f) + private val PARIS = Pair(48.8566f, 2.3522f) + private val LONDON = Pair(51.5074f, 0.1278f) + private val SHANGHAI = Pair(31.2304f, 121.4737f) + private val MADRID = Pair(40.4168f, 3.7038f) + private val LAHORE = Pair(31.5204f, 74.3587f) + private val CHICAGO = Pair(41.8781f, 87.6298f) + + val citiesLocationsList = listOf( + BARCELONA, + BUDAPEST, + ABU_DHABI, + HYDERABAD, + QUEZON_CITY, + PARIS, + LONDON, + SHANGHAI, + MADRID, + LAHORE, + CHICAGO + ) +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt index 5f22c96..78cb4e4 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/LocalStateRepository.kt @@ -26,6 +26,8 @@ class LocalStateRepository(context: Context) { private const val SHARED_PREFS_FILE = "localState" private const val KEY_QUICK_PRIVACY = "quickPrivacy" private const val KEY_IP_SCRAMBLING = "ipScrambling" + private const val KEY_FAKE_LATITUDE = "fakeLatitude" + private const val KEY_FAKE_LONGITUDE = "fakeLongitude" } private val sharedPref = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE) @@ -40,6 +42,27 @@ class LocalStateRepository(context: Context) { var quickPrivacyEnabledFlow: Flow<Boolean> = quickPrivacyEnabledMutableFlow + var fakeLocation: Pair<Float, Float>? + get() = if (sharedPref.contains(KEY_FAKE_LATITUDE) && sharedPref.contains( + KEY_FAKE_LONGITUDE)) + Pair( + sharedPref.getFloat(KEY_FAKE_LATITUDE, 0f), + sharedPref.getFloat(KEY_FAKE_LONGITUDE, 0f)) + else null + set(value) { + if (value == null) { + sharedPref.edit() + .remove(KEY_FAKE_LATITUDE) + .remove(KEY_FAKE_LONGITUDE) + .commit() + } else { + sharedPref.edit() + .putFloat(KEY_FAKE_LATITUDE, value.first) + .putFloat(KEY_FAKE_LONGITUDE, value.second) + .commit() + } + } + var isIpScramblingEnabled: Boolean get() = sharedPref.getBoolean(KEY_IP_SCRAMBLING, false) set(value) = set(KEY_IP_SCRAMBLING, value) diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt index dbb9b0a..35a77b3 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/LocationMode.kt @@ -18,5 +18,5 @@ package foundation.e.privacycentralapp.domain.entities enum class LocationMode { - REAL_LOCATION, RANDOM_LOCATION, CUSTOM_LOCATION + REAL_LOCATION, RANDOM_LOCATION, SPECIFIC_LOCATION } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt index 6d13f0e..02fdb0f 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt @@ -17,47 +17,89 @@ package foundation.e.privacycentralapp.domain.usecases -/*import android.app.AppOpsManager -import android.content.Intent -import android.util.Log +import android.app.AppOpsManager import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.entities.LocationMode -import foundation.e.privacycentralapp.features.location.LocationApiDelegate -import foundation.e.privacymodules.location.IFakeLocation +import foundation.e.privacycentralapp.dummy.CityDataSource +import foundation.e.privacymodules.location.IFakeLocationModule import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.AppOpModes import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlin.random.Random class FakeLocationStateUseCase( - private val fakeLocationModule: IFakeLocation, + private val fakeLocationModule: IFakeLocationModule, + private val permissionsModule: PermissionsPrivacyModule, private val localStateRepository: LocalStateRepository, + private val citiesRepository: CityDataSource, private val appDesc: ApplicationDescription, private val coroutineScope: CoroutineScope ) { - private fun acquireLocationPermission() { - try { - - permissionsModule.setAppOpMode( - appDesc, AppOpsManager.OPSTR_COARSE_LOCATION, - AppOpModes.ALLOWED - ) - permissionsModule.setAppOpMode( - appDesc, AppOpsManager.OPSTR_FINE_LOCATION, - AppOpModes.ALLOWED - ) - } catch (e: Exception) { - // Log.e(TAG, "Can't start RealLocation", e) + init { + coroutineScope.launch { + localStateRepository.quickPrivacyEnabledFlow.collect { + applySettings(it, localStateRepository.fakeLocation) + } } } - private fun applySettings(isQuickPrivacyEnabled: Boolean, fakeLocationMode: LocationMode) { - when { - // isQuickPrivacyEnabled -> + fun getLocationMode(): LocationMode = when(localStateRepository.fakeLocation) { + null -> LocationMode.REAL_LOCATION + in citiesRepository.citiesLocationsList -> LocationMode.RANDOM_LOCATION + else -> LocationMode.SPECIFIC_LOCATION + } + + private fun acquireLocationPermission() { + permissionsModule.toggleDangerousPermission(appDesc, + android.Manifest.permission.ACCESS_FINE_LOCATION, true) + + // permissionsModule.setAppOpMode( + // appDesc, AppOpsManager.OPSTR_COARSE_LOCATION, + // AppOpModes.ALLOWED + // ) + // permissionsModule.setAppOpMode( + // appDesc, AppOpsManager.OPSTR_FINE_LOCATION, + // AppOpModes.ALLOWED + // ) + } + + private fun applySettings(isQuickPrivacyEnabled: Boolean, fakeLocation: Pair<Float, Float>?) { + if (isQuickPrivacyEnabled && fakeLocation != null) { + if (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) != AppOpModes.ALLOWED) { + permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED) + } + fakeLocationModule.startFakeLocation() + + fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble()) + } else { + fakeLocationModule.stopFakeLocation() } } + fun setSpecificLocation(latitude: Float, longitude: Float) { + setFakeLocation(latitude to longitude) + } + + fun setRandomLocation() { + val randomIndex = Random.nextInt(citiesRepository.citiesLocationsList.size) + val location = citiesRepository.citiesLocationsList[randomIndex] + + setFakeLocation(location) + } + + private fun setFakeLocation(location: Pair<Float, Float>) { + localStateRepository.fakeLocation = location + applySettings(true, location) + } + + fun stopFakeLocation() { + localStateRepository.fakeLocation = null + applySettings(true, null) + } -}*/ +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/CityDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/CityDataSource.kt deleted file mode 100644 index 988c7f4..0000000 --- a/app/src/main/java/foundation/e/privacycentralapp/dummy/CityDataSource.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacycentralapp.dummy - -import foundation.e.privacycentralapp.domain.entities.LocationMode -import kotlin.random.Random - -data class City(val name: String, val latitude: Double, val longitude: Double) { - - fun toRandomLocation(): Location { - return Location(LocationMode.RANDOM_LOCATION, this.latitude, this.longitude) - } -} - -object CityDataSource { - private val BARCELONA = Pair(41.3851, 2.1734) - private val BUDAPEST = Pair(47.4979, 19.0402) - private val ABU_DHABI = Pair(24.4539, 54.3773) - private val HYDERABAD = Pair(17.3850, 78.4867) - private val QUEZON_CITY = Pair(14.6760, 121.0437) - private val PARIS = Pair(48.8566, 2.3522) - private val LONDON = Pair(51.5074, 0.1278) - private val SHANGHAI = Pair(31.2304, 121.4737) - private val MADRID = Pair(40.4168, 3.7038) - private val LAHORE = Pair(31.5204, 74.3587) - private val CHICAGO = Pair(41.8781, 87.6298) - - // LatLong Array, the order should be the same as that of R.array.cities - private val latLongArray = arrayOf( - BARCELONA, - BUDAPEST, - ABU_DHABI, - HYDERABAD, - QUEZON_CITY, - PARIS, - LONDON, - SHANGHAI, - MADRID, - LAHORE, - CHICAGO - ) - - fun getRandomCity(cities: Array<String>): City { - if (cities.size != latLongArray.size) { - throw IllegalStateException("LatLong array must have the same number of element as in cities array.") - } - val randomIndex = Random.nextInt(cities.size) - val latLong = latLongArray[randomIndex] - return City(cities[randomIndex], latLong.first, latLong.second) - } -} diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt index 246854b..38daeab 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt @@ -183,9 +183,9 @@ object DummyDataSource { requireNotNull(location) { "Custom location should be null" } _location.value = location } - LocationMode.CUSTOM_LOCATION -> { + LocationMode.SPECIFIC_LOCATION -> { requireNotNull(location) { "Custom location should be null" } - _location.value = location.copy(mode = LocationMode.CUSTOM_LOCATION) + _location.value = location.copy(mode = LocationMode.SPECIFIC_LOCATION) } } return true diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt index 04ee5bf..ab4ba72 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt @@ -24,5 +24,5 @@ import foundation.e.privacycentralapp.domain.entities.LocationMode fun LocationMode.mapToString(context: Context): String = when (this) { LocationMode.REAL_LOCATION -> context.getString(R.string.real_location_mode) LocationMode.RANDOM_LOCATION -> context.getString(R.string.random_location_mode) - LocationMode.CUSTOM_LOCATION -> context.getString(R.string.fake_location_mode) + LocationMode.SPECIFIC_LOCATION -> context.getString(R.string.fake_location_mode) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt index 5185737..39b6138 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt @@ -56,7 +56,7 @@ class DashboardFeature( val totalGraph: Int? = null, // val graphData val trackersCount: Int? = null, - val dayTrackersCount: Int? = null, + val activeTrackersCount: Int? = null, val dayStatistics: List<Int>? = null ) diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt index b16af28..7c6a715 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt @@ -18,17 +18,17 @@ package foundation.e.privacycentralapp.features.location import android.util.Log -import com.mapbox.mapboxsdk.geometry.LatLng import foundation.e.flowmvi.Actor import foundation.e.flowmvi.Reducer import foundation.e.flowmvi.SingleEventProducer import foundation.e.flowmvi.feature.BaseFeature import foundation.e.privacycentralapp.domain.entities.LocationMode -import foundation.e.privacycentralapp.dummy.CityDataSource -import foundation.e.privacycentralapp.dummy.DummyDataSource -import foundation.e.privacycentralapp.dummy.Location +import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase +import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge // Define a state machine for Fake location feature class FakeLocationFeature( @@ -46,7 +46,10 @@ class FakeLocationFeature( singleEventProducer ) { data class State( - val location: Location + val isEnabled: Boolean, + val mode: LocationMode, + val specificLatitude: Float? = null, + val specificLongitude: Float? = null ) sealed class SingleEvent { @@ -57,153 +60,97 @@ class FakeLocationFeature( } sealed class Action { + object Init : Action() // Action which is triggered everytime the location is updated. - data class UpdateLocationAction(val latLng: LatLng) : Action() - object UseRealLocationAction : Action() - data class UseRandomLocationAction( - val cities: Array<String> - ) : Action() { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UseRandomLocationAction - - if (!cities.contentEquals(other.cities)) return false + //data class UpdateLocationAction(val latLng: LatLng) : Action() - return true - } - - override fun hashCode(): Int { - return cities.contentHashCode() - } - } - - object UseSpecificLocationAction : Action() - data class SetCustomFakeLocationAction( - val latitude: Double, - val longitude: Double - ) : Action() + object UseRealLocationAction : Action() + object UseRandomLocationAction : Action() + data class SetSpecificLocationAction( + val latitude: Float, + val longitude: Float) : Action() } sealed class Effect { - data class LocationUpdatedEffect(val latitude: Double, val longitude: Double) : Effect() - object RealLocationSelectedEffect : Effect() - object RandomLocationSelectedEffect : Effect() - object SpecificLocationSelectedEffect : Effect() - object SpecificLocationSavedEffect : Effect() + data class QuickPrivacyUpdatedEffect(val isEnabled: Boolean): Effect() + data class LocationModeUpdatedEffect( + val mode: LocationMode, + val latitude: Float? = null, + val longitude: Float? = null) : Effect() data class ErrorEffect(val message: String) : Effect() + object QuickPrivacyDisabledWarningEffect : Effect() } companion object { fun create( initialState: State = State( - Location( - LocationMode.REAL_LOCATION, - 0.0, - 0.0 - ) + isEnabled = false, + mode = LocationMode.REAL_LOCATION ), - coroutineScope: CoroutineScope, - locationApi: LocationApiDelegate + getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, + fakeLocationStateUseCase: FakeLocationStateUseCase, + coroutineScope: CoroutineScope ) = FakeLocationFeature( initialState, coroutineScope, reducer = { state, effect -> when (effect) { - Effect.RandomLocationSelectedEffect -> state.copy( - location = state.location.copy( - mode = LocationMode.RANDOM_LOCATION - ) - ) - Effect.RealLocationSelectedEffect -> state.copy( - location = state.location.copy( - mode = LocationMode.REAL_LOCATION - ) - ) - is Effect.ErrorEffect, Effect.SpecificLocationSavedEffect -> state - is Effect.LocationUpdatedEffect -> state.copy( - location = state.location.copy( - latitude = effect.latitude, - longitude = effect.longitude - ) - ) - is Effect.SpecificLocationSelectedEffect -> state.copy( - location = state.location.copy( - mode = LocationMode.CUSTOM_LOCATION - ) - ) + is Effect.QuickPrivacyUpdatedEffect -> state.copy(isEnabled = effect.isEnabled) + is Effect.LocationModeUpdatedEffect -> state.copy( + mode = effect.mode, + specificLatitude = effect.latitude, + specificLongitude = effect.longitude) + + is Effect.ErrorEffect, + Effect.QuickPrivacyDisabledWarningEffect -> state } }, - actor = { _, action -> + actor = { state, action -> when (action) { - is Action.UpdateLocationAction -> flowOf( - Effect.LocationUpdatedEffect( - action.latLng.latitude, - action.latLng.longitude - ) - ) - is Action.SetCustomFakeLocationAction -> { - val location = Location( - LocationMode.CUSTOM_LOCATION, - action.latitude, - action.longitude - ) - locationApi.setFakeLocation(action.latitude, action.longitude) - val success = DummyDataSource.setLocationMode( - LocationMode.CUSTOM_LOCATION, - location - ) - if (success) { - flowOf( - Effect.SpecificLocationSavedEffect + is Action.Init -> merge( + getQuickPrivacyStateUseCase.quickPrivacyEnabledFlow.map { Effect.QuickPrivacyUpdatedEffect(it) }, + flowOf(Effect.LocationModeUpdatedEffect(fakeLocationStateUseCase.getLocationMode()))) + + // is Action.UpdateLocationAction -> flowOf( + // Effect.LocationUpdatedEffect( + // action.latLng.latitude, + // action.latLng.longitude + // ) + // ) + + is Action.SetSpecificLocationAction -> { + if (state.isEnabled) { + fakeLocationStateUseCase.setSpecificLocation( + action.latitude, + action.longitude ) - } else { flowOf( - Effect.ErrorEffect("Couldn't select location") + Effect.LocationModeUpdatedEffect( + mode = LocationMode.SPECIFIC_LOCATION, + latitude = action.latitude, + longitude = action.longitude + ) ) - } + } else flowOf(Effect.QuickPrivacyDisabledWarningEffect) } is Action.UseRandomLocationAction -> { - val randomCity = CityDataSource.getRandomCity(action.cities) - locationApi.setFakeLocation(randomCity.latitude, randomCity.longitude) - val success = DummyDataSource.setLocationMode( - LocationMode.RANDOM_LOCATION, - randomCity.toRandomLocation() - ) - if (success) { - flowOf( - Effect.RandomLocationSelectedEffect - ) - } else { - flowOf( - Effect.ErrorEffect("Couldn't select location") - ) - } + if (state.isEnabled) { + fakeLocationStateUseCase.setRandomLocation() + flowOf(Effect.LocationModeUpdatedEffect(LocationMode.RANDOM_LOCATION)) + } else flowOf(Effect.QuickPrivacyDisabledWarningEffect) } is Action.UseRealLocationAction -> { - locationApi.startRealLocation() - val success = DummyDataSource.setLocationMode(LocationMode.REAL_LOCATION) - if (success) { - flowOf( - Effect.RealLocationSelectedEffect - ) - } else { - flowOf( - Effect.ErrorEffect("Couldn't select location") - ) - } - } - is Action.UseSpecificLocationAction -> { - flowOf(Effect.SpecificLocationSelectedEffect) + if (state.isEnabled) { + fakeLocationStateUseCase.stopFakeLocation() + flowOf(Effect.LocationModeUpdatedEffect(LocationMode.REAL_LOCATION)) + } else flowOf(Effect.QuickPrivacyDisabledWarningEffect) } } }, singleEventProducer = { _, _, effect -> when (effect) { - Effect.RandomLocationSelectedEffect -> SingleEvent.RandomLocationSelectedEvent - Effect.SpecificLocationSavedEffect -> SingleEvent.SpecificLocationSavedEvent - Effect.RealLocationSelectedEffect -> SingleEvent.RealLocationSelectedEvent + Effect.QuickPrivacyDisabledWarningEffect -> + SingleEvent.ErrorEvent("Enabled Quick Privacy to use functionalities") is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) else -> null } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt index bdc405e..0f69808 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt @@ -28,7 +28,6 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView -import android.widget.RadioButton import android.widget.Toast import androidx.annotation.NonNull import androidx.core.view.isVisible @@ -62,6 +61,7 @@ import foundation.e.privacycentralapp.common.NavToolbarFragment import foundation.e.privacycentralapp.databinding.FragmentFakeLocationBinding import foundation.e.privacycentralapp.domain.entities.LocationMode import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf +import foundation.e.privacycentralapp.features.location.FakeLocationFeature.Action import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive @@ -93,11 +93,13 @@ class FakeLocationFragment : private var inputJob: Job? = null + private var displayedLocation: Pair<Float, Float>? = null // Callback which updates the map in realtime. private val locationChangeCallback: LocationEngineCallback<LocationEngineResult> = object : LocationEngineCallback<LocationEngineResult> { override fun onSuccess(result: LocationEngineResult?) { result?.lastLocation?.let { + displayedLocation = it.latitude.toFloat() to it.longitude.toFloat() mapboxMap.locationComponent.forceLocationUpdate( LocationUpdate.Builder().location(it).animationDuration(100) .build() @@ -114,16 +116,16 @@ class FakeLocationFragment : } // Only update location when location mode is set to real location or random location. // It basically triggers a UI update. - if (viewModel.fakeLocationFeature.state.value.location.mode != LocationMode.CUSTOM_LOCATION) { - viewModel.submitAction( - FakeLocationFeature.Action.UpdateLocationAction( - LatLng( - it.latitude, - it.longitude - ) - ) - ) - } + // if (viewModel.fakeLocationFeature.state.value.location.mode != LocationMode.SPECIFIC_LOCATION) { + // viewModel.submitAction( + // FakeLocationFeature.Action.UpdateLocationAction( + // LatLng( + // it.latitude, + // it.longitude + // ) + // ) + // ) + // } } } @@ -148,29 +150,13 @@ class FakeLocationFragment : lifecycleScope.launchWhenStarted { viewModel.fakeLocationFeature.singleEvents.collect { event -> when (event) { - is FakeLocationFeature.SingleEvent.RandomLocationSelectedEvent -> { - displayToast("Random location selected") - hoveringMarker?.visibility = View.GONE - isCameraMoved = false - } - is FakeLocationFeature.SingleEvent.SpecificLocationSavedEvent -> { - // Hide camera hover marker when custom location is picked from map. - displayToast("Specific location selected") - hoveringMarker?.visibility = View.GONE - isCameraMoved = false - } is FakeLocationFeature.SingleEvent.ErrorEvent -> { displayToast(event.error) - isCameraMoved = false - } - FakeLocationFeature.SingleEvent.RealLocationSelectedEvent -> { - displayToast("Real location selected") - hoveringMarker?.visibility = View.GONE - isCameraMoved = false } } } } + lifecycleScope.launchWhenStarted { viewModel.submitAction(Action.Init) } } override fun onAttach(context: Context) { @@ -206,25 +192,29 @@ class FakeLocationFragment : binding.mapView.addView(hoveringMarker) hoveringMarker?.visibility = View.GONE // Keep hovering marker hidden by default - mapboxMap.addOnCameraMoveStartedListener { - // Show marker when user starts to move across the map. - hoveringMarker?.visibility = if (binding.mapView.isEnabled) { - View.VISIBLE - } else { - View.GONE - } - isCameraMoved = true - } - - mapboxMap.addOnCameraMoveListener { - if (binding.mapView.isEnabled) { - viewModel.submitAction( - FakeLocationFeature.Action.UpdateLocationAction( - mapboxMap.cameraPosition.target - ) - ) - } - } + // mapboxMap.addOnCameraMoveStartedListener { + // // Show marker when user starts to move across the map. + // hoveringMarker?.visibility = if (binding.mapView.isEnabled) { + // View.VISIBLE + // } else { + // View.GONE + // } + // isCameraMoved = true + // } + // + // mapboxMap.addOnCameraMoveListener { + // if (binding.mapView.isEnabled) { + // mapboxMap.cameraPosition.target.let { + // viewModel.submitAction( + // Action.SetSpecificLocationAction( + // it.latitude.toFloat(), + // it.longitude.toFloat() + // ) + // ) + // } + // } + + // } // Bind click listeners once map is ready. bindClickListeners() } @@ -238,8 +228,8 @@ class FakeLocationFragment : delay(DEBOUNCE_PERIOD) ensureActive() try { - val value = editable.toString().toDouble() - val maxValue = if (isLat) 90.0 else 180.0 + val value = editable.toString().toFloat() + val maxValue = if (isLat) 90f else 180f if (value > maxValue || value < -maxValue) { throw NumberFormatException("value $value is out of bounds") @@ -251,12 +241,10 @@ class FakeLocationFragment : // Here, value is valid, try to send the values try { - val lat = binding.edittextLatitude.text.toString().toDouble() - val lon = binding.edittextLongitude.text.toString().toDouble() - if (lat <= 90.0 && lat >= -90.0 && lon <= 180.0 && lon >= -180.0) { - viewModel.submitAction( - FakeLocationFeature.Action.SetCustomFakeLocationAction(lat, lon) - ) + val lat = binding.edittextLatitude.text.toString().toFloat() + val lon = binding.edittextLongitude.text.toString().toFloat() + if (lat <= 90f && lat >= -90f && lon <= 180f && lon >= -180f) { + viewModel.submitAction(Action.SetSpecificLocationAction(lat, lon)) } } catch (e: NumberFormatException) {} } catch (e: NumberFormatException) { @@ -268,14 +256,16 @@ class FakeLocationFragment : } private fun bindClickListeners() { - binding.radioUseRealLocation.setOnClickListener { radioButton -> - toggleLocationType(radioButton) + binding.radioUseRealLocation.setOnClickListener { + viewModel.submitAction(Action.UseRealLocationAction) } - binding.radioUseRandomLocation.setOnClickListener { radioButton -> - toggleLocationType(radioButton) + binding.radioUseRandomLocation.setOnClickListener { + viewModel.submitAction(Action.UseRandomLocationAction) } - binding.radioUseSpecificLocation.setOnClickListener { radioButton -> - toggleLocationType(radioButton) + binding.radioUseSpecificLocation.setOnClickListener { + viewModel.submitAction( +Action.SetSpecificLocationAction(displayedLocation?.first?: 0f, displayedLocation?.second?: 0f) + ) } binding.edittextLatitude.addTextChangedListener( @@ -295,50 +285,25 @@ class FakeLocationFragment : ) } - private fun toggleLocationType(radioButton: View?) { - if (radioButton is RadioButton) { - val checked = radioButton.isChecked - when (radioButton.id) { - R.id.radio_use_real_location -> - if (checked) { - viewModel.submitAction( - FakeLocationFeature.Action.UseRealLocationAction - ) - } - R.id.radio_use_random_location -> - if (checked) { - viewModel.submitAction( - FakeLocationFeature.Action.UseRandomLocationAction( - resources.getStringArray(R.array.cities) - ) - ) - } - R.id.radio_use_specific_location -> - if (checked) { - viewModel.submitAction( - FakeLocationFeature.Action.UseSpecificLocationAction - ) - } - } - } - } - override fun render(state: FakeLocationFeature.State) { - binding.radioUseRandomLocation.isChecked = (state.location.mode == LocationMode.RANDOM_LOCATION) + hoveringMarker?.visibility = View.GONE + isCameraMoved = false + + binding.radioUseRandomLocation.isChecked = (state.mode == LocationMode.RANDOM_LOCATION) binding.radioUseSpecificLocation.isChecked = - (state.location.mode == LocationMode.CUSTOM_LOCATION) - binding.radioUseRealLocation.isChecked = (state.location.mode == LocationMode.REAL_LOCATION) + (state.mode == LocationMode.SPECIFIC_LOCATION) + binding.radioUseRealLocation.isChecked = (state.mode == LocationMode.REAL_LOCATION) - binding.mapView.isEnabled = (state.location.mode == LocationMode.CUSTOM_LOCATION) + binding.mapView.isEnabled = (state.mode == LocationMode.SPECIFIC_LOCATION) - binding.textlayoutLatitude.isVisible = (state.location.mode == LocationMode.CUSTOM_LOCATION) - binding.textlayoutLongitude.isVisible = (state.location.mode == LocationMode.CUSTOM_LOCATION) + binding.textlayoutLatitude.isVisible = (state.mode == LocationMode.SPECIFIC_LOCATION) + binding.textlayoutLongitude.isVisible = (state.mode == LocationMode.SPECIFIC_LOCATION) - binding.edittextLatitude.setText(state.location.latitude.toString()) - binding.edittextLongitude.setText(state.location.longitude.toString()) + binding.edittextLatitude.setText(state.specificLatitude?.toString()) + binding.edittextLongitude.setText(state.specificLongitude?.toString()) } - override fun actions(): Flow<FakeLocationFeature.Action> = viewModel.actions + override fun actions(): Flow<Action> = viewModel.actions @SuppressLint("MissingPermission") private fun enableLocationPlugin(@NonNull loadedMapStyle: Style) { diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt index b73c228..70ee0c1 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt @@ -20,17 +20,24 @@ package foundation.e.privacycentralapp.features.location import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.common.Factory +import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase +import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch -class FakeLocationViewModel(private val locationApi: LocationApiDelegate) : ViewModel() { +class FakeLocationViewModel( + private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, + private val fakeLocationStateUseCase: FakeLocationStateUseCase) : ViewModel() { private val _actions = MutableSharedFlow<FakeLocationFeature.Action>() val actions = _actions.asSharedFlow() val fakeLocationFeature: FakeLocationFeature by lazy { - FakeLocationFeature.create(coroutineScope = viewModelScope, locationApi = locationApi) + FakeLocationFeature.create( + getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase, + fakeLocationStateUseCase = fakeLocationStateUseCase, + coroutineScope = viewModelScope) } fun submitAction(action: FakeLocationFeature.Action) { @@ -40,8 +47,10 @@ class FakeLocationViewModel(private val locationApi: LocationApiDelegate) : View } } -class FakeLocationViewModelFactory(private val locationApi: LocationApiDelegate) : Factory<FakeLocationViewModel> { +class FakeLocationViewModelFactory( + private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, + private val fakeLocationStateUseCase: FakeLocationStateUseCase) : Factory<FakeLocationViewModel> { override fun create(): FakeLocationViewModel { - return FakeLocationViewModel((locationApi)) + return FakeLocationViewModel(getQuickPrivacyStateUseCase, fakeLocationStateUseCase) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt deleted file mode 100644 index 88fef3e..0000000 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 <https://www.gnu.org/licenses/>. - */ - -package foundation.e.privacycentralapp.features.location - -import android.app.AppOpsManager -import android.util.Log -import foundation.e.privacymodules.location.IFakeLocation -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule -import foundation.e.privacymodules.permissions.data.AppOpModes -import foundation.e.privacymodules.permissions.data.ApplicationDescription - -class LocationApiDelegate( - private val fakeLocationModule: IFakeLocation, - private val permissionsModule: PermissionsPrivacyModule, - private val appDesc: ApplicationDescription -) { - - private val TAG = LocationApiDelegate::class.simpleName - - fun setFakeLocation(latitude: Double, longitude: Double) { - if (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) != AppOpModes.ALLOWED) { - permissionsModule.setAppOpMode( - appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, - AppOpModes.ALLOWED - ) - } - try { - fakeLocationModule.startFakeLocation() - } catch (e: Exception) { - Log.e(TAG, "Can't startFakeLocation", e) - } - fakeLocationModule.setFakeLocation(latitude, longitude) - } - - fun stopFakeLocation() { - try { - permissionsModule.setAppOpMode( - appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, - AppOpModes.IGNORED - ) - permissionsModule.setAppOpMode( - appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, - AppOpModes.IGNORED - ) - fakeLocationModule.stopFakeLocation() - } catch (e: Exception) { - Log.e(TAG, "Can't stop FakeLocation", e) - } - } - - fun startRealLocation() { - stopFakeLocation() - try { - permissionsModule.setAppOpMode( - appDesc, AppOpsManager.OPSTR_COARSE_LOCATION, - AppOpModes.ALLOWED - ) - permissionsModule.setAppOpMode( - appDesc, AppOpsManager.OPSTR_FINE_LOCATION, - AppOpModes.ALLOWED - ) - } catch (e: Exception) { - Log.e(TAG, "Can't start RealLocation", e) - } - } -} diff --git a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt index 03784a0..ce7edd3 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt @@ -29,6 +29,7 @@ import foundation.e.privacycentralapp.features.dashboard.DashboardFragment import foundation.e.trackerfilter.DNSBlockerService import foundation.e.trackerfilter.StatsIntentService + open class MainActivity : FragmentActivity(R.layout.activity_main) { override fun onCreate(savedInstanceState: Bundle?) { @@ -44,15 +45,15 @@ open class MainActivity : FragmentActivity(R.layout.activity_main) { override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) handleIntent(intent) + // TODO move into tracker module. + AndroidEnvironment.initEnvironment(this) + startService(Intent(this, DNSBlockerService::class.java)) + startService(Intent(this, StatsIntentService::class.java)) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleIntent(intent) - - AndroidEnvironment.initEnvironment(this) - startService(Intent(this, DNSBlockerService::class.java)) - startService(Intent(this, StatsIntentService::class.java)) } open fun handleIntent(intent: Intent) {} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml deleted file mode 100644 index 702947b..0000000 --- a/app/src/main/res/values/arrays.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ 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 <https://www.gnu.org/licenses/>. - --> - -<resources> - <string-array name="cities"> - <item>Barcelona</item> - <item>Budapest</item> - <item>Abu Dhabi</item> - <item>Hyderabad</item> - <item>Quezon City</item> - <item>Paris</item> - <item>London</item> - <item>Shanghai</item> - <item>Madrid</item> - <item>Lahore</item> - <item>Chicago</item> - </string-array> -</resources>
\ No newline at end of file diff --git a/build.gradle b/build.gradle index f1ec9b7..e1df856 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ import foundation.e.privacycentral.buildsrc.ReleaseType buildscript { ext.buildConfig = [ 'compileSdk': 30, - 'minSdk' : 24, + 'minSdk' : 26, 'targetSdk' : 30, 'version' : [ 'major': 1, |