diff options
author | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-05-02 21:25:17 +0200 |
---|---|---|
committer | Guillaume Jacquart <guillaume.jacquart@hoodbrains.com> | 2023-05-02 22:00:35 +0200 |
commit | a8874167f663885f2d3371801cf03681576ac817 (patch) | |
tree | 5be07b8768142efeade536d4135f2250c1ac9071 /app/src/main/java/foundation/e/advancedprivacy/domain | |
parent | a0ee04ea9dbc0802c828afdf660eb37dc6fa350f (diff) | |
download | advanced-privacy-a8874167f663885f2d3371801cf03681576ac817.tar.gz |
1200: rename everything to AdvancedPrivacy
Diffstat (limited to 'app/src/main/java/foundation/e/advancedprivacy/domain')
15 files changed, 1180 insertions, 0 deletions
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/AppWithCounts.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/AppWithCounts.kt new file mode 100644 index 0000000..4169ecc --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/AppWithCounts.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 MURENA SAS + * Copyright (C) 2022 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.advancedprivacy.domain.entities + +import android.graphics.drawable.Drawable +import foundation.e.privacymodules.permissions.data.ApplicationDescription + +data class AppWithCounts( + val appDesc: ApplicationDescription, + val packageName: String, + val uid: Int, + var label: CharSequence?, + var icon: Drawable?, + val isWhitelisted: Boolean = false, + val trackersCount: Int = 0, + val whiteListedTrackersCount: Int = 0, + val blockedLeaks: Int = 0, + val leaks: Int = 0, +) { + constructor( + app: ApplicationDescription, + isWhitelisted: Boolean, + trackersCount: Int, + whiteListedTrackersCount: Int, + blockedLeaks: Int, + leaks: Int, + ) : + this( + appDesc = app, + packageName = app.packageName, + uid = app.uid, + label = app.label, + icon = app.icon, + isWhitelisted = isWhitelisted, + trackersCount = trackersCount, + whiteListedTrackersCount = whiteListedTrackersCount, + blockedLeaks = blockedLeaks, + leaks = leaks + ) + + val blockedTrackersCount get() = if (isWhitelisted) 0 + else Math.max(trackersCount - whiteListedTrackersCount, 0) +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/InternetPrivacyMode.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/InternetPrivacyMode.kt new file mode 100644 index 0000000..986e798 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/InternetPrivacyMode.kt @@ -0,0 +1,29 @@ +/* + * 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.advancedprivacy.domain.entities + +enum class InternetPrivacyMode { + REAL_IP, + HIDE_IP, + HIDE_IP_LOADING, + REAL_IP_LOADING; + + val isChecked get() = this == HIDE_IP || this == HIDE_IP_LOADING + + val isLoading get() = this == HIDE_IP_LOADING || this == REAL_IP_LOADING +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt new file mode 100644 index 0000000..62581eb --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/LocationMode.kt @@ -0,0 +1,22 @@ +/* + * 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.advancedprivacy.domain.entities + +enum class LocationMode { + REAL_LOCATION, RANDOM_LOCATION, SPECIFIC_LOCATION +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/MainFeatures.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/MainFeatures.kt new file mode 100644 index 0000000..c63d3ab --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/MainFeatures.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 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.advancedprivacy.domain.entities + +enum class MainFeatures { + TRACKERS_CONTROL, FAKE_LOCATION, IP_SCRAMBLING +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/QuickPrivacyState.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/QuickPrivacyState.kt new file mode 100644 index 0000000..c21bb1d --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/QuickPrivacyState.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 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.advancedprivacy.domain.entities + +enum class QuickPrivacyState { + DISABLED, ENABLED, FULL_ENABLED; + + fun isEnabled(): Boolean = this != DISABLED +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackerMode.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackerMode.kt new file mode 100644 index 0000000..2033251 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackerMode.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 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.advancedprivacy.domain.entities + +enum class TrackerMode { + DENIED, CUSTOM, VULNERABLE +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersPeriodicStatistics.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersPeriodicStatistics.kt new file mode 100644 index 0000000..c0fa637 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersPeriodicStatistics.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 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.advancedprivacy.domain.entities + +data class TrackersPeriodicStatistics( + val callsBlockedNLeaked: List<Pair<Int, Int>>, + val periods: List<String>, + val trackersCount: Int, + val graduations: List<String?>? = null +) diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppListUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppListUseCase.kt new file mode 100644 index 0000000..8d38ee8 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppListUseCase.kt @@ -0,0 +1,39 @@ +/* + * 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.advancedprivacy.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import kotlinx.coroutines.flow.Flow + +class AppListUseCase( + private val appListsRepository: AppListsRepository +) { + val dummySystemApp = appListsRepository.dummySystemApp + fun getApp(uid: Int): ApplicationDescription { + return when (uid) { + dummySystemApp.uid -> dummySystemApp + appListsRepository.dummyCompatibilityApp.uid -> + appListsRepository.dummyCompatibilityApp + else -> appListsRepository.getApp(uid) ?: dummySystemApp + } + } + fun getAppsUsingInternet(): Flow<List<ApplicationDescription>> { + return appListsRepository.mainProfileApps() + } +} 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 new file mode 100644 index 0000000..9b99b95 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt @@ -0,0 +1,209 @@ +/* + * 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.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 android.util.Log +import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.advancedprivacy.domain.entities.LocationMode +import foundation.e.advancedprivacy.dummy.CityDataSource +import foundation.e.privacymodules.fakelocation.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.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlin.random.Random + +class FakeLocationStateUseCase( + private val fakeLocationModule: IFakeLocationModule, + private val permissionsModule: PermissionsPrivacyModule, + private val localStateRepository: LocalStateRepository, + private val citiesRepository: CityDataSource, + private val appDesc: ApplicationDescription, + private val appContext: Context, + coroutineScope: CoroutineScope +) { + companion object { + private const val TAG = "FakeLocationStateUseCase" + } + + private val _configuredLocationMode = MutableStateFlow<Triple<LocationMode, Float?, Float?>>(Triple(LocationMode.REAL_LOCATION, null, null)) + val configuredLocationMode: StateFlow<Triple<LocationMode, Float?, Float?>> = _configuredLocationMode + + init { + coroutineScope.launch { + localStateRepository.fakeLocationEnabled.collect { + applySettings(it, localStateRepository.fakeLocation) + } + } + } + + 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, fakeLocation: Pair<Float, Float>, isSpecificLocation: Boolean = false) { + _configuredLocationMode.value = computeLocationMode(isEnabled, fakeLocation, isSpecificLocation) + + if (isEnabled && hasAcquireMockLocationPermission()) { + fakeLocationModule.startFakeLocation() + fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble()) + localStateRepository.locationMode.value = configuredLocationMode.value.first + } else { + fakeLocationModule.stopFakeLocation() + localStateRepository.locationMode.value = 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 setSpecificLocation(latitude: Float, longitude: Float) { + setFakeLocation(latitude to longitude, true) + } + + fun setRandomLocation() { + val randomIndex = Random.nextInt(citiesRepository.citiesLocationsList.size) + val location = citiesRepository.citiesLocationsList[randomIndex] + + setFakeLocation(location) + } + + private fun setFakeLocation(location: Pair<Float, Float>, isSpecificLocation: Boolean = false) { + localStateRepository.fakeLocation = location + localStateRepository.setFakeLocationEnabled(true) + applySettings(true, location, isSpecificLocation) + } + + fun stopFakeLocation() { + localStateRepository.setFakeLocationEnabled(false) + applySettings(false, localStateRepository.fakeLocation) + } + + private fun computeLocationMode( + isFakeLocationEnabled: Boolean, + fakeLocation: Pair<Float, Float>, + isSpecificLocation: Boolean = false, + ): Triple<LocationMode, Float?, Float?> { + return Triple( + when { + !isFakeLocationEnabled -> LocationMode.REAL_LOCATION + (fakeLocation in citiesRepository.citiesLocationsList && !isSpecificLocation) -> + LocationMode.RANDOM_LOCATION + else -> LocationMode.SPECIFIC_LOCATION + }, + fakeLocation.first, + fakeLocation.second + ) + } + + val currentLocation = MutableStateFlow<Location?>(null) + + private var localListener = object : LocationListener { + + override fun onLocationChanged(location: Location) { + currentLocation.update { previous -> + if ((previous?.time ?: 0) + 1800 < location.time || + (previous?.accuracy ?: Float.MAX_VALUE) > location.accuracy + ) { + location + } else { + previous + } + } + } + + // 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 + ) + } + + networkProvider?.let { locationManager.getLastKnownLocation(it) } + ?: gpsProvider?.let { locationManager.getLastKnownLocation(it) } + ?.let { + localListener.onLocationChanged(it) + } + } catch (se: SecurityException) { + Log.e(TAG, "Missing permission", se) + } + } +} 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 new file mode 100644 index 0000000..475c05d --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt @@ -0,0 +1,89 @@ +/* + * 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.advancedprivacy.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode +import foundation.e.advancedprivacy.domain.entities.LocationMode +import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState +import foundation.e.advancedprivacy.domain.entities.TrackerMode +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map + +class GetQuickPrivacyStateUseCase( + private val localStateRepository: LocalStateRepository +) { + val quickPrivacyState: Flow<QuickPrivacyState> = combine( + localStateRepository.blockTrackers, + localStateRepository.areAllTrackersBlocked, + localStateRepository.locationMode, + localStateRepository.internetPrivacyMode + ) { isBlockTrackers, isAllTrackersBlocked, locationMode, internetPrivacyMode -> + when { + !isBlockTrackers && + locationMode == LocationMode.REAL_LOCATION && + internetPrivacyMode == InternetPrivacyMode.REAL_IP -> QuickPrivacyState.DISABLED + + isAllTrackersBlocked && + locationMode != LocationMode.REAL_LOCATION && + internetPrivacyMode in listOf( + InternetPrivacyMode.HIDE_IP, + InternetPrivacyMode.HIDE_IP_LOADING + ) -> QuickPrivacyState.FULL_ENABLED + + else -> QuickPrivacyState.ENABLED + } + } + + val trackerMode: Flow<TrackerMode> = combine( + localStateRepository.blockTrackers, + localStateRepository.areAllTrackersBlocked + ) { isBlockTrackers, isAllTrackersBlocked -> + when { + isBlockTrackers && isAllTrackersBlocked -> TrackerMode.DENIED + isBlockTrackers && !isAllTrackersBlocked -> TrackerMode.CUSTOM + else -> TrackerMode.VULNERABLE + } + } + + val isLocationHidden: Flow<Boolean> = localStateRepository.locationMode.map { locationMode -> + locationMode != LocationMode.REAL_LOCATION + } + + val locationMode: StateFlow<LocationMode> = localStateRepository.locationMode + + val ipScramblingMode: Flow<InternetPrivacyMode> = localStateRepository.internetPrivacyMode + + fun toggleTrackers() { + localStateRepository.setBlockTrackers(!localStateRepository.blockTrackers.value) + } + + fun toggleLocation() { + localStateRepository.setFakeLocationEnabled(!localStateRepository.fakeLocationEnabled.value) + } + + fun toggleIpScrambling() { + localStateRepository.setIpScramblingSetting(!localStateRepository.ipScramblingSetting.value) + } + + val otherVpnRunning: SharedFlow<ApplicationDescription> = localStateRepository.otherVpnRunning +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt new file mode 100644 index 0000000..8c94602 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2021 E FOUNDATION, 2023 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package foundation.e.advancedprivacy.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode +import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode.HIDE_IP +import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode.HIDE_IP_LOADING +import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode.REAL_IP +import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode.REAL_IP_LOADING +import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule +import foundation.e.privacymodules.permissions.IPermissionsPrivacyModule +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class IpScramblingStateUseCase( + private val ipScramblerModule: IIpScramblerModule, + private val permissionsPrivacyModule: IPermissionsPrivacyModule, + private val appDesc: ApplicationDescription, + private val localStateRepository: LocalStateRepository, + private val appListsRepository: AppListsRepository, + private val coroutineScope: CoroutineScope +) { + val internetPrivacyMode: StateFlow<InternetPrivacyMode> = callbackFlow { + val listener = object : IIpScramblerModule.Listener { + override fun onStatusChanged(newStatus: IIpScramblerModule.Status) { + trySend(map(newStatus)) + } + + override fun log(message: String) {} + override fun onTrafficUpdate( + upload: Long, + download: Long, + read: Long, + write: Long + ) { + } + } + ipScramblerModule.addListener(listener) + ipScramblerModule.requestStatus() + awaitClose { ipScramblerModule.removeListener(listener) } + }.stateIn( + scope = coroutineScope, + started = SharingStarted.Eagerly, + initialValue = REAL_IP + ) + + init { + coroutineScope.launch(Dispatchers.Default) { + localStateRepository.ipScramblingSetting.collect { + applySettings(it) + } + } + + coroutineScope.launch { + internetPrivacyMode.collect { localStateRepository.internetPrivacyMode.value = it } + } + } + + fun toggle(hideIp: Boolean) { + localStateRepository.setIpScramblingSetting(enabled = hideIp) + } + + private fun getHiddenPackageNames(): List<String> { + return appListsRepository.getMainProfileHiddenSystemApps().map { it.packageName } + } + + val bypassTorApps: Set<String> get() { + var whitelist = ipScramblerModule.appList + if (getHiddenPackageNames().any { it in whitelist }) { + val mutable = whitelist.toMutableSet() + mutable.removeAll(getHiddenPackageNames()) + mutable.add(appListsRepository.dummySystemApp.packageName) + whitelist = mutable + } + if (AppListsRepository.compatibiltyPNames.any { it in whitelist }) { + val mutable = whitelist.toMutableSet() + mutable.removeAll(AppListsRepository.compatibiltyPNames) + mutable.add(appListsRepository.dummyCompatibilityApp.packageName) + whitelist = mutable + } + return whitelist + } + + fun toggleBypassTor(packageName: String) { + val visibleList = bypassTorApps.toMutableSet() + val rawList = ipScramblerModule.appList.toMutableSet() + + if (visibleList.contains(packageName)) { + if (packageName == appListsRepository.dummySystemApp.packageName) { + rawList.removeAll(getHiddenPackageNames()) + } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) { + rawList.removeAll(AppListsRepository.compatibiltyPNames) + } else { + rawList.remove(packageName) + } + } else { + if (packageName == appListsRepository.dummySystemApp.packageName) { + rawList.addAll(getHiddenPackageNames()) + } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) { + rawList.addAll(AppListsRepository.compatibiltyPNames) + } else { + rawList.add(packageName) + } + } + ipScramblerModule.appList = rawList + } + + private fun applySettings(isIpScramblingEnabled: Boolean) { + val currentMode = localStateRepository.internetPrivacyMode.value + when { + isIpScramblingEnabled && currentMode in setOf(REAL_IP, REAL_IP_LOADING) -> + applyStartIpScrambling() + + !isIpScramblingEnabled && currentMode in setOf(HIDE_IP, HIDE_IP_LOADING) -> + ipScramblerModule.stop() + + else -> {} + } + } + + private fun applyStartIpScrambling() { + ipScramblerModule.prepareAndroidVpn()?.let { + permissionsPrivacyModule.setVpnPackageAuthorization(appDesc.packageName) + permissionsPrivacyModule.getAlwaysOnVpnPackage() + }?.let { + coroutineScope.launch { + localStateRepository.emitOtherVpnRunning( + permissionsPrivacyModule.getApplicationDescription(packageName = it, withIcon = false) + ) + } + localStateRepository.setIpScramblingSetting(enabled = false) + } ?: run { + ipScramblerModule.start(enableNotification = false) + } + } + + private fun map(status: IIpScramblerModule.Status): InternetPrivacyMode { + return when (status) { + IIpScramblerModule.Status.OFF -> REAL_IP + IIpScramblerModule.Status.ON -> HIDE_IP + IIpScramblerModule.Status.STARTING -> HIDE_IP_LOADING + IIpScramblerModule.Status.STOPPING, + IIpScramblerModule.Status.START_DISABLED -> REAL_IP_LOADING + } + } +} 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 new file mode 100644 index 0000000..11bce86 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/ShowFeaturesWarningUseCase.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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.advancedprivacy.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.advancedprivacy.domain.entities.MainFeatures +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.dropWhile +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +class ShowFeaturesWarningUseCase( + private val localStateRepository: LocalStateRepository +) { + + fun showWarning(): Flow<MainFeatures> { + return merge( + localStateRepository.blockTrackers.drop(1).dropWhile { !it } + .filter { it && !localStateRepository.hideWarningTrackers } + .map { MainFeatures.TRACKERS_CONTROL }, + localStateRepository.fakeLocationEnabled.drop(1).dropWhile { !it } + .filter { it && !localStateRepository.hideWarningLocation } + .map { MainFeatures.FAKE_LOCATION }, + localStateRepository.ipScramblingSetting.drop(1).dropWhile { !it } + .filter { it && !localStateRepository.hideWarningIpScrambling } + .map { MainFeatures.IP_SCRAMBLING } + ) + } + + fun doNotShowAgain(feature: MainFeatures) { + when (feature) { + MainFeatures.TRACKERS_CONTROL -> localStateRepository.hideWarningTrackers = true + MainFeatures.FAKE_LOCATION -> localStateRepository.hideWarningLocation = true + MainFeatures.IP_SCRAMBLING -> localStateRepository.hideWarningIpScrambling = true + } + } +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt new file mode 100644 index 0000000..882d53f --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 E FOUNDATION, 2022 - 2023 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package foundation.e.advancedprivacy.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.advancedprivacy.data.repositories.TrackersRepository +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.privacymodules.trackers.api.IBlockTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.Tracker +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class TrackersStateUseCase( + private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule, + private val trackersPrivacyModule: ITrackTrackersPrivacyModule, + private val localStateRepository: LocalStateRepository, + private val trackersRepository: TrackersRepository, + private val appListsRepository: AppListsRepository, + private val coroutineScope: CoroutineScope +) { + init { + trackersPrivacyModule.start( + trackers = trackersRepository.trackers, + getAppByAPId = appListsRepository::getApp, + getAppByUid = appListsRepository::getApp, + enableNotification = false + ) + coroutineScope.launch { + localStateRepository.blockTrackers.collect { enabled -> + if (enabled) { + blockTrackersPrivacyModule.enableBlocking() + } else { + blockTrackersPrivacyModule.disableBlocking() + } + updateAllTrackersBlockedState() + } + } + } + + private fun updateAllTrackersBlockedState() { + localStateRepository.areAllTrackersBlocked.value = blockTrackersPrivacyModule.isBlockingEnabled() && + blockTrackersPrivacyModule.isWhiteListEmpty() + } + + fun isWhitelisted(app: ApplicationDescription): Boolean { + return isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule) + } + + fun toggleAppWhitelist(app: ApplicationDescription, isWhitelisted: Boolean) { + appListsRepository.applyForHiddenApps(app) { + blockTrackersPrivacyModule.setWhiteListed(it, isWhitelisted) + } + updateAllTrackersBlockedState() + } + + fun blockTracker(app: ApplicationDescription, tracker: Tracker, isBlocked: Boolean) { + appListsRepository.applyForHiddenApps(app) { + blockTrackersPrivacyModule.setWhiteListed(tracker, it, !isBlocked) + } + updateAllTrackersBlockedState() + } + + fun clearWhitelist(app: ApplicationDescription) { + appListsRepository.applyForHiddenApps( + app, + blockTrackersPrivacyModule::clearWhiteList + ) + updateAllTrackersBlockedState() + } + + fun updateTrackers() = coroutineScope.launch { + trackersRepository.update() + trackersPrivacyModule.start( + trackers = trackersRepository.trackers, + getAppByAPId = appListsRepository::getApp, + getAppByUid = appListsRepository::getApp, + enableNotification = false + ) + } +} + +fun isWhitelisted( + app: ApplicationDescription, + appListsRepository: AppListsRepository, + blockTrackersPrivacyModule: IBlockTrackersPrivacyModule +): Boolean { + return appListsRepository.anyForHiddenApps(app, blockTrackersPrivacyModule::isWhitelisted) +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt new file mode 100644 index 0000000..43e4496 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2021 E FOUNDATION, 2022 - 2023 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package foundation.e.advancedprivacy.domain.usecases + +import android.content.res.Resources +import foundation.e.advancedprivacy.R +import foundation.e.advancedprivacy.common.throttleFirst +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.domain.entities.AppWithCounts +import foundation.e.advancedprivacy.domain.entities.TrackersPeriodicStatistics +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.privacymodules.trackers.api.IBlockTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.api.Tracker +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +class TrackersStatisticsUseCase( + private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, + private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule, + private val appListsRepository: AppListsRepository, + private val resources: Resources +) { + fun initAppList() { + appListsRepository.apps() + } + + private fun rawUpdates(): Flow<Unit> = callbackFlow { + val listener = object : ITrackTrackersPrivacyModule.Listener { + override fun onNewData() { + trySend(Unit) + } + } + trackTrackersPrivacyModule.addListener(listener) + awaitClose { trackTrackersPrivacyModule.removeListener(listener) } + } + + @OptIn(FlowPreview::class) + fun listenUpdates(debounce: Duration = 1.seconds) = rawUpdates() + .throttleFirst(windowDuration = debounce) + .onStart { emit(Unit) } + + fun getDayStatistics(): Pair<TrackersPeriodicStatistics, Int> { + return TrackersPeriodicStatistics( + callsBlockedNLeaked = trackTrackersPrivacyModule.getPastDayTrackersCalls(), + periods = buildDayLabels(), + trackersCount = trackTrackersPrivacyModule.getPastDayTrackersCount(), + graduations = buildDayGraduations(), + ) to trackTrackersPrivacyModule.getTrackersCount() + } + + fun getNonBlockedTrackersCount(): Flow<Int> { + return if (blockTrackersPrivacyModule.isBlockingEnabled()) + appListsRepository.allApps().map { apps -> + val whiteListedTrackers = mutableSetOf<Tracker>() + val whiteListedApps = blockTrackersPrivacyModule.getWhiteListedApp() + apps.forEach { app -> + if (app in whiteListedApps) { + whiteListedTrackers.addAll(trackTrackersPrivacyModule.getTrackersForApp(app)) + } else { + whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app)) + } + } + whiteListedTrackers.size + } + else flowOf(trackTrackersPrivacyModule.getTrackersCount()) + } + + fun getMostLeakedApp(): ApplicationDescription? { + return trackTrackersPrivacyModule.getPastDayMostLeakedApp() + } + + fun getDayTrackersCalls() = trackTrackersPrivacyModule.getPastDayTrackersCalls() + + fun getDayTrackersCount() = trackTrackersPrivacyModule.getPastDayTrackersCount() + + private fun buildDayGraduations(): List<String?> { + val formatter = DateTimeFormatter.ofPattern( + resources.getString(R.string.trackers_graph_hours_period_format) + ) + + val periods = mutableListOf<String?>() + var end = ZonedDateTime.now() + for (i in 1..24) { + val start = end.truncatedTo(ChronoUnit.HOURS) + periods.add(if (start.hour % 6 == 0) formatter.format(start) else null) + end = start.minus(1, ChronoUnit.MINUTES) + } + return periods.reversed() + } + + private fun buildDayLabels(): List<String> { + val formatter = DateTimeFormatter.ofPattern( + resources.getString(R.string.trackers_graph_hours_period_format) + ) + val periods = mutableListOf<String>() + var end = ZonedDateTime.now() + for (i in 1..24) { + val start = end.truncatedTo(ChronoUnit.HOURS) + periods.add("${formatter.format(start)} - ${formatter.format(end)}") + end = start.minus(1, ChronoUnit.MINUTES) + } + return periods.reversed() + } + + private fun buildMonthLabels(): List<String> { + val formater = DateTimeFormatter.ofPattern( + resources.getString(R.string.trackers_graph_days_period_format) + ) + val periods = mutableListOf<String>() + var day = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS) + for (i in 1..30) { + periods.add(formater.format(day)) + day = day.minus(1, ChronoUnit.DAYS) + } + return periods.reversed() + } + + private fun buildYearLabels(): List<String> { + val formater = DateTimeFormatter.ofPattern( + resources.getString(R.string.trackers_graph_months_period_format) + ) + val periods = mutableListOf<String>() + var month = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1) + for (i in 1..12) { + periods.add(formater.format(month)) + month = month.minus(1, ChronoUnit.MONTHS) + } + return periods.reversed() + } + + fun getDayMonthYearStatistics(): Triple<TrackersPeriodicStatistics, TrackersPeriodicStatistics, TrackersPeriodicStatistics> { + return with(trackTrackersPrivacyModule) { + Triple( + TrackersPeriodicStatistics( + callsBlockedNLeaked = getPastDayTrackersCalls(), + periods = buildDayLabels(), + trackersCount = getPastDayTrackersCount() + ), + TrackersPeriodicStatistics( + callsBlockedNLeaked = getPastMonthTrackersCalls(), + periods = buildMonthLabels(), + trackersCount = getPastMonthTrackersCount() + ), + TrackersPeriodicStatistics( + callsBlockedNLeaked = getPastYearTrackersCalls(), + periods = buildYearLabels(), + trackersCount = getPastYearTrackersCount() + ) + ) + } + } + + fun getTrackersWithWhiteList(app: ApplicationDescription): List<Pair<Tracker, Boolean>> { + return appListsRepository.mapReduceForHiddenApps( + app = app, + map = { appDesc: ApplicationDescription -> + ( + trackTrackersPrivacyModule.getTrackersForApp(appDesc) to + blockTrackersPrivacyModule.getWhiteList(appDesc) + ) + }, + reduce = { lists -> + lists.unzip().let { (trackerLists, whiteListedIdLists) -> + val whiteListedIds = whiteListedIdLists.flatten().map { it.id }.toSet() + + trackerLists.flatten().distinctBy { it.id }.sortedBy { it.label.lowercase() } + .map { tracker -> tracker to (tracker.id in whiteListedIds) } + } + } + ) + } + + fun isWhiteListEmpty(app: ApplicationDescription): Boolean { + return appListsRepository.mapReduceForHiddenApps( + app = app, + map = { appDesc: ApplicationDescription -> + blockTrackersPrivacyModule.getWhiteList(appDesc).isEmpty() + }, + reduce = { areEmpty -> areEmpty.all { it } } + ) + } + + fun getCalls(app: ApplicationDescription): Pair<Int, Int> { + return appListsRepository.mapReduceForHiddenApps( + app = app, + map = trackTrackersPrivacyModule::getPastDayTrackersCallsForApp, + reduce = { zip -> + zip.unzip().let { (blocked, leaked) -> + blocked.sum() to leaked.sum() + } + } + ) + } + + fun getAppsWithCounts(): Flow<List<AppWithCounts>> { + val trackersCounts = trackTrackersPrivacyModule.getTrackersCountByApp() + val hiddenAppsTrackersWithWhiteList = + getTrackersWithWhiteList(appListsRepository.dummySystemApp) + val acAppsTrackersWithWhiteList = + getTrackersWithWhiteList(appListsRepository.dummyCompatibilityApp) + + return appListsRepository.apps() + .map { apps -> + val callsByApp = trackTrackersPrivacyModule.getPastDayTrackersCallsByApps() + apps.map { app -> + val calls = appListsRepository.mapReduceForHiddenApps( + app = app, + map = { callsByApp.getOrDefault(app, 0 to 0) }, + reduce = { + it.unzip().let { (blocked, leaked) -> + blocked.sum() to leaked.sum() + } + } + ) + + AppWithCounts( + app = app, + isWhitelisted = !blockTrackersPrivacyModule.isBlockingEnabled() || + isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule), + trackersCount = when (app) { + appListsRepository.dummySystemApp -> + hiddenAppsTrackersWithWhiteList.size + appListsRepository.dummyCompatibilityApp -> + acAppsTrackersWithWhiteList.size + else -> trackersCounts.getOrDefault(app, 0) + }, + whiteListedTrackersCount = when (app) { + appListsRepository.dummySystemApp -> + hiddenAppsTrackersWithWhiteList.count { it.second } + appListsRepository.dummyCompatibilityApp -> + acAppsTrackersWithWhiteList.count { it.second } + else -> + blockTrackersPrivacyModule.getWhiteList(app).size + }, + blockedLeaks = calls.first, + leaks = calls.second + ) + } + .sortedWith(mostLeakedAppsComparator) + } + } + + private val mostLeakedAppsComparator: Comparator<AppWithCounts> = Comparator { o1, o2 -> + val leaks = o2.leaks - o1.leaks + if (leaks != 0) leaks else { + val whitelisted = o2.whiteListedTrackersCount - o1.whiteListedTrackersCount + if (whitelisted != 0) whitelisted else { + o2.trackersCount - o1.trackersCount + } + } + } +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/UpdateWidgetUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/UpdateWidgetUseCase.kt new file mode 100644 index 0000000..94c734c --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/UpdateWidgetUseCase.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 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.advancedprivacy.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule + +class UpdateWidgetUseCase( + private val localStateRepository: LocalStateRepository, + private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, +) { + init { + trackTrackersPrivacyModule.addListener(object : ITrackTrackersPrivacyModule.Listener { + override fun onNewData() { + } + }) + } +} |