From 53f4a9ce311d612d43fa770cf7e8f8e98fbb43a0 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 12 Sep 2023 06:17:39 +0000 Subject: 2: organise module with clean archi, use Koin for injection. --- app/build.gradle | 8 +- .../advancedprivacy/AdvancedPrivacyApplication.kt | 57 ++- .../e/advancedprivacy/DependencyContainer.kt | 210 ---------- .../foundation/e/advancedprivacy/KoinModule.kt | 124 ++++++ .../foundation/e/advancedprivacy/Notifications.kt | 6 +- .../e/advancedprivacy/UpdateTrackersWorker.kt | 59 --- .../e/advancedprivacy/common/ToggleAppsAdapter.kt | 2 +- .../e/advancedprivacy/common/WarningDialog.kt | 5 +- .../data/repositories/AppListsRepository.kt | 281 ------------- .../data/repositories/LocalStateRepository.kt | 2 +- .../data/repositories/TrackersRepository.kt | 133 ------ .../domain/entities/AppWithCounts.kt | 1 - .../domain/usecases/AppListUseCase.kt | 2 +- .../domain/usecases/FakeLocationStateUseCase.kt | 12 +- .../domain/usecases/GetQuickPrivacyStateUseCase.kt | 4 +- .../domain/usecases/IpScramblingStateUseCase.kt | 24 +- .../domain/usecases/TrackersStateUseCase.kt | 61 +-- .../domain/usecases/TrackersStatisticsUseCase.kt | 115 +++-- .../domain/usecases/UpdateWidgetUseCase.kt | 33 -- .../features/dashboard/DashboardFragment.kt | 12 +- .../internetprivacy/InternetPrivacyFragment.kt | 12 +- .../internetprivacy/InternetPrivacyState.kt | 2 +- .../internetprivacy/InternetPrivacyViewModel.kt | 4 +- .../features/location/FakeLocationFragment.kt | 12 +- .../features/trackers/TrackersFragment.kt | 11 +- .../trackers/apptrackers/AppTrackersFragment.kt | 14 +- .../trackers/apptrackers/AppTrackersState.kt | 4 +- .../trackers/apptrackers/AppTrackersViewModel.kt | 4 +- .../trackers/apptrackers/ToggleTrackersAdapter.kt | 2 +- .../widget/WidgetCommandReceiver.kt | 11 +- core/.gitignore | 1 + core/build.gradle | 52 +++ core/consumer-rules.pro | 0 core/proguard-rules.pro | 21 + core/src/main/AndroidManifest.xml | 22 + .../e/advancedprivacy/core/KoinModule.kt | 23 + .../advancedprivacy/core/utils/CoroutinesUtils.kt | 45 ++ .../data/repositories/AppListsRepository.kt | 264 ++++++++++++ .../advancedprivacy/domain/entities/AppOpModes.kt | 47 +++ .../domain/entities/ApplicationDescription.kt | 50 +++ .../domain/entities/PermissionDescription.kt | 26 ++ .../permissions/APermissionsPrivacyModule.kt | 160 +++++++ .../permissions/IPermissionsPrivacyModule.kt | 142 +++++++ fakelocation/build.gradle | 8 +- fakelocation/fakelocationdemo/build.gradle | 2 +- .../fakelocationdemo/MainActivity.kt | 10 +- fakelocation/src/main/AndroidManifest.xml | 3 +- .../e/advancedprivacy/fakelocation/KoinModule.kt | 9 + .../domain/usecases/FakeLocationModule.kt | 133 ++++++ .../fakelocation/services/FakeLocationService.kt | 111 +++++ .../fakelocation/FakeLocationModule.kt | 132 ------ .../fakelocation/FakeLocationService.kt | 110 ----- .../fakelocation/IFakeLocationModule.kt | 41 -- gradle/libs.versions.toml | 5 + ipscrambling/build.gradle | 1 + ipscrambling/orbotservice | 1 - ipscrambling/src/main/AndroidManifest.xml | 2 +- .../ipscrambler/IpScramblerModule.kt | 308 ++++++++++++++ .../e/advancedprivacy/ipscrambler/KoinModule.kt | 8 + .../ipscrambler/IIpScramblerModule.kt | 54 --- .../ipscrambler/IpScramblerModule.kt | 301 -------------- permissionse/build.gradle | 2 +- permissionse/src/main/AndroidManifest.xml | 19 +- .../permissions/PermissionsPrivacyModule.kt | 258 ++++++++++++ .../permissions/PermissionsPrivacyModule.kt | 257 ------------ permissionsstandalone/build.gradle | 2 +- permissionsstandalone/src/main/AndroidManifest.xml | 3 +- .../externalinterfaces/PermissionsPrivacyModule.kt | 70 ++++ .../permissions/PermissionsPrivacyModule.kt | 69 --- privacymodule-api/.gitignore | 1 - privacymodule-api/build.gradle | 111 ----- privacymodule-api/consumer-rules.pro | 0 privacymodule-api/proguard-rules.pro | 21 - privacymodule-api/src/main/AndroidManifest.xml | 21 - .../e/privacymodules/DependencyInjector.kt | 31 -- .../permissions/APermissionsPrivacyModule.kt | 160 ------- .../permissions/IPermissionsPrivacyModule.kt | 143 ------- .../privacymodules/permissions/data/AppOpModes.kt | 47 --- .../permissions/data/ApplicationDescription.kt | 50 --- .../permissions/data/PermissionDescription.kt | 26 -- .../e/privacymodules/trackers/IDNSBlocker.kt | 26 -- settings.gradle | 4 +- trackers/build.gradle | 9 +- trackers/src/main/AndroidManifest.xml | 5 +- .../e/advancedprivacy/trackers/KoinModule.kt | 72 ++++ .../trackers/data/ETrackersResponse.kt | 10 + .../trackers/data/RemoteTrackersListRepository.kt | 61 +++ .../advancedprivacy/trackers/data/StatsDatabase.kt | 461 +++++++++++++++++++++ .../trackers/data/TrackersRepository.kt | 109 +++++ .../trackers/data/WhitelistRepository.kt | 195 +++++++++ .../trackers/domain/entities/Tracker.kt | 28 ++ .../trackers/domain/usecases/DNSBlocker.kt | 143 +++++++ .../trackers/domain/usecases/StatisticsUseCase.kt | 86 ++++ .../trackers/domain/usecases/TrackersLogger.kt | 60 +++ .../domain/usecases/UpdateTrackerListUseCase.kt | 29 ++ .../trackers/services/DNSBlockerService.kt | 68 +++ .../trackers/services/ForegroundStarter.kt | 45 ++ .../trackers/services/UpdateTrackersWorker.kt | 60 +++ .../privacymodules/trackers/DNSBlockerRunnable.kt | 141 ------- .../e/privacymodules/trackers/DNSBlockerService.kt | 79 ---- .../e/privacymodules/trackers/ForegroundStarter.kt | 45 -- .../e/privacymodules/trackers/TrackersLogger.kt | 69 --- .../trackers/api/BlockTrackersPrivacyModule.kt | 98 ----- .../trackers/api/IBlockTrackersPrivacyModule.kt | 98 ----- .../trackers/api/ITrackTrackersPrivacyModule.kt | 110 ----- .../trackers/api/TrackTrackersPrivacyModule.kt | 126 ------ .../e/privacymodules/trackers/api/Tracker.kt | 28 -- .../privacymodules/trackers/data/StatsDatabase.kt | 459 -------------------- .../trackers/data/StatsRepository.kt | 105 ----- .../trackers/data/TrackersRepository.kt | 57 --- .../trackers/data/WhitelistRepository.kt | 207 --------- 111 files changed, 3544 insertions(+), 4159 deletions(-) delete mode 100644 app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt create mode 100644 app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt delete mode 100644 app/src/main/java/foundation/e/advancedprivacy/UpdateTrackersWorker.kt delete mode 100644 app/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt delete mode 100644 app/src/main/java/foundation/e/advancedprivacy/data/repositories/TrackersRepository.kt delete mode 100644 app/src/main/java/foundation/e/advancedprivacy/domain/usecases/UpdateWidgetUseCase.kt create mode 100644 core/.gitignore create mode 100644 core/build.gradle create mode 100644 core/consumer-rules.pro create mode 100644 core/proguard-rules.pro create mode 100644 core/src/main/AndroidManifest.xml create mode 100644 core/src/main/java/foundation/e/advancedprivacy/core/KoinModule.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/core/utils/CoroutinesUtils.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/domain/entities/AppOpModes.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/domain/entities/ApplicationDescription.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/domain/entities/PermissionDescription.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/APermissionsPrivacyModule.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/IPermissionsPrivacyModule.kt create mode 100644 fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/KoinModule.kt create mode 100644 fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/domain/usecases/FakeLocationModule.kt create mode 100644 fakelocation/src/main/java/foundation/e/advancedprivacy/fakelocation/services/FakeLocationService.kt delete mode 100644 fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationModule.kt delete mode 100644 fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/FakeLocationService.kt delete mode 100644 fakelocation/src/main/java/foundation/e/privacymodules/fakelocation/IFakeLocationModule.kt delete mode 160000 ipscrambling/orbotservice create mode 100644 ipscrambling/src/main/java/foundation/e/advancedprivacy/ipscrambler/IpScramblerModule.kt create mode 100644 ipscrambling/src/main/java/foundation/e/advancedprivacy/ipscrambler/KoinModule.kt delete mode 100644 ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IIpScramblerModule.kt delete mode 100644 ipscrambling/src/main/java/foundation/e/privacymodules/ipscrambler/IpScramblerModule.kt create mode 100644 permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModule.kt delete mode 100644 permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt create mode 100644 permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModule.kt delete mode 100644 permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt delete mode 100644 privacymodule-api/.gitignore delete mode 100644 privacymodule-api/build.gradle delete mode 100644 privacymodule-api/consumer-rules.pro delete mode 100644 privacymodule-api/proguard-rules.pro delete mode 100644 privacymodule-api/src/main/AndroidManifest.xml delete mode 100644 privacymodule-api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt delete mode 100644 privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt delete mode 100644 privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt delete mode 100644 privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt delete mode 100644 privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt delete mode 100644 privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt delete mode 100644 privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/ETrackersResponse.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/WhitelistRepository.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/entities/Tracker.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/UpdateTrackerListUseCase.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/ForegroundStarter.kt create mode 100644 trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/UpdateTrackersWorker.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt delete mode 100644 trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt diff --git a/app/build.gradle b/app/build.gradle index af05ec0..5ce72f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -140,7 +140,7 @@ android { } dependencies { - implementation project(':privacymodule-api') + implementation project(':core') standaloneImplementation project(':permissionsstandalone') eImplementation project(':permissionse') @@ -160,15 +160,13 @@ dependencies { libs.androidx.fragment.ktx, libs.androidx.lifecycle.runtime, libs.androidx.lifecycle.viewmodel, - libs.androidx.work.ktx, + + libs.bundles.koin, libs.google.material, libs.androidx.navigation.fragment, libs.androidx.navigation.ui, - libs.retrofit, - libs.retrofit.scalars, - libs.maplibre, libs.mpandroidcharts, diff --git a/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt b/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt index 9ce0c2b..0af2a0e 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt @@ -18,17 +18,64 @@ package foundation.e.advancedprivacy import android.app.Application +import android.content.Intent +import foundation.e.advancedprivacy.common.WarningDialog +import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase +import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase +import foundation.e.advancedprivacy.domain.usecases.IpScramblingStateUseCase +import foundation.e.advancedprivacy.domain.usecases.ShowFeaturesWarningUseCase +import foundation.e.advancedprivacy.domain.usecases.TrackersStateUseCase +import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase +import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule +import foundation.e.advancedprivacy.trackers.services.DNSBlockerService +import foundation.e.advancedprivacy.trackers.services.UpdateTrackersWorker import foundation.e.lib.telemetry.Telemetry +import kotlinx.coroutines.CoroutineScope +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin +import org.koin.java.KoinJavaComponent.get class AdvancedPrivacyApplication : Application() { - - // Initialize the dependency container. - val dependencyContainer: DependencyContainer by lazy { DependencyContainer(this) } - override fun onCreate() { super.onCreate() Telemetry.init(BuildConfig.SENTRY_DSN, this, true) - dependencyContainer.initBackgroundSingletons() + startKoin { + androidContext(this@AdvancedPrivacyApplication) + modules(appModule) + } + initBackgroundSingletons() + } + + private fun initBackgroundSingletons() { + UpdateTrackersWorker.periodicUpdate(this) + + WarningDialog.startListening( + get(ShowFeaturesWarningUseCase::class.java), + get(CoroutineScope::class.java), + this + ) + + Widget.startListening( + this, + get(GetQuickPrivacyStateUseCase::class.java), + get(TrackersStatisticsUseCase::class.java), + ) + + Notifications.startListening( + this, + get(GetQuickPrivacyStateUseCase::class.java), + get(IPermissionsPrivacyModule::class.java), + get(CoroutineScope::class.java), + ) + + get(IpScramblingStateUseCase::class.java) + get(FakeLocationStateUseCase::class.java) + get(TrackersStateUseCase::class.java) + + val intent = Intent(this, DNSBlockerService::class.java) + intent.action = DNSBlockerService.ACTION_START + intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, false) + startService(intent) } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt b/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt deleted file mode 100644 index f6f2038..0000000 --- a/app/src/main/java/foundation/e/advancedprivacy/DependencyContainer.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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 - -import android.app.Application -import android.content.Context -import android.os.Process -import androidx.lifecycle.DEFAULT_ARGS_KEY -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.CreationExtras -import foundation.e.advancedprivacy.common.WarningDialog -import foundation.e.advancedprivacy.data.repositories.AppListsRepository -import foundation.e.advancedprivacy.data.repositories.LocalStateRepository -import foundation.e.advancedprivacy.data.repositories.TrackersRepository -import foundation.e.advancedprivacy.domain.usecases.AppListUseCase -import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase -import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase -import foundation.e.advancedprivacy.domain.usecases.IpScramblingStateUseCase -import foundation.e.advancedprivacy.domain.usecases.ShowFeaturesWarningUseCase -import foundation.e.advancedprivacy.domain.usecases.TrackersStateUseCase -import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase -import foundation.e.advancedprivacy.dummy.CityDataSource -import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel -import foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyViewModel -import foundation.e.advancedprivacy.features.location.FakeLocationViewModel -import foundation.e.advancedprivacy.features.trackers.TrackersViewModel -import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersFragmentArgs -import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersViewModel -import foundation.e.privacymodules.fakelocation.FakeLocationModule -import foundation.e.privacymodules.ipscrambler.IIpScramblerModule -import foundation.e.privacymodules.ipscrambler.IpScramblerModule -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.permissions.data.ProfileType -import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule -import foundation.e.privacymodules.trackers.api.TrackTrackersPrivacyModule -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope - -/** - * Simple container to hold application wide dependencies. - * - */ -@OptIn(DelicateCoroutinesApi::class) -class DependencyContainer(val app: Application) { - val context: Context by lazy { app.applicationContext } - - // Drivers - private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(app.applicationContext) } - private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) } - private val ipScramblerModule: IIpScramblerModule by lazy { IpScramblerModule(app.applicationContext) } - - private val appDesc by lazy { - ApplicationDescription( - packageName = context.packageName, - uid = Process.myUid(), - label = context.resources.getString(R.string.app_name), - icon = null, - profileId = -1, - profileType = ProfileType.MAIN - ) - } - - private val blockTrackersPrivacyModule by lazy { BlockTrackersPrivacyModule.getInstance(context) } - private val trackTrackersPrivacyModule by lazy { TrackTrackersPrivacyModule.getInstance(context) } - - // Repositories - private val localStateRepository by lazy { LocalStateRepository(context) } - private val trackersRepository by lazy { TrackersRepository(context) } - private val appListsRepository by lazy { AppListsRepository(permissionsModule, context, GlobalScope) } - - // Usecases - val getQuickPrivacyStateUseCase by lazy { - GetQuickPrivacyStateUseCase(localStateRepository) - } - private val ipScramblingStateUseCase by lazy { - IpScramblingStateUseCase( - ipScramblerModule, permissionsModule, appDesc, localStateRepository, - appListsRepository, GlobalScope - ) - } - private val appListUseCase = AppListUseCase(appListsRepository) - - val trackersStatisticsUseCase by lazy { - TrackersStatisticsUseCase(trackTrackersPrivacyModule, blockTrackersPrivacyModule, appListsRepository, context.resources) - } - - val trackersStateUseCase by lazy { - TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, localStateRepository, trackersRepository, appListsRepository, GlobalScope) - } - - private val fakeLocationStateUseCase by lazy { - FakeLocationStateUseCase( - fakeLocationModule, permissionsModule, localStateRepository, CityDataSource, appDesc, context, GlobalScope - ) - } - - val showFeaturesWarningUseCase by lazy { - ShowFeaturesWarningUseCase(localStateRepository = localStateRepository) - } - - val viewModelsFactory by lazy { - ViewModelsFactory( - getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase, - trackersStatisticsUseCase = trackersStatisticsUseCase, - trackersStateUseCase = trackersStateUseCase, - fakeLocationStateUseCase = fakeLocationStateUseCase, - ipScramblerModule = ipScramblerModule, - ipScramblingStateUseCase = ipScramblingStateUseCase, - appListUseCase = appListUseCase - ) - } - - // Background - fun initBackgroundSingletons() { - trackersStateUseCase - ipScramblingStateUseCase - fakeLocationStateUseCase - - UpdateTrackersWorker.periodicUpdate(context) - - WarningDialog.startListening( - showFeaturesWarningUseCase, - GlobalScope, - context - ) - - Widget.startListening( - context, - getQuickPrivacyStateUseCase, - trackersStatisticsUseCase, - ) - - Notifications.startListening( - context, - getQuickPrivacyStateUseCase, - permissionsModule, - GlobalScope - ) - } -} - -@Suppress("LongParameterList") -class ViewModelsFactory( - private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - private val trackersStatisticsUseCase: TrackersStatisticsUseCase, - private val trackersStateUseCase: TrackersStateUseCase, - private val fakeLocationStateUseCase: FakeLocationStateUseCase, - private val ipScramblerModule: IIpScramblerModule, - private val ipScramblingStateUseCase: IpScramblingStateUseCase, - private val appListUseCase: AppListUseCase -) : ViewModelProvider.Factory { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class, extras: CreationExtras): T { - return when (modelClass) { - AppTrackersViewModel::class.java -> { - val app = extras[DEFAULT_ARGS_KEY]?.let { - appListUseCase.getApp(AppTrackersFragmentArgs.fromBundle(it).appUid) - } ?: appListUseCase.dummySystemApp - - AppTrackersViewModel( - app = app, - trackersStateUseCase = trackersStateUseCase, - trackersStatisticsUseCase = trackersStatisticsUseCase, - getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase - ) - } - - TrackersViewModel::class.java -> - TrackersViewModel( - trackersStatisticsUseCase = trackersStatisticsUseCase - ) - FakeLocationViewModel::class.java -> - FakeLocationViewModel( - fakeLocationStateUseCase = fakeLocationStateUseCase - ) - InternetPrivacyViewModel::class.java -> - InternetPrivacyViewModel( - ipScramblerModule = ipScramblerModule, - getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase, - ipScramblingStateUseCase = ipScramblingStateUseCase, - appListUseCase = appListUseCase - ) - DashboardViewModel::class.java -> - DashboardViewModel( - getPrivacyStateUseCase = getQuickPrivacyStateUseCase, - trackersStatisticsUseCase = trackersStatisticsUseCase - ) - else -> throw IllegalArgumentException("Unknown class $modelClass") - } as T - } -} diff --git a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt new file mode 100644 index 0000000..534aa6b --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt @@ -0,0 +1,124 @@ +package foundation.e.advancedprivacy + +import android.content.res.Resources +import android.os.Process +import foundation.e.advancedprivacy.core.coreModule +import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.ProfileType +import foundation.e.advancedprivacy.domain.usecases.AppListUseCase +import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase +import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase +import foundation.e.advancedprivacy.domain.usecases.IpScramblingStateUseCase +import foundation.e.advancedprivacy.domain.usecases.ShowFeaturesWarningUseCase +import foundation.e.advancedprivacy.domain.usecases.TrackersStateUseCase +import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase +import foundation.e.advancedprivacy.dummy.CityDataSource +import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule +import foundation.e.advancedprivacy.fakelocation.fakelocationModule +import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel +import foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyViewModel +import foundation.e.advancedprivacy.features.location.FakeLocationViewModel +import foundation.e.advancedprivacy.features.trackers.TrackersViewModel +import foundation.e.advancedprivacy.features.trackers.apptrackers.AppTrackersViewModel +import foundation.e.advancedprivacy.ipscrambler.ipScramblerModule +import foundation.e.advancedprivacy.permissions.externalinterfaces.PermissionsPrivacyModule +import foundation.e.advancedprivacy.trackers.trackersModule +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.core.module.dsl.singleOf +import org.koin.core.qualifier.named +import org.koin.dsl.module + +val appModule = module { + includes(coreModule, trackersModule, fakelocationModule, ipScramblerModule) + + factory { androidContext().resources } + single { + LocalStateRepository(context = androidContext()) + } + + single(named("AdvancedPrivacy")) { + ApplicationDescription( + packageName = androidContext().packageName, + uid = Process.myUid(), + label = androidContext().resources.getString(R.string.app_name), + icon = null, + profileId = -1, + profileType = ProfileType.MAIN + ) + } + + single(named("DummySystemApp")) { + ApplicationDescription( + packageName = "foundation.e.dummysystemapp", + uid = -1, + label = androidContext().getString(R.string.dummy_system_app_label), + icon = androidContext().getDrawable(R.drawable.ic_e_app_logo), + profileId = -1, + profileType = ProfileType.MAIN + ) + } + + single(named("DummyCompatibilityApp")) { + ApplicationDescription( + packageName = "foundation.e.dummyappscompatibilityapp", + uid = -2, + label = androidContext().getString(R.string.dummy_apps_compatibility_app_label), + icon = androidContext().getDrawable(R.drawable.ic_apps_compatibility_components), + profileId = -1, + profileType = ProfileType.MAIN + ) + } + + single { CityDataSource } + + singleOf(::AppListUseCase) + single { + FakeLocationStateUseCase( + fakeLocationModule = get(), + permissionsModule = get(), + localStateRepository = get(), + citiesRepository = get(), + appDesc = get(named("AdvancedPrivacy")), + appContext = androidContext(), + coroutineScope = get() + ) + } + + singleOf(::GetQuickPrivacyStateUseCase) + single { + IpScramblingStateUseCase( + ipScramblerModule = get(), + permissionsPrivacyModule = get(), + appDesc = get(named("AdvancedPrivacy")), + localStateRepository = get(), + appListsRepository = get(), + coroutineScope = get() + ) + } + singleOf(::ShowFeaturesWarningUseCase) + singleOf(::TrackersStateUseCase) + singleOf(::TrackersStatisticsUseCase) + + single { + PermissionsPrivacyModule(context = androidContext()) + } + + viewModel { parameters -> + val appListUseCase: AppListUseCase = get() + val app = appListUseCase.getApp(parameters.get()) + + AppTrackersViewModel( + app = app, + trackersStateUseCase = get(), + trackersStatisticsUseCase = get(), + getQuickPrivacyStateUseCase = get() + ) + } + viewModelOf(::TrackersViewModel) + viewModelOf(::FakeLocationViewModel) + viewModelOf(::InternetPrivacyViewModel) + viewModelOf(::DashboardViewModel) +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt b/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt index 291f9bc..cd85e9a 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/Notifications.kt @@ -28,8 +28,8 @@ import androidx.core.app.NotificationManagerCompat import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode import foundation.e.advancedprivacy.domain.entities.MainFeatures import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase +import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule import foundation.e.advancedprivacy.main.MainActivity -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn @@ -69,7 +69,7 @@ object Notifications { fun startListening( appContext: Context, getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - permissionsPrivacyModule: PermissionsPrivacyModule, + permissionsPrivacyModule: IPermissionsPrivacyModule, appScope: CoroutineScope ) { createNotificationFlagChannel( @@ -118,7 +118,7 @@ object Notifications { private fun createNotificationFlagChannel( context: Context, - permissionsPrivacyModule: PermissionsPrivacyModule, + permissionsPrivacyModule: IPermissionsPrivacyModule, channelId: String, @StringRes channelName: Int, @StringRes channelDescription: Int, diff --git a/app/src/main/java/foundation/e/advancedprivacy/UpdateTrackersWorker.kt b/app/src/main/java/foundation/e/advancedprivacy/UpdateTrackersWorker.kt deleted file mode 100644 index 418f75b..0000000 --- a/app/src/main/java/foundation/e/advancedprivacy/UpdateTrackersWorker.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.advancedprivacy - -import android.content.Context -import androidx.work.Constraints -import androidx.work.CoroutineWorker -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.NetworkType -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import androidx.work.WorkerParameters -import java.util.concurrent.TimeUnit - -class UpdateTrackersWorker(appContext: Context, workerParams: WorkerParameters) : - CoroutineWorker(appContext, workerParams) { - - override suspend fun doWork(): Result { - val trackersStateUseCase = (applicationContext as AdvancedPrivacyApplication) - .dependencyContainer.trackersStateUseCase - - trackersStateUseCase.updateTrackers() - return Result.success() - } - - companion object { - private val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - - fun periodicUpdate(context: Context) { - val request = PeriodicWorkRequestBuilder( - 7, TimeUnit.DAYS - ) - .setConstraints(constraints).build() - - WorkManager.getInstance(context).enqueueUniquePeriodicWork( - UpdateTrackersWorker::class.qualifiedName ?: "", - ExistingPeriodicWorkPolicy.KEEP, - request - ) - } - } -} diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/common/ToggleAppsAdapter.kt index d8ee8ea..8a0cc83 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/common/ToggleAppsAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/common/ToggleAppsAdapter.kt @@ -25,7 +25,7 @@ import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import foundation.e.advancedprivacy.R -import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription class ToggleAppsAdapter( private val itemsLayout: Int, diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt index 3f3f66c..1a83692 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/common/WarningDialog.kt @@ -27,7 +27,6 @@ import android.util.Log import android.view.View import android.widget.CheckBox import androidx.appcompat.app.AlertDialog -import foundation.e.advancedprivacy.AdvancedPrivacyApplication import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.domain.entities.MainFeatures import foundation.e.advancedprivacy.domain.entities.MainFeatures.FAKE_LOCATION @@ -38,6 +37,7 @@ import foundation.e.advancedprivacy.main.MainActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import org.koin.java.KoinJavaComponent.get class WarningDialog : Activity() { companion object { @@ -112,8 +112,7 @@ class WarningDialog : Activity() { } ) { _, _ -> if (checkbox.isChecked()) { - (application as AdvancedPrivacyApplication) - .dependencyContainer.showFeaturesWarningUseCase + get(ShowFeaturesWarningUseCase::class.java) .doNotShowAgain(feature) } finish() diff --git a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt deleted file mode 100644 index 2d7651d..0000000 --- a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2022 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 . - */ - -package foundation.e.advancedprivacy.data.repositories - -import android.Manifest -import android.content.Context -import android.content.Intent -import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo -import foundation.e.advancedprivacy.R -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.permissions.data.ProfileType -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking - -class AppListsRepository( - private val permissionsModule: PermissionsPrivacyModule, - private val context: Context, - private val coroutineScope: CoroutineScope -) { - companion object { - private const val PNAME_SETTINGS = "com.android.settings" - private const val PNAME_PWAPLAYER = "foundation.e.pwaplayer" - private const val PNAME_INTENT_VERIFICATION = "com.android.statementservice" - private const val PNAME_MICROG_SERVICES_CORE = "com.google.android.gms" - - val compatibiltyPNames = setOf( - PNAME_PWAPLAYER, PNAME_INTENT_VERIFICATION, PNAME_MICROG_SERVICES_CORE - ) - } - - val dummySystemApp = ApplicationDescription( - packageName = "foundation.e.dummysystemapp", - uid = -1, - label = context.getString(R.string.dummy_system_app_label), - icon = context.getDrawable(R.drawable.ic_e_app_logo), - profileId = -1, - profileType = ProfileType.MAIN - ) - - val dummyCompatibilityApp = ApplicationDescription( - packageName = "foundation.e.dummyappscompatibilityapp", - uid = -2, - label = context.getString(R.string.dummy_apps_compatibility_app_label), - icon = context.getDrawable(R.drawable.ic_apps_compatibility_components), - profileId = -1, - profileType = ProfileType.MAIN - ) - - private suspend fun fetchAppDescriptions(fetchMissingIcons: Boolean = false) { - val launcherPackageNames = context.packageManager.queryIntentActivities( - Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) }, - 0 - ).mapNotNull { it.activityInfo?.packageName } - - val visibleAppsFilter = { packageInfo: PackageInfo -> - hasInternetPermission(packageInfo) && - isStandardApp(packageInfo.applicationInfo, launcherPackageNames) - } - - val hiddenAppsFilter = { packageInfo: PackageInfo -> - hasInternetPermission(packageInfo) && - isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames) - } - - val compatibilityAppsFilter = { packageInfo: PackageInfo -> - packageInfo.packageName in compatibiltyPNames - } - - val visibleApps = recycleIcons( - newApps = permissionsModule.getApplications(visibleAppsFilter), - fetchMissingIcons = fetchMissingIcons - ) - val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter) - val compatibilityApps = permissionsModule.getApplications(compatibilityAppsFilter) - - updateMaps(visibleApps + hiddenApps + compatibilityApps) - - allProfilesAppDescriptions.emit( - Triple( - visibleApps + dummySystemApp + dummyCompatibilityApp, - hiddenApps, - compatibilityApps - ) - ) - } - - private fun recycleIcons( - newApps: List, - fetchMissingIcons: Boolean - ): List { - val oldVisibleApps = allProfilesAppDescriptions.value.first - return newApps.map { app -> - app.copy( - icon = oldVisibleApps.find { app.apId == it.apId }?.icon - ?: if (fetchMissingIcons) permissionsModule.getApplicationIcon(app) else null - ) - } - } - - private fun updateMaps(apps: List) { - val byUid = mutableMapOf() - val byApId = mutableMapOf() - apps.forEach { app -> - byUid[app.uid]?.run { packageName > app.packageName } == true - if (byUid[app.uid].let { it == null || it.packageName > app.packageName }) { - byUid[app.uid] = app - } - - byApId[app.apId] = app - } - appsByUid = byUid - appsByAPId = byApId - } - - private var lastFetchApps = 0 - private var refreshAppJob: Job? = null - private fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? { - if (refreshAppJob == null || refreshAppJob?.isCompleted == true) { - refreshAppJob = coroutineScope.launch(Dispatchers.IO) { - if (appsByUid.isEmpty() || appsByAPId.isEmpty() || - force || context.packageManager.getChangedPackages(lastFetchApps) != null - ) { - fetchAppDescriptions(fetchMissingIcons = fetchMissingIcons) - if (fetchMissingIcons) { - lastFetchApps = context.packageManager.getChangedPackages(lastFetchApps) - ?.sequenceNumber ?: lastFetchApps - } - } - } - } - - return refreshAppJob - } - - fun mainProfileApps(): Flow> { - refreshAppDescriptions() - return allProfilesAppDescriptions.map { - it.first.filter { app -> app.profileType == ProfileType.MAIN } - .sortedBy { app -> app.label.toString().lowercase() } - } - } - - fun getMainProfileHiddenSystemApps(): List { - return allProfilesAppDescriptions.value.second.filter { it.profileType == ProfileType.MAIN } - } - - fun apps(): Flow> { - refreshAppDescriptions() - return allProfilesAppDescriptions.map { - it.first.sortedBy { app -> app.label.toString().lowercase() } - } - } - - fun allApps(): Flow> { - return allProfilesAppDescriptions.map { - it.first + it.second + it.third - } - } - - private fun getHiddenSystemApps(): List { - return allProfilesAppDescriptions.value.second - } - - private fun getCompatibilityApps(): List { - return allProfilesAppDescriptions.value.third - } - - fun anyForHiddenApps(app: ApplicationDescription, test: (ApplicationDescription) -> Boolean): Boolean { - return if (app == dummySystemApp) { - getHiddenSystemApps().any { test(it) } - } else if (app == dummyCompatibilityApp) { - getCompatibilityApps().any { test(it) } - } else test(app) - } - - fun applyForHiddenApps(app: ApplicationDescription, action: (ApplicationDescription) -> Unit) { - mapReduceForHiddenApps(app = app, map = action, reduce = {}) - } - - fun mapReduceForHiddenApps( - app: ApplicationDescription, - map: (ApplicationDescription) -> T, - reduce: (List) -> R - ): R { - return if (app == dummySystemApp) { - reduce(getHiddenSystemApps().map(map)) - } else if (app == dummyCompatibilityApp) { - reduce(getCompatibilityApps().map(map)) - } else reduce(listOf(map(app))) - } - - private var appsByUid = mapOf() - private var appsByAPId = mapOf() - - fun getApp(appUid: Int): ApplicationDescription? { - return appsByUid[appUid] ?: run { - runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() } - appsByUid[appUid] - } - } - - fun getApp(apId: String): ApplicationDescription? { - if (apId.isBlank()) return null - - return appsByAPId[apId] ?: run { - runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() } - appsByAPId[apId] - } - } - - private val allProfilesAppDescriptions = MutableStateFlow( - Triple( - emptyList(), - emptyList(), - emptyList() - ) - ) - - private fun hasInternetPermission(packageInfo: PackageInfo): Boolean { - return packageInfo.requestedPermissions?.contains(Manifest.permission.INTERNET) == true - } - - @Suppress("ReturnCount") - private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List): Boolean { - if (app.packageName == PNAME_SETTINGS) { - return false - } else if (app.packageName == PNAME_PWAPLAYER) { - return true - } else if (app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { - return true - } else if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) { - return true - } else if (launcherApps.contains(app.packageName)) { - return true - } - return false - } - - private fun isStandardApp(app: ApplicationInfo, launcherApps: List): Boolean { - return when { - app.packageName == PNAME_SETTINGS -> false - app.packageName in compatibiltyPNames -> false - app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) -> true - !app.hasFlag(ApplicationInfo.FLAG_SYSTEM) -> true - launcherApps.contains(app.packageName) -> true - else -> false - } - } - - private fun isHiddenSystemApp(app: ApplicationInfo, launcherApps: List): Boolean { - return when { - app.packageName in compatibiltyPNames -> false - else -> !isNotHiddenSystemApp(app, launcherApps) - } - } - - private fun ApplicationInfo.hasFlag(flag: Int) = (flags and flag) == 1 -} diff --git a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt index 3f73c78..ba2836f 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/LocalStateRepository.kt @@ -18,9 +18,9 @@ package foundation.e.advancedprivacy.data.repositories import android.content.Context +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode import foundation.e.advancedprivacy.domain.entities.LocationMode -import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow diff --git a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/TrackersRepository.kt b/app/src/main/java/foundation/e/advancedprivacy/data/repositories/TrackersRepository.kt deleted file mode 100644 index 568d76b..0000000 --- a/app/src/main/java/foundation/e/advancedprivacy/data/repositories/TrackersRepository.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.advancedprivacy.data.repositories - -import android.content.Context -import com.google.gson.Gson -import foundation.e.privacymodules.trackers.api.Tracker -import retrofit2.Retrofit -import retrofit2.converter.scalars.ScalarsConverterFactory -import retrofit2.http.GET -import timber.log.Timber -import java.io.File -import java.io.FileInputStream -import java.io.FileWriter -import java.io.IOException -import java.io.InputStreamReader -import java.io.PrintWriter - -class TrackersRepository(private val context: Context) { - - private val eTrackerFileName = "e_trackers.json" - private val eTrackerFile = File(context.filesDir.absolutePath, eTrackerFileName) - - var trackers: List = emptyList() - private set - - init { - initTrackersFile() - } - - suspend fun update() { - val api = ETrackersApi.build() - try { - saveData(eTrackerFile, api.trackers()) - initTrackersFile() - } catch (e: Exception) { - Timber.e("While updating trackers", e) - } - } - - private fun initTrackersFile() { - try { - var inputStream = context.assets.open(eTrackerFileName) - if (eTrackerFile.exists()) { - inputStream = FileInputStream(eTrackerFile) - } - val reader = InputStreamReader(inputStream, "UTF-8") - val trackerResponse = - Gson().fromJson(reader, ETrackersApi.ETrackersResponse::class.java) - - trackers = mapper(trackerResponse) - - reader.close() - inputStream.close() - } catch (e: Exception) { - Timber.e("While parsing trackers in assets", e) - } - } - - private fun mapper(response: ETrackersApi.ETrackersResponse): List { - return response.trackers.mapNotNull { - try { - it.toTracker() - } catch (e: Exception) { - null - } - } - } - - private fun ETrackersApi.ETrackersResponse.ETracker.toTracker(): Tracker { - return Tracker( - id = id!!, - hostnames = hostnames!!.toSet(), - label = name!!, - exodusId = exodusId - ) - } - - private fun saveData(file: File, data: String): Boolean { - try { - val fos = FileWriter(file, false) - val ps = PrintWriter(fos) - ps.apply { - print(data) - flush() - close() - } - return true - } catch (e: IOException) { - e.printStackTrace() - } - return false - } -} - -interface ETrackersApi { - companion object { - fun build(): ETrackersApi { - val retrofit = Retrofit.Builder() - .baseUrl("https://gitlab.e.foundation/e/os/tracker-list/-/raw/main/") - .addConverterFactory(ScalarsConverterFactory.create()) - .build() - return retrofit.create(ETrackersApi::class.java) - } - } - - @GET("list/e_trackers.json") - suspend fun trackers(): String - - data class ETrackersResponse(val trackers: List) { - data class ETracker( - val id: String?, - val hostnames: List?, - val name: String?, - val exodusId: String? - ) - } -} 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 index 4169ecc..344e689 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/AppWithCounts.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/AppWithCounts.kt @@ -19,7 +19,6 @@ package foundation.e.advancedprivacy.domain.entities import android.graphics.drawable.Drawable -import foundation.e.privacymodules.permissions.data.ApplicationDescription data class AppWithCounts( val appDesc: ApplicationDescription, 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 index 8d38ee8..dfd32b6 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppListUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppListUseCase.kt @@ -18,7 +18,7 @@ package foundation.e.advancedprivacy.domain.usecases import foundation.e.advancedprivacy.data.repositories.AppListsRepository -import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import kotlinx.coroutines.flow.Flow class AppListUseCase( diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt index 8831fff..30c8e6b 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/FakeLocationStateUseCase.kt @@ -26,12 +26,12 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +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.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 foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule +import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -41,8 +41,8 @@ import timber.log.Timber import kotlin.random.Random class FakeLocationStateUseCase( - private val fakeLocationModule: IFakeLocationModule, - private val permissionsModule: PermissionsPrivacyModule, + private val fakeLocationModule: FakeLocationModule, + private val permissionsModule: IPermissionsPrivacyModule, private val localStateRepository: LocalStateRepository, private val citiesRepository: CityDataSource, private val appDesc: ApplicationDescription, diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt index b82918e..bc4871a 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/GetQuickPrivacyStateUseCase.kt @@ -18,11 +18,11 @@ package foundation.e.advancedprivacy.domain.usecases import foundation.e.advancedprivacy.data.repositories.LocalStateRepository +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription 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 @@ -30,7 +30,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map class GetQuickPrivacyStateUseCase( - private val localStateRepository: LocalStateRepository + private val localStateRepository: LocalStateRepository, ) { val quickPrivacyState: Flow = combine( localStateRepository.blockTrackers, 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 index 70607cf..a7ed660 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/IpScramblingStateUseCase.kt @@ -19,14 +19,14 @@ 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.ApplicationDescription 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.ipscrambler.IIpScramblerModule -import foundation.e.privacymodules.permissions.IPermissionsPrivacyModule -import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule +import foundation.e.advancedprivacy.ipscrambler.IpScramblerModule import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose @@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch class IpScramblingStateUseCase( - private val ipScramblerModule: IIpScramblerModule, + private val ipScramblerModule: IpScramblerModule, private val permissionsPrivacyModule: IPermissionsPrivacyModule, private val appDesc: ApplicationDescription, private val localStateRepository: LocalStateRepository, @@ -46,8 +46,8 @@ class IpScramblingStateUseCase( private val coroutineScope: CoroutineScope ) { val internetPrivacyMode: StateFlow = callbackFlow { - val listener = object : IIpScramblerModule.Listener { - override fun onStatusChanged(newStatus: IIpScramblerModule.Status) { + val listener = object : IpScramblerModule.Listener { + override fun onStatusChanged(newStatus: IpScramblerModule.Status) { trySend(map(newStatus)) } @@ -169,13 +169,13 @@ class IpScramblingStateUseCase( } } - private fun map(status: IIpScramblerModule.Status): InternetPrivacyMode { + private fun map(status: IpScramblerModule.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 + IpScramblerModule.Status.OFF -> REAL_IP + IpScramblerModule.Status.ON -> HIDE_IP + IpScramblerModule.Status.STARTING -> HIDE_IP_LOADING + IpScramblerModule.Status.STOPPING, + IpScramblerModule.Status.START_DISABLED -> REAL_IP_LOADING } } } 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 index 882d53f..ed15a41 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStateUseCase.kt @@ -19,87 +19,68 @@ 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 foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.trackers.data.WhitelistRepository +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class TrackersStateUseCase( - private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule, - private val trackersPrivacyModule: ITrackTrackersPrivacyModule, + private val whitelistRepository: WhitelistRepository, private val localStateRepository: LocalStateRepository, - private val trackersRepository: TrackersRepository, private val appListsRepository: AppListsRepository, - private val coroutineScope: CoroutineScope + 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() - } + whitelistRepository.isBlockingEnabled = enabled updateAllTrackersBlockedState() } } } private fun updateAllTrackersBlockedState() { - localStateRepository.areAllTrackersBlocked.value = blockTrackersPrivacyModule.isBlockingEnabled() && - blockTrackersPrivacyModule.isWhiteListEmpty() + localStateRepository.areAllTrackersBlocked.value = whitelistRepository.isBlockingEnabled && + whitelistRepository.areWhiteListEmpty() } fun isWhitelisted(app: ApplicationDescription): Boolean { - return isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule) + return isWhitelisted(app, appListsRepository, whitelistRepository) } fun toggleAppWhitelist(app: ApplicationDescription, isWhitelisted: Boolean) { appListsRepository.applyForHiddenApps(app) { - blockTrackersPrivacyModule.setWhiteListed(it, isWhitelisted) + whitelistRepository.setWhiteListed(it.apId, isWhitelisted) } updateAllTrackersBlockedState() } fun blockTracker(app: ApplicationDescription, tracker: Tracker, isBlocked: Boolean) { appListsRepository.applyForHiddenApps(app) { - blockTrackersPrivacyModule.setWhiteListed(tracker, it, !isBlocked) + whitelistRepository.setWhiteListed(tracker, it.apId, !isBlocked) } updateAllTrackersBlockedState() } fun clearWhitelist(app: ApplicationDescription) { appListsRepository.applyForHiddenApps( - app, - blockTrackersPrivacyModule::clearWhiteList - ) + app + ) { + whitelistRepository.clearWhiteList(it.apId) + } 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 + whitelistRepository: WhitelistRepository + ): Boolean { - return appListsRepository.anyForHiddenApps(app, blockTrackersPrivacyModule::isWhitelisted) + return appListsRepository.anyForHiddenApps( + app, + whitelistRepository::isAppWhiteListed + ) } 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 index 43e4496..b0c9f39 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt @@ -22,15 +22,14 @@ 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.ApplicationDescription 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 foundation.e.advancedprivacy.trackers.data.TrackersRepository +import foundation.e.advancedprivacy.trackers.data.WhitelistRepository +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker +import foundation.e.advancedprivacy.trackers.domain.usecases.StatisticsUseCase 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 @@ -41,8 +40,9 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds class TrackersStatisticsUseCase( - private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, - private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule, + private val statisticsUseCase: StatisticsUseCase, + private val whitelistRepository: WhitelistRepository, + private val trackersRepository: TrackersRepository, private val appListsRepository: AppListsRepository, private val resources: Resources ) { @@ -50,54 +50,45 @@ class TrackersStatisticsUseCase( appListsRepository.apps() } - private fun rawUpdates(): Flow = 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 listenUpdates(debounce: Duration = 1.seconds) = + statisticsUseCase.newDataAvailable + .throttleFirst(windowDuration = debounce) + .onStart { emit(Unit) } fun getDayStatistics(): Pair { return TrackersPeriodicStatistics( - callsBlockedNLeaked = trackTrackersPrivacyModule.getPastDayTrackersCalls(), + callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS), periods = buildDayLabels(), - trackersCount = trackTrackersPrivacyModule.getPastDayTrackersCount(), + trackersCount = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS), graduations = buildDayGraduations(), - ) to trackTrackersPrivacyModule.getTrackersCount() + ) to statisticsUseCase.getContactedTrackersCount() } fun getNonBlockedTrackersCount(): Flow { - return if (blockTrackersPrivacyModule.isBlockingEnabled()) + return if (whitelistRepository.isBlockingEnabled) appListsRepository.allApps().map { apps -> val whiteListedTrackers = mutableSetOf() - val whiteListedApps = blockTrackersPrivacyModule.getWhiteListedApp() + val whiteListedApps = whitelistRepository.getWhiteListedApp() apps.forEach { app -> if (app in whiteListedApps) { - whiteListedTrackers.addAll(trackTrackersPrivacyModule.getTrackersForApp(app)) + whiteListedTrackers.addAll(statisticsUseCase.getTrackers(listOf(app))) } else { - whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app)) + whiteListedTrackers.addAll(getWhiteList(app)) } } whiteListedTrackers.size } - else flowOf(trackTrackersPrivacyModule.getTrackersCount()) + else flowOf(statisticsUseCase.getContactedTrackersCount()) } fun getMostLeakedApp(): ApplicationDescription? { - return trackTrackersPrivacyModule.getPastDayMostLeakedApp() + return statisticsUseCase.getMostLeakedApp(24, ChronoUnit.HOURS) } - fun getDayTrackersCalls() = trackTrackersPrivacyModule.getPastDayTrackersCalls() + fun getDayTrackersCalls() = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS) - fun getDayTrackersCount() = trackTrackersPrivacyModule.getPastDayTrackersCount() + fun getDayTrackersCount() = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) private fun buildDayGraduations(): List { val formatter = DateTimeFormatter.ofPattern( @@ -155,25 +146,23 @@ class TrackersStatisticsUseCase( } fun getDayMonthYearStatistics(): Triple { - 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() - ) + return Triple( + TrackersPeriodicStatistics( + callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS), + periods = buildDayLabels(), + trackersCount = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) + ), + TrackersPeriodicStatistics( + callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS), + periods = buildMonthLabels(), + trackersCount = statisticsUseCase.getActiveTrackersByPeriod(30, ChronoUnit.DAYS) + ), + TrackersPeriodicStatistics( + callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS), + periods = buildYearLabels(), + trackersCount = statisticsUseCase.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS) ) - } + ) } fun getTrackersWithWhiteList(app: ApplicationDescription): List> { @@ -181,8 +170,8 @@ class TrackersStatisticsUseCase( app = app, map = { appDesc: ApplicationDescription -> ( - trackTrackersPrivacyModule.getTrackersForApp(appDesc) to - blockTrackersPrivacyModule.getWhiteList(appDesc) + statisticsUseCase.getTrackers(listOf(appDesc)) to + getWhiteList(appDesc) ) }, reduce = { lists -> @@ -200,7 +189,7 @@ class TrackersStatisticsUseCase( return appListsRepository.mapReduceForHiddenApps( app = app, map = { appDesc: ApplicationDescription -> - blockTrackersPrivacyModule.getWhiteList(appDesc).isEmpty() + getWhiteList(appDesc).isEmpty() }, reduce = { areEmpty -> areEmpty.all { it } } ) @@ -209,7 +198,9 @@ class TrackersStatisticsUseCase( fun getCalls(app: ApplicationDescription): Pair { return appListsRepository.mapReduceForHiddenApps( app = app, - map = trackTrackersPrivacyModule::getPastDayTrackersCallsForApp, + map = { + statisticsUseCase.getCalls(it, 24, ChronoUnit.HOURS) + }, reduce = { zip -> zip.unzip().let { (blocked, leaked) -> blocked.sum() to leaked.sum() @@ -219,7 +210,7 @@ class TrackersStatisticsUseCase( } fun getAppsWithCounts(): Flow> { - val trackersCounts = trackTrackersPrivacyModule.getTrackersCountByApp() + val trackersCounts = statisticsUseCase.getContactedTrackersCountByApp() val hiddenAppsTrackersWithWhiteList = getTrackersWithWhiteList(appListsRepository.dummySystemApp) val acAppsTrackersWithWhiteList = @@ -227,7 +218,7 @@ class TrackersStatisticsUseCase( return appListsRepository.apps() .map { apps -> - val callsByApp = trackTrackersPrivacyModule.getPastDayTrackersCallsByApps() + val callsByApp = statisticsUseCase.getCallsByApps(24, ChronoUnit.HOURS) apps.map { app -> val calls = appListsRepository.mapReduceForHiddenApps( app = app, @@ -241,8 +232,8 @@ class TrackersStatisticsUseCase( AppWithCounts( app = app, - isWhitelisted = !blockTrackersPrivacyModule.isBlockingEnabled() || - isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule), + isWhitelisted = !whitelistRepository.isBlockingEnabled || + isWhitelisted(app, appListsRepository, whitelistRepository), trackersCount = when (app) { appListsRepository.dummySystemApp -> hiddenAppsTrackersWithWhiteList.size @@ -256,7 +247,7 @@ class TrackersStatisticsUseCase( appListsRepository.dummyCompatibilityApp -> acAppsTrackersWithWhiteList.count { it.second } else -> - blockTrackersPrivacyModule.getWhiteList(app).size + getWhiteList(app).size }, blockedLeaks = calls.first, leaks = calls.second @@ -266,6 +257,12 @@ class TrackersStatisticsUseCase( } } + private fun getWhiteList(app: ApplicationDescription): List { + return whitelistRepository.getWhiteListForApp(app).mapNotNull { + trackersRepository.getTracker(it) + } + } + private val mostLeakedAppsComparator: Comparator = Comparator { o1, o2 -> val leaks = o2.leaks - o1.leaks if (leaks != 0) leaks else { 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 deleted file mode 100644 index 94c734c..0000000 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/UpdateWidgetUseCase.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 . - */ - -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() { - } - }) - } -} diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt index 6ca9792..5eb0bb6 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt @@ -25,14 +25,11 @@ import android.view.View import android.widget.Toast import androidx.core.content.ContextCompat.getColor import androidx.core.view.isVisible -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import foundation.e.advancedprivacy.AdvancedPrivacyApplication -import foundation.e.advancedprivacy.DependencyContainer import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.common.GraphHolder import foundation.e.advancedprivacy.common.NavToolbarFragment @@ -44,15 +41,10 @@ import foundation.e.advancedprivacy.domain.entities.TrackerMode import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.Action import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.SingleEvent import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { - private val dependencyContainer: DependencyContainer by lazy { - (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer - } - - private val viewModel: DashboardViewModel by viewModels { - dependencyContainer.viewModelsFactory - } + private val viewModel: DashboardViewModel by viewModel() private var graphHolder: GraphHolder? = null diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt index 35fc1d4..1180af3 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyFragment.kt @@ -23,13 +23,10 @@ import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Toast -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager -import foundation.e.advancedprivacy.AdvancedPrivacyApplication -import foundation.e.advancedprivacy.DependencyContainer import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.common.NavToolbarFragment import foundation.e.advancedprivacy.common.ToggleAppsAdapter @@ -37,17 +34,12 @@ import foundation.e.advancedprivacy.common.setToolTipForAsterisk import foundation.e.advancedprivacy.databinding.FragmentInternetActivityPolicyBinding import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel import java.util.Locale class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_activity_policy) { - private val dependencyContainer: DependencyContainer by lazy { - (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer - } - - private val viewModel: InternetPrivacyViewModel by viewModels { - dependencyContainer.viewModelsFactory - } + private val viewModel: InternetPrivacyViewModel by viewModel() private var _binding: FragmentInternetActivityPolicyBinding? = null private val binding get() = _binding!! diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyState.kt index e0df73b..4d0fb38 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyState.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyState.kt @@ -17,8 +17,8 @@ package foundation.e.advancedprivacy.features.internetprivacy +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode -import foundation.e.privacymodules.permissions.data.ApplicationDescription data class InternetPrivacyState( val mode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP, diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt index 4c707a2..80e00bc 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/InternetPrivacyViewModel.kt @@ -25,7 +25,7 @@ import foundation.e.advancedprivacy.domain.entities.InternetPrivacyMode import foundation.e.advancedprivacy.domain.usecases.AppListUseCase import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.IpScramblingStateUseCase -import foundation.e.privacymodules.ipscrambler.IIpScramblerModule +import foundation.e.advancedprivacy.ipscrambler.IpScramblerModule import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow @@ -40,7 +40,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class InternetPrivacyViewModel( - private val ipScramblerModule: IIpScramblerModule, + private val ipScramblerModule: IpScramblerModule, private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, private val ipScramblingStateUseCase: IpScramblingStateUseCase, private val appListUseCase: AppListUseCase diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt index 7d18930..1c629c2 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/location/FakeLocationFragment.kt @@ -31,7 +31,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.NonNull import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -50,8 +49,6 @@ import com.mapbox.mapboxsdk.location.modes.CameraMode import com.mapbox.mapboxsdk.location.modes.RenderMode import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.Style -import foundation.e.advancedprivacy.AdvancedPrivacyApplication -import foundation.e.advancedprivacy.DependencyContainer import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.common.NavToolbarFragment import foundation.e.advancedprivacy.databinding.FragmentFakeLocationBinding @@ -60,19 +57,14 @@ import foundation.e.advancedprivacy.features.location.FakeLocationViewModel.Acti import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location) { private var isFirstLaunch: Boolean = true - private val dependencyContainer: DependencyContainer by lazy { - (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer - } - - private val viewModel: FakeLocationViewModel by viewModels { - dependencyContainer.viewModelsFactory - } + private val viewModel: FakeLocationViewModel by viewModel() private var _binding: FragmentFakeLocationBinding? = null private val binding get() = _binding!! diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt index f486114..132fa3b 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersFragment.kt @@ -31,14 +31,11 @@ import android.view.View import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.view.isVisible -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager -import foundation.e.advancedprivacy.AdvancedPrivacyApplication -import foundation.e.advancedprivacy.DependencyContainer import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.common.AppsAdapter import foundation.e.advancedprivacy.common.GraphHolder @@ -48,14 +45,10 @@ import foundation.e.advancedprivacy.databinding.FragmentTrackersBinding import foundation.e.advancedprivacy.databinding.TrackersItemGraphBinding import foundation.e.advancedprivacy.domain.entities.TrackersPeriodicStatistics import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel class TrackersFragment : NavToolbarFragment(R.layout.fragment_trackers) { - - private val dependencyContainer: DependencyContainer by lazy { - (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer - } - - private val viewModel: TrackersViewModel by viewModels { dependencyContainer.viewModelsFactory } + private val viewModel: TrackersViewModel by viewModel() private var _binding: FragmentTrackersBinding? = null private val binding get() = _binding!! diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt index 457a02a..7fb9ca6 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersFragment.kt @@ -24,27 +24,23 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.core.view.isVisible -import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar -import foundation.e.advancedprivacy.AdvancedPrivacyApplication -import foundation.e.advancedprivacy.DependencyContainer import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.common.NavToolbarFragment import foundation.e.advancedprivacy.databinding.ApptrackersFragmentBinding import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { - private val dependencyContainer: DependencyContainer by lazy { - (this.requireActivity().application as AdvancedPrivacyApplication).dependencyContainer - } - private val viewModel: AppTrackersViewModel by viewModels { - dependencyContainer.viewModelsFactory - } + private val args: AppTrackersFragmentArgs by navArgs() + private val viewModel: AppTrackersViewModel by viewModel { parametersOf(args.appUid) } private var _binding: ApptrackersFragmentBinding? = null private val binding get() = _binding!! diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersState.kt index 2a9e6e8..a597da6 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersState.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersState.kt @@ -18,8 +18,8 @@ package foundation.e.advancedprivacy.features.trackers.apptrackers -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.Tracker +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker data class AppTrackersState( val appDesc: ApplicationDescription? = null, diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersViewModel.kt index cda4b4b..8740779 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/AppTrackersViewModel.kt @@ -22,12 +22,12 @@ import android.net.Uri import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import foundation.e.advancedprivacy.domain.entities.TrackerMode import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.Tracker +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/ToggleTrackersAdapter.kt index 3696939..ef845b6 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/ToggleTrackersAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/apptrackers/ToggleTrackersAdapter.kt @@ -27,7 +27,7 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import foundation.e.advancedprivacy.R -import foundation.e.privacymodules.trackers.api.Tracker +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker class ToggleTrackersAdapter( private val itemsLayout: Int, diff --git a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetCommandReceiver.kt b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetCommandReceiver.kt index 9021125..917cbda 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetCommandReceiver.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetCommandReceiver.kt @@ -20,11 +20,12 @@ package foundation.e.advancedprivacy.widget import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import foundation.e.advancedprivacy.AdvancedPrivacyApplication +import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase +import org.koin.java.KoinJavaComponent.get class WidgetCommandReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - val getQuickPrivacyStateUseCase = (context?.applicationContext as? AdvancedPrivacyApplication)?.dependencyContainer?.getQuickPrivacyStateUseCase + val getQuickPrivacyStateUseCase = get(GetQuickPrivacyStateUseCase::class.java) val featureEnabled = intent?.extras?.let { bundle -> if (bundle.containsKey(PARAM_FEATURE_ENABLED)) @@ -32,9 +33,9 @@ class WidgetCommandReceiver : BroadcastReceiver() { else null } when (intent?.action) { - ACTION_TOGGLE_TRACKERS -> getQuickPrivacyStateUseCase?.toggleTrackers(featureEnabled) - ACTION_TOGGLE_LOCATION -> getQuickPrivacyStateUseCase?.toggleLocation(featureEnabled) - ACTION_TOGGLE_IPSCRAMBLING -> getQuickPrivacyStateUseCase?.toggleIpScrambling(featureEnabled) + ACTION_TOGGLE_TRACKERS -> getQuickPrivacyStateUseCase.toggleTrackers(featureEnabled) + ACTION_TOGGLE_LOCATION -> getQuickPrivacyStateUseCase.toggleLocation(featureEnabled) + ACTION_TOGGLE_IPSCRAMBLING -> getQuickPrivacyStateUseCase.toggleIpScrambling(featureEnabled) else -> {} } } diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000..0e53f22 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +group 'foundation.e' + +android { + compileSdkVersion buildConfig.compileSdk + + defaultConfig { + minSdkVersion buildConfig.minSdk + targetSdkVersion buildConfig.targetSdk + + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation( + libs.androidx.core.ktx, + libs.bundles.koin, + libs.kotlinx.coroutines + ) +} diff --git a/core/consumer-rules.pro b/core/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/core/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a29e84c --- /dev/null +++ b/core/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/core/src/main/java/foundation/e/advancedprivacy/core/KoinModule.kt b/core/src/main/java/foundation/e/advancedprivacy/core/KoinModule.kt new file mode 100644 index 0000000..141da86 --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/core/KoinModule.kt @@ -0,0 +1,23 @@ +package foundation.e.advancedprivacy.core + +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import org.koin.android.ext.koin.androidContext +import org.koin.core.qualifier.named +import org.koin.dsl.module + +@OptIn(DelicateCoroutinesApi::class) +val coreModule = module { + single { GlobalScope } + single { + AppListsRepository( + permissionsModule = get(), + dummySystemApp = get(named("DummySystemApp")), + dummyCompatibilityApp = get(named("DummyCompatibilityApp")), + context = androidContext(), + coroutineScope = get() + ) + } +} diff --git a/core/src/main/java/foundation/e/advancedprivacy/core/utils/CoroutinesUtils.kt b/core/src/main/java/foundation/e/advancedprivacy/core/utils/CoroutinesUtils.kt new file mode 100644 index 0000000..5344c6a --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/core/utils/CoroutinesUtils.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 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 . + */ + +package foundation.e.advancedprivacy.core.utils + +import kotlinx.coroutines.CancellationException +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +inline fun runSuspendCatching(block: () -> T): Result { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + + return runCatching(block).onFailure { + if (it is CancellationException) { + throw it + } + } +} + +inline fun Result.recoverSuspendCatching( + transform: (exception: Throwable) -> R +): Result { + return when (val exception = exceptionOrNull()) { + null -> this + else -> runSuspendCatching { transform(exception) } + } +} diff --git a/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt b/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt new file mode 100644 index 0000000..f29bb8a --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2022 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 . + */ + +package foundation.e.advancedprivacy.data.repositories + +import android.Manifest +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.ProfileType +import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +class AppListsRepository( + private val permissionsModule: IPermissionsPrivacyModule, + val dummySystemApp: ApplicationDescription, + val dummyCompatibilityApp: ApplicationDescription, + private val context: Context, + private val coroutineScope: CoroutineScope +) { + companion object { + private const val PNAME_SETTINGS = "com.android.settings" + private const val PNAME_PWAPLAYER = "foundation.e.pwaplayer" + private const val PNAME_INTENT_VERIFICATION = "com.android.statementservice" + private const val PNAME_MICROG_SERVICES_CORE = "com.google.android.gms" + + val compatibiltyPNames = setOf( + PNAME_PWAPLAYER, PNAME_INTENT_VERIFICATION, PNAME_MICROG_SERVICES_CORE + ) + } + + private suspend fun fetchAppDescriptions(fetchMissingIcons: Boolean = false) { + val launcherPackageNames = context.packageManager.queryIntentActivities( + Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) }, + 0 + ).mapNotNull { it.activityInfo?.packageName } + + val visibleAppsFilter = { packageInfo: PackageInfo -> + hasInternetPermission(packageInfo) && + isStandardApp(packageInfo.applicationInfo, launcherPackageNames) + } + + val hiddenAppsFilter = { packageInfo: PackageInfo -> + hasInternetPermission(packageInfo) && + isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames) + } + + val compatibilityAppsFilter = { packageInfo: PackageInfo -> + packageInfo.packageName in compatibiltyPNames + } + + val visibleApps = recycleIcons( + newApps = permissionsModule.getApplications(visibleAppsFilter), + fetchMissingIcons = fetchMissingIcons + ) + val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter) + val compatibilityApps = permissionsModule.getApplications(compatibilityAppsFilter) + + updateMaps(visibleApps + hiddenApps + compatibilityApps) + + allProfilesAppDescriptions.emit( + Triple( + visibleApps + dummySystemApp + dummyCompatibilityApp, + hiddenApps, + compatibilityApps + ) + ) + } + + private fun recycleIcons( + newApps: List, + fetchMissingIcons: Boolean + ): List { + val oldVisibleApps = allProfilesAppDescriptions.value.first + return newApps.map { app -> + app.copy( + icon = oldVisibleApps.find { app.apId == it.apId }?.icon + ?: if (fetchMissingIcons) permissionsModule.getApplicationIcon(app) else null + ) + } + } + + private fun updateMaps(apps: List) { + val byUid = mutableMapOf() + val byApId = mutableMapOf() + apps.forEach { app -> + byUid[app.uid]?.run { packageName > app.packageName } == true + if (byUid[app.uid].let { it == null || it.packageName > app.packageName }) { + byUid[app.uid] = app + } + + byApId[app.apId] = app + } + appsByUid = byUid + appsByAPId = byApId + } + + private var lastFetchApps = 0 + private var refreshAppJob: Job? = null + private fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? { + if (refreshAppJob == null || refreshAppJob?.isCompleted == true) { + refreshAppJob = coroutineScope.launch(Dispatchers.IO) { + if (appsByUid.isEmpty() || appsByAPId.isEmpty() || + force || context.packageManager.getChangedPackages(lastFetchApps) != null + ) { + fetchAppDescriptions(fetchMissingIcons = fetchMissingIcons) + if (fetchMissingIcons) { + lastFetchApps = context.packageManager.getChangedPackages(lastFetchApps) + ?.sequenceNumber ?: lastFetchApps + } + } + } + } + + return refreshAppJob + } + + fun mainProfileApps(): Flow> { + refreshAppDescriptions() + return allProfilesAppDescriptions.map { + it.first.filter { app -> app.profileType == ProfileType.MAIN } + .sortedBy { app -> app.label.toString().lowercase() } + } + } + + fun getMainProfileHiddenSystemApps(): List { + return allProfilesAppDescriptions.value.second.filter { it.profileType == ProfileType.MAIN } + } + + fun apps(): Flow> { + refreshAppDescriptions() + return allProfilesAppDescriptions.map { + it.first.sortedBy { app -> app.label.toString().lowercase() } + } + } + + fun allApps(): Flow> { + return allProfilesAppDescriptions.map { + it.first + it.second + it.third + } + } + + private fun getHiddenSystemApps(): List { + return allProfilesAppDescriptions.value.second + } + + private fun getCompatibilityApps(): List { + return allProfilesAppDescriptions.value.third + } + + fun anyForHiddenApps(app: ApplicationDescription, test: (ApplicationDescription) -> Boolean): Boolean { + return if (app == dummySystemApp) { + getHiddenSystemApps().any { test(it) } + } else if (app == dummyCompatibilityApp) { + getCompatibilityApps().any { test(it) } + } else test(app) + } + + fun applyForHiddenApps(app: ApplicationDescription, action: (ApplicationDescription) -> Unit) { + mapReduceForHiddenApps(app = app, map = action, reduce = {}) + } + + fun mapReduceForHiddenApps( + app: ApplicationDescription, + map: (ApplicationDescription) -> T, + reduce: (List) -> R + ): R { + return if (app == dummySystemApp) { + reduce(getHiddenSystemApps().map(map)) + } else if (app == dummyCompatibilityApp) { + reduce(getCompatibilityApps().map(map)) + } else reduce(listOf(map(app))) + } + + private var appsByUid = mapOf() + private var appsByAPId = mapOf() + + fun getApp(appUid: Int): ApplicationDescription? { + return appsByUid[appUid] ?: run { + runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() } + appsByUid[appUid] + } + } + + fun getApp(apId: String): ApplicationDescription? { + if (apId.isBlank()) return null + + return appsByAPId[apId] ?: run { + runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() } + appsByAPId[apId] + } + } + + private val allProfilesAppDescriptions = MutableStateFlow( + Triple( + emptyList(), + emptyList(), + emptyList() + ) + ) + + private fun hasInternetPermission(packageInfo: PackageInfo): Boolean { + return packageInfo.requestedPermissions?.contains(Manifest.permission.INTERNET) == true + } + + @Suppress("ReturnCount") + private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List): Boolean { + if (app.packageName == PNAME_SETTINGS) { + return false + } else if (app.packageName == PNAME_PWAPLAYER) { + return true + } else if (app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { + return true + } else if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) { + return true + } else if (launcherApps.contains(app.packageName)) { + return true + } + return false + } + + private fun isStandardApp(app: ApplicationInfo, launcherApps: List): Boolean { + return when { + app.packageName == PNAME_SETTINGS -> false + app.packageName in compatibiltyPNames -> false + app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) -> true + !app.hasFlag(ApplicationInfo.FLAG_SYSTEM) -> true + launcherApps.contains(app.packageName) -> true + else -> false + } + } + + private fun isHiddenSystemApp(app: ApplicationInfo, launcherApps: List): Boolean { + return when { + app.packageName in compatibiltyPNames -> false + else -> !isNotHiddenSystemApp(app, launcherApps) + } + } + + private fun ApplicationInfo.hasFlag(flag: Int) = (flags and flag) == 1 +} diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/AppOpModes.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/AppOpModes.kt new file mode 100644 index 0000000..3e0a261 --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/AppOpModes.kt @@ -0,0 +1,47 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.domain.entities + +import android.app.AppOpsManager.MODE_ALLOWED +import android.app.AppOpsManager.MODE_DEFAULT +import android.app.AppOpsManager.MODE_ERRORED +import android.app.AppOpsManager.MODE_FOREGROUND +import android.app.AppOpsManager.MODE_IGNORED +import android.os.Build + +enum class AppOpModes(val modeValue: Int) { + ALLOWED(MODE_ALLOWED), + IGNORED(MODE_IGNORED), + ERRORED(MODE_ERRORED), + DEFAULT(MODE_DEFAULT), + FOREGROUND(if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) MODE_ALLOWED else MODE_FOREGROUND); + + companion object { + private val byMode = mapOf( + FOREGROUND.modeValue to FOREGROUND, + ALLOWED.modeValue to ALLOWED, + IGNORED.modeValue to IGNORED, + ERRORED.modeValue to ERRORED, + DEFAULT.modeValue to DEFAULT, + ) + + fun getByModeValue(modeValue: Int): AppOpModes { + return byMode.get(modeValue) ?: DEFAULT + } + } +} diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/ApplicationDescription.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/ApplicationDescription.kt new file mode 100644 index 0000000..90b637f --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/ApplicationDescription.kt @@ -0,0 +1,50 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.domain.entities + +import android.graphics.drawable.Drawable + +/** + * Useful informations to identify and describe an application. + */ +data class ApplicationDescription( + val packageName: String, + val uid: Int, + val profileId: Int, + val profileType: ProfileType, + var label: CharSequence?, + var icon: Drawable? +) { + val profileFlag = when (profileType) { + ProfileType.MAIN -> PROFILE_FLAG_MAIN + ProfileType.WORK -> PROFILE_FLAG_WORK + else -> profileId + } + + val apId: String get() = "${profileFlag}_$packageName" + + companion object { + const val PROFILE_FLAG_MAIN = -1 + const val PROFILE_FLAG_WORK = -2 + } +} + +enum class ProfileType { + MAIN, WORK, OTHER +} diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/PermissionDescription.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/PermissionDescription.kt new file mode 100644 index 0000000..c3899a9 --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/PermissionDescription.kt @@ -0,0 +1,26 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.domain.entities + +data class PermissionDescription( + val name: String, + var isDangerous: Boolean, + val group: String?, + var label: CharSequence?, + var description: CharSequence? +) diff --git a/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/APermissionsPrivacyModule.kt b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/APermissionsPrivacyModule.kt new file mode 100644 index 0000000..78f424b --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/APermissionsPrivacyModule.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2022 - 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.externalinterfaces.permissions + +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PermissionInfo +import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.Log +import foundation.e.advancedprivacy.domain.entities.AppOpModes +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.PermissionDescription +import foundation.e.advancedprivacy.domain.entities.ProfileType + +/** + * Implementation of the commons functionality between privileged and standard + * versions of the module. + * @param context an Android context, to retrieve packageManager for example. + */ +abstract class APermissionsPrivacyModule(protected val context: Context) : IPermissionsPrivacyModule { + + companion object { + private const val TAG = "PermissionsModule" + } + + /** + * @see IPermissionsPrivacyModule.getInstalledApplications + */ + override fun getApplicationDescription(packageName: String, withIcon: Boolean): ApplicationDescription { + val appDesc = buildApplicationDescription(context.packageManager.getApplicationInfo(packageName, 0)) + if (withIcon) { + appDesc.icon = getApplicationIcon(appDesc.packageName) + } + return appDesc + } + + /** + * * @see IPermissionsPrivacyModule.getPermissions + */ + override fun getPermissions(packageName: String): List { + val packageInfo = context.packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) + return packageInfo.requestedPermissions?.asList() ?: emptyList() + } + + override fun getPermissionDescription(permissionName: String): PermissionDescription { + val info = context.packageManager.getPermissionInfo(permissionName, 0) + return PermissionDescription( + name = permissionName, + isDangerous = isPermissionsDangerous(info), + group = null, + label = info.loadLabel(context.packageManager), + description = info.loadDescription(context.packageManager) + ) + } + + /** + * @see IPermissionsPrivacyModule.isDangerousPermissionGranted + */ + override fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean { + return context.packageManager + .checkPermission(permissionName, packageName) == PackageManager.PERMISSION_GRANTED + } + + // on google version, work only for the current package. + @Suppress("DEPRECATION") + override fun getAppOpMode( + appDesc: ApplicationDescription, + appOpPermissionName: String + ): AppOpModes { + + val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + + val mode = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + appOps.checkOpNoThrow( + appOpPermissionName, + + appDesc.uid, appDesc.packageName + ) + } else { + appOps.unsafeCheckOpNoThrow( + appOpPermissionName, + appDesc.uid, appDesc.packageName + ) + } + + return AppOpModes.getByModeValue(mode) + } + + override fun isPermissionsDangerous(permissionName: String): Boolean { + try { + val permissionInfo = context.packageManager.getPermissionInfo(permissionName, 0) + return isPermissionsDangerous(permissionInfo) + } catch (e: Exception) { + Log.w(TAG, "exception in isPermissionsDangerous(String)", e) + return false + } + } + + @Suppress("DEPRECATION") + private fun isPermissionsDangerous(permissionInfo: PermissionInfo): Boolean { + try { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + permissionInfo.protectionLevel and PROTECTION_DANGEROUS == 1 + } else { + permissionInfo.protection == PROTECTION_DANGEROUS + } + } catch (e: Exception) { + Log.w(TAG, "exception in isPermissionsDangerous(PermissionInfo)", e) + return false + } + } + + override fun buildApplicationDescription( + appInfo: ApplicationInfo, + profileId: Int, + profileType: ProfileType + ): + ApplicationDescription { + return ApplicationDescription( + packageName = appInfo.packageName, + uid = appInfo.uid, + label = getAppLabel(appInfo), + icon = null, + profileId = profileId, + profileType = profileType, + ) + } + + private fun getAppLabel(appInfo: ApplicationInfo): CharSequence { + return context.packageManager.getApplicationLabel(appInfo) + } + + fun getApplicationIcon(appInfo: ApplicationInfo): Drawable? { + return context.packageManager.getApplicationIcon(appInfo) + } + + override fun getApplicationIcon(packageName: String): Drawable? { + return context.packageManager.getApplicationIcon(packageName) + } +} diff --git a/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/IPermissionsPrivacyModule.kt b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/IPermissionsPrivacyModule.kt new file mode 100644 index 0000000..da11769 --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/IPermissionsPrivacyModule.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 - 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.externalinterfaces.permissions + +import android.app.NotificationChannel +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.graphics.drawable.Drawable +import foundation.e.advancedprivacy.domain.entities.AppOpModes +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.PermissionDescription +import foundation.e.advancedprivacy.domain.entities.ProfileType + +/** + * List applications and manage theirs permissions. + */ +interface IPermissionsPrivacyModule { + + fun buildApplicationDescription( + appInfo: ApplicationInfo, + profileId: Int = -1, + profileType: ProfileType = ProfileType.MAIN + ): ApplicationDescription + + fun getApplications( + filter: ((PackageInfo) -> Boolean)?, + ): List + + /** + * List of permissions names used by an app, specified by its [packageName]. + * @param packageName the appId of the app + * @return the list off permission, in the "android.permission.PERMISSION" format. + */ + fun getPermissions(packageName: String): List + + fun getPermissionDescription(permissionName: String): PermissionDescription + + /** + * Get the filled up [ApplicationDescription] for the app specified by its [packageName] + * @param packageName the appId of the app + * @return the informations about the app. + */ + fun getApplicationDescription(packageName: String, withIcon: Boolean = true): ApplicationDescription + + /** + * Check if the current runtime permission is granted for the specified app. + * + * @param packageName the packageName of the app + * @param permissionName the name of the permission in "android.permission.PERMISSION" format. + * @return the current status for this permission. + */ + fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean + + /** + * Get the appOps mode for the specified [appOpPermissionName] of the specified application. + * + * @param appDesc the application + * @param appOpPermissionName the AppOps permission name. + * @return mode, as a [AppOpModes] + */ + fun getAppOpMode(appDesc: ApplicationDescription, appOpPermissionName: String): AppOpModes + + /** + * Grant or revoke the specified permission for the specigfied app. + * If their is not enough privileges to get the permission, return the false + * + * @param appDesc the application + * @param permissionName the name of the permission in "android.permission.PERMISSION" format. + * @param grant true grant the permission, false revoke it. + * @return true if the permission is or has just been granted, false if + * user has to do it himself. + */ + fun toggleDangerousPermission( + appDesc: ApplicationDescription, + permissionName: String, + grant: Boolean + ): Boolean + + /** + * Change the appOp Mode for the specified appOpPermission and application. + * @param appDesc the application + * @param appOpPermissionName the AppOps permission name. + * @return true if the mode has been changed, false if + * user has to do it himself. + */ + fun setAppOpMode( + appDesc: ApplicationDescription, + appOpPermissionName: String, + status: AppOpModes + ): Boolean + + /** + * Return true if the application is flagged Dangerous. + */ + fun isPermissionsDangerous(permissionName: String): Boolean + + /** + * Get the application icon. + */ + fun getApplicationIcon(packageName: String): Drawable? + + /** + * Get the application icon. + */ + fun getApplicationIcon(app: ApplicationDescription): Drawable? + + /** + * Authorize the specified package to be used as Vpn. + * @return true if authorization has been set, false if an error has occurred. + */ + fun setVpnPackageAuthorization(packageName: String): Boolean + + /** + * Returns the package name of the currently set always-on VPN application, or null. + */ + fun getAlwaysOnVpnPackage(): String? + + /** + * Allows users to block notifications sent through this channel, if this channel belongs to + * a package that is signed with the system signature. + * + * If the channel does not belong to a package that is signed with the system signature, this + * method does nothing, since such channels are blockable by default and cannot be set to be + * unblockable. + */ + fun setBlockable(notificationChannel: NotificationChannel) +} diff --git a/fakelocation/build.gradle b/fakelocation/build.gradle index 91f8031..64fc633 100644 --- a/fakelocation/build.gradle +++ b/fakelocation/build.gradle @@ -46,7 +46,11 @@ android { } dependencies { - implementation (libs.bundles.kotlin.android.coroutines) - implementation project(':privacymodule-api') + implementation( + libs.bundles.koin, + libs.bundles.kotlin.android.coroutines + ) + implementation project(':core') + } diff --git a/fakelocation/fakelocationdemo/build.gradle b/fakelocation/fakelocationdemo/build.gradle index 0145eca..6cb9af3 100644 --- a/fakelocation/fakelocationdemo/build.gradle +++ b/fakelocation/fakelocationdemo/build.gradle @@ -54,7 +54,7 @@ android { } dependencies { - implementation project(':privacymodule-api') + implementation project(':core') implementation project(':fakelocation') implementation project(':permissionsstandalone') diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt index b7c9ced..f2e10a4 100644 --- a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt +++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt @@ -33,12 +33,12 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.databinding.DataBindingUtil -import foundation.e.privacymodules.fakelocation.FakeLocationModule +import foundation.e.advancedprivacy.domain.entities.AppOpModes +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.ProfileType +import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule +import foundation.e.advancedprivacy.permissions.externalinterfaces.PermissionsPrivacyModule import foundation.e.privacymodules.fakelocationdemo.databinding.ActivityMainBinding -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule -import foundation.e.privacymodules.permissions.data.AppOpModes -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.permissions.data.ProfileType class MainActivity : AppCompatActivity() { companion object { diff --git a/fakelocation/src/main/AndroidManifest.xml b/fakelocation/src/main/AndroidManifest.xml index 5077c24..fde371c 100644 --- a/fakelocation/src/main/AndroidManifest.xml +++ b/fakelocation/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + package="foundation.e.advancedprivacy.permissions.e"> diff --git a/permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModule.kt b/permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModule.kt new file mode 100644 index 0000000..59a20dd --- /dev/null +++ b/permissionse/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModule.kt @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2021 E FOUNDATION, 2022 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 . + */ + +package foundation.e.advancedprivacy.permissions.externalinterfaces + +import android.annotation.TargetApi +import android.app.AppOpsManager +import android.app.AppOpsManager.OP_NONE +import android.app.AppOpsManager.strOpToOp +import android.app.NotificationChannel +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.UserInfo +import android.graphics.drawable.Drawable +import android.net.IConnectivityManager +import android.net.VpnManager +import android.net.VpnManager.TYPE_VPN_SERVICE +import android.os.Build +import android.os.ServiceManager +import android.os.UserHandle +import android.os.UserManager +import android.util.Log +import foundation.e.advancedprivacy.domain.entities.AppOpModes +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.ProfileType.MAIN +import foundation.e.advancedprivacy.domain.entities.ProfileType.WORK +import foundation.e.advancedprivacy.externalinterfaces.permissions.APermissionsPrivacyModule + +/** + * Implements [IPermissionsPrivacyModule] with all privileges of a system app. + */ +class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) { + + private val appOpsManager: AppOpsManager + get() = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + + /** + * @see IPermissionsPrivacyModule.toggleDangerousPermission + * Always return true, permission is set using privileged capacities. + */ + override fun toggleDangerousPermission( + appDesc: ApplicationDescription, + permissionName: String, + grant: Boolean + ): Boolean { + try { + if (grant) { + context.packageManager.grantRuntimePermission( + appDesc.packageName, + permissionName, + android.os.Process.myUserHandle() + ) + } else { + context.packageManager.revokeRuntimePermission( + appDesc.packageName, + permissionName, + android.os.Process.myUserHandle() + ) + } + } catch (e: Exception) { + Log.e("Permissions-e", "Exception while setting permission", e) + return false + } + + return true + } + + override fun setAppOpMode( + appDesc: ApplicationDescription, + appOpPermissionName: String, + status: AppOpModes + ): Boolean { + val op = strOpToOp(appOpPermissionName) + if (op != OP_NONE) { + appOpsManager.setMode(op, appDesc.uid, appDesc.packageName, status.modeValue) + } + return true + } + + override fun getApplications( + filter: ((PackageInfo) -> Boolean)? + ): List { + val pm = context.packageManager + val mainUserId = UserHandle.myUserId() + val workProfileId = getWorkProfile()?.id + + val userIds = listOf(mainUserId, workProfileId).filterNotNull() + return userIds.map { profileId -> + pm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, profileId) + .filter { filter?.invoke(it) ?: true } + .map { + buildApplicationDescription( + appInfo = it.applicationInfo, + profileId = profileId, + profileType = if (profileId == mainUserId) MAIN else WORK + ) + } + }.flatten() + } + + override fun getApplicationIcon(app: ApplicationDescription): Drawable? { + return if (app.profileType == WORK) { + getWorkProfile()?.let { workProfile -> + val pm = context.packageManager + getApplicationIcon( + pm.getApplicationInfoAsUser(app.packageName, 0, workProfile.id) + )?.let { + pm.getUserBadgedIcon(it, workProfile.getUserHandle()) + } + } + } else getApplicationIcon(app.packageName) + } + + override fun setBlockable(notificationChannel: NotificationChannel) { + when (Build.VERSION.SDK_INT) { + 29 -> notificationChannel.setBlockableSystem(true) + 30, 31, 32, 33 -> notificationChannel.setBlockable(true) + else -> { + Log.e("Permissions-e", "Bad android sdk version") + } + } + } + + override fun setVpnPackageAuthorization(packageName: String): Boolean { + return when (Build.VERSION.SDK_INT) { + 29 -> setVpnPackageAuthorizationSDK29(packageName) + 30 -> setVpnPackageAuthorizationSDK30(packageName) + 31, 32, 33 -> setVpnPackageAuthorizationSDK32(packageName) + else -> { + Log.e("Permissions-e", "Bad android sdk version") + false + } + } + } + + @TargetApi(29) + private fun setVpnPackageAuthorizationSDK29(packageName: String): Boolean { + val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE) + ) + + try { + if (service.prepareVpn(null, packageName, UserHandle.myUserId())) { + // Authorize this app to initiate VPN connections in the future without user + // intervention. + service.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), true) + return true + } + } catch (e: java.lang.Exception) { + Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) + } catch (e: NoSuchMethodError) { + Log.e("Permissions-e", "Bad android sdk version", e) + } + return false + } + + @TargetApi(30) + private fun setVpnPackageAuthorizationSDK30(packageName: String): Boolean { + val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE) + ) + + try { + if (service.prepareVpn(null, packageName, UserHandle.myUserId())) { + // Authorize this app to initiate VPN connections in the future without user + // intervention. + service.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), TYPE_VPN_SERVICE) + return true + } + } catch (e: java.lang.Exception) { + Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) + } catch (e: NoSuchMethodError) { + Log.e("Permissions-e", "Bad android sdk version", e) + } + return false + } + + @TargetApi(31) + private fun setVpnPackageAuthorizationSDK32(packageName: String): Boolean { + val vpnManager = context.getSystemService(Context.VPN_MANAGEMENT_SERVICE) as VpnManager + + try { + if (vpnManager.prepareVpn(null, packageName, UserHandle.myUserId())) { + // Authorize this app to initiate VPN connections in the future without user + // intervention. + vpnManager.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), TYPE_VPN_SERVICE) + return true + } + } catch (e: java.lang.Exception) { + Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) + } catch (e: NoSuchMethodError) { + Log.e("Permissions-e", "Bad android sdk version", e) + } + return false + } + + override fun getAlwaysOnVpnPackage(): String? { + return when (Build.VERSION.SDK_INT) { + 29, 30 -> getAlwaysOnVpnPackageSDK29() + 31, 32, 33 -> getAlwaysOnVpnPackageSDK32() + else -> { + Log.e("Permissions-e", "Bad android sdk version") + null + } + } + } + + @TargetApi(29) + private fun getAlwaysOnVpnPackageSDK29(): String? { + val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( + ServiceManager.getService(Context.CONNECTIVITY_SERVICE) + ) + + return try { + service.getAlwaysOnVpnPackage(UserHandle.myUserId()) + } catch (e: java.lang.Exception) { + Log.e("Permissions-e", "Bad android sdk version ", e) + return null + } + } + + @TargetApi(31) + private fun getAlwaysOnVpnPackageSDK32(): String? { + val vpnManager = context.getSystemService(Context.VPN_MANAGEMENT_SERVICE) as VpnManager + return try { + vpnManager.getAlwaysOnVpnPackageForUser(UserHandle.myUserId()) + } catch (e: java.lang.Exception) { + Log.e("Permissions-e", "Bad android sdk version ", e) + return null + } + } + + private fun getWorkProfile(): UserInfo? { + val userManager: UserManager = context.getSystemService(UserManager::class.java) + val userId = UserHandle.myUserId() + for (user in userManager.getProfiles(UserHandle.myUserId())) { + if (user.id != userId && userManager.isManagedProfile(user.id)) { + return user + } + } + return null + } +} diff --git a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt deleted file mode 100644 index 6d0a17c..0000000 --- a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2021 E FOUNDATION, 2022 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 . - */ - -package foundation.e.privacymodules.permissions - -import android.annotation.TargetApi -import android.app.AppOpsManager -import android.app.AppOpsManager.OP_NONE -import android.app.AppOpsManager.strOpToOp -import android.app.NotificationChannel -import android.content.Context -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.content.pm.UserInfo -import android.graphics.drawable.Drawable -import android.net.IConnectivityManager -import android.net.VpnManager -import android.net.VpnManager.TYPE_VPN_SERVICE -import android.os.Build -import android.os.ServiceManager -import android.os.UserHandle -import android.os.UserManager -import android.util.Log -import foundation.e.privacymodules.permissions.data.AppOpModes -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.permissions.data.ProfileType.MAIN -import foundation.e.privacymodules.permissions.data.ProfileType.WORK - -/** - * Implements [IPermissionsPrivacyModule] with all privileges of a system app. - */ -class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) { - - private val appOpsManager: AppOpsManager - get() = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager - - /** - * @see IPermissionsPrivacyModule.toggleDangerousPermission - * Always return true, permission is set using privileged capacities. - */ - override fun toggleDangerousPermission( - appDesc: ApplicationDescription, - permissionName: String, - grant: Boolean - ): Boolean { - try { - if (grant) { - context.packageManager.grantRuntimePermission( - appDesc.packageName, - permissionName, - android.os.Process.myUserHandle() - ) - } else { - context.packageManager.revokeRuntimePermission( - appDesc.packageName, - permissionName, - android.os.Process.myUserHandle() - ) - } - } catch (e: Exception) { - Log.e("Permissions-e", "Exception while setting permission", e) - return false - } - - return true - } - - override fun setAppOpMode( - appDesc: ApplicationDescription, - appOpPermissionName: String, - status: AppOpModes - ): Boolean { - val op = strOpToOp(appOpPermissionName) - if (op != OP_NONE) { - appOpsManager.setMode(op, appDesc.uid, appDesc.packageName, status.modeValue) - } - return true - } - - override fun getApplications( - filter: ((PackageInfo) -> Boolean)? - ): List { - val pm = context.packageManager - val mainUserId = UserHandle.myUserId() - val workProfileId = getWorkProfile()?.id - - val userIds = listOf(mainUserId, workProfileId).filterNotNull() - return userIds.map { profileId -> - pm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, profileId) - .filter { filter?.invoke(it) ?: true } - .map { - buildApplicationDescription( - appInfo = it.applicationInfo, - profileId = profileId, - profileType = if (profileId == mainUserId) MAIN else WORK - ) - } - }.flatten() - } - - override fun getApplicationIcon(app: ApplicationDescription): Drawable? { - return if (app.profileType == WORK) { - getWorkProfile()?.let { workProfile -> - val pm = context.packageManager - getApplicationIcon( - pm.getApplicationInfoAsUser(app.packageName, 0, workProfile.id) - )?.let { - pm.getUserBadgedIcon(it, workProfile.getUserHandle()) - } - } - } else getApplicationIcon(app.packageName) - } - - override fun setBlockable(notificationChannel: NotificationChannel) { - when (Build.VERSION.SDK_INT) { - 29 -> notificationChannel.setBlockableSystem(true) - 30, 31, 32, 33 -> notificationChannel.setBlockable(true) - else -> { - Log.e("Permissions-e", "Bad android sdk version") - } - } - } - - override fun setVpnPackageAuthorization(packageName: String): Boolean { - return when (Build.VERSION.SDK_INT) { - 29 -> setVpnPackageAuthorizationSDK29(packageName) - 30 -> setVpnPackageAuthorizationSDK30(packageName) - 31, 32, 33 -> setVpnPackageAuthorizationSDK32(packageName) - else -> { - Log.e("Permissions-e", "Bad android sdk version") - false - } - } - } - - @TargetApi(29) - private fun setVpnPackageAuthorizationSDK29(packageName: String): Boolean { - val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE) - ) - - try { - if (service.prepareVpn(null, packageName, UserHandle.myUserId())) { - // Authorize this app to initiate VPN connections in the future without user - // intervention. - service.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), true) - return true - } - } catch (e: java.lang.Exception) { - Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) - } catch (e: NoSuchMethodError) { - Log.e("Permissions-e", "Bad android sdk version", e) - } - return false - } - - @TargetApi(30) - private fun setVpnPackageAuthorizationSDK30(packageName: String): Boolean { - val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE) - ) - - try { - if (service.prepareVpn(null, packageName, UserHandle.myUserId())) { - // Authorize this app to initiate VPN connections in the future without user - // intervention. - service.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), TYPE_VPN_SERVICE) - return true - } - } catch (e: java.lang.Exception) { - Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) - } catch (e: NoSuchMethodError) { - Log.e("Permissions-e", "Bad android sdk version", e) - } - return false - } - - @TargetApi(31) - private fun setVpnPackageAuthorizationSDK32(packageName: String): Boolean { - val vpnManager = context.getSystemService(Context.VPN_MANAGEMENT_SERVICE) as VpnManager - - try { - if (vpnManager.prepareVpn(null, packageName, UserHandle.myUserId())) { - // Authorize this app to initiate VPN connections in the future without user - // intervention. - vpnManager.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), TYPE_VPN_SERVICE) - return true - } - } catch (e: java.lang.Exception) { - Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) - } catch (e: NoSuchMethodError) { - Log.e("Permissions-e", "Bad android sdk version", e) - } - return false - } - - override fun getAlwaysOnVpnPackage(): String? { - return when (Build.VERSION.SDK_INT) { - 29, 30 -> getAlwaysOnVpnPackageSDK29() - 31, 32, 33 -> getAlwaysOnVpnPackageSDK32() - else -> { - Log.e("Permissions-e", "Bad android sdk version") - null - } - } - } - - @TargetApi(29) - private fun getAlwaysOnVpnPackageSDK29(): String? { - val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE) - ) - - return try { - service.getAlwaysOnVpnPackage(UserHandle.myUserId()) - } catch (e: java.lang.Exception) { - Log.e("Permissions-e", "Bad android sdk version ", e) - return null - } - } - - @TargetApi(31) - private fun getAlwaysOnVpnPackageSDK32(): String? { - val vpnManager = context.getSystemService(Context.VPN_MANAGEMENT_SERVICE) as VpnManager - return try { - vpnManager.getAlwaysOnVpnPackageForUser(UserHandle.myUserId()) - } catch (e: java.lang.Exception) { - Log.e("Permissions-e", "Bad android sdk version ", e) - return null - } - } - - private fun getWorkProfile(): UserInfo? { - val userManager: UserManager = context.getSystemService(UserManager::class.java) - val userId = UserHandle.myUserId() - for (user in userManager.getProfiles(UserHandle.myUserId())) { - if (user.id != userId && userManager.isManagedProfile(user.id)) { - return user - } - } - return null - } -} diff --git a/permissionsstandalone/build.gradle b/permissionsstandalone/build.gradle index cf3563e..e330d31 100644 --- a/permissionsstandalone/build.gradle +++ b/permissionsstandalone/build.gradle @@ -47,7 +47,7 @@ android { dependencies { implementation(libs.bundles.kotlin.android.coroutines) - implementation project(':privacymodule-api') + implementation project(':core') testImplementation libs.junit diff --git a/permissionsstandalone/src/main/AndroidManifest.xml b/permissionsstandalone/src/main/AndroidManifest.xml index 662ea44..60ff274 100644 --- a/permissionsstandalone/src/main/AndroidManifest.xml +++ b/permissionsstandalone/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModule.kt b/permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModule.kt new file mode 100644 index 0000000..95f5ff0 --- /dev/null +++ b/permissionsstandalone/src/main/java/foundation/e/advancedprivacy/permissions/externalinterfaces/PermissionsPrivacyModule.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 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 . + */ + +package foundation.e.advancedprivacy.permissions.externalinterfaces + +import android.app.NotificationChannel +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import foundation.e.advancedprivacy.domain.entities.AppOpModes +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.externalinterfaces.permissions.APermissionsPrivacyModule + +/** + * Implements [IPermissionsPrivacyModule] using only API authorized on the PlayStore. + */ +class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) { + override fun getApplications( + filter: ((PackageInfo) -> Boolean)? + ): List { + return context.packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS) + .filter { filter?.invoke(it) == true } + .map { buildApplicationDescription(it.applicationInfo) } + } + + override fun getApplicationIcon(app: ApplicationDescription): Drawable? { + return getApplicationIcon(app.packageName) + } + + /** + * @see IPermissionsPrivacyModule.toggleDangerousPermission + * Return an ManualAction to go toggle manually the permission in the ap page of the settings. + */ + override fun toggleDangerousPermission( + appDesc: ApplicationDescription, + permissionName: String, + grant: Boolean + ): Boolean = false + + override fun setAppOpMode( + appDesc: ApplicationDescription, + appOpPermissionName: String, + status: AppOpModes + ): Boolean = false + + override fun setVpnPackageAuthorization(packageName: String): Boolean { + return false + } + + override fun getAlwaysOnVpnPackage(): String? { + return null + } + + override fun setBlockable(notificationChannel: NotificationChannel) {} +} diff --git a/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt deleted file mode 100644 index 283b417..0000000 --- a/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2022 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 . - */ - -package foundation.e.privacymodules.permissions - -import android.app.NotificationChannel -import android.content.Context -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import foundation.e.privacymodules.permissions.data.AppOpModes -import foundation.e.privacymodules.permissions.data.ApplicationDescription - -/** - * Implements [IPermissionsPrivacyModule] using only API authorized on the PlayStore. - */ -class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) { - override fun getApplications( - filter: ((PackageInfo) -> Boolean)? - ): List { - return context.packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS) - .filter { filter?.invoke(it) == true } - .map { buildApplicationDescription(it.applicationInfo) } - } - - override fun getApplicationIcon(app: ApplicationDescription): Drawable? { - return getApplicationIcon(app.packageName) - } - - /** - * @see IPermissionsPrivacyModule.toggleDangerousPermission - * Return an ManualAction to go toggle manually the permission in the ap page of the settings. - */ - override fun toggleDangerousPermission( - appDesc: ApplicationDescription, - permissionName: String, - grant: Boolean - ): Boolean = false - - override fun setAppOpMode( - appDesc: ApplicationDescription, - appOpPermissionName: String, - status: AppOpModes - ): Boolean = false - - override fun setVpnPackageAuthorization(packageName: String): Boolean { - return false - } - - override fun getAlwaysOnVpnPackage(): String? { - return null - } - - override fun setBlockable(notificationChannel: NotificationChannel) {} -} diff --git a/privacymodule-api/.gitignore b/privacymodule-api/.gitignore deleted file mode 100644 index 42afabf..0000000 --- a/privacymodule-api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/privacymodule-api/build.gradle b/privacymodule-api/build.gradle deleted file mode 100644 index 259672b..0000000 --- a/privacymodule-api/build.gradle +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 . - */ - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'maven-publish' - -group 'foundation.e' - -android { - compileSdkVersion buildConfig.compileSdk - - defaultConfig { - minSdkVersion buildConfig.minSdk - targetSdkVersion buildConfig.targetSdk - - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation( - libs.androidx.core.ktx, - libs.kotlinx.coroutines - ) -} - -//url "https://gitlab.e.foundation/api/v4/groups/e/privacy-central/-/packages/maven" - -publishing { - publications { - maven(MavenPublication) { - groupId 'foundation.e' - //You can either define these here or get them from project conf elsewhere - artifactId 'privacymodule-api' - version buildConfig.version.name - artifact "$buildDir/outputs/aar/privacymodule-api-release.aar" - //aar artifact you want to publish - - //generate pom nodes for dependencies - pom.withXml { - def dependenciesNode = asNode().appendNode('dependencies') - configurations.implementation.allDependencies.each { dependency -> - if (dependency.name != 'unspecified') { - def dependencyNode = dependenciesNode.appendNode('dependency') - dependencyNode.appendNode('groupId', dependency.group) - dependencyNode.appendNode('artifactId', dependency.name) - dependencyNode.appendNode('version', dependency.version) - } - } - } - repositories { - def ciJobToken = System.getenv("CI_JOB_TOKEN") - def ciApiV4Url = System.getenv("CI_API_V4_URL") - if (ciJobToken != null) { - maven { - url "${ciApiV4Url}/projects/900/packages/maven" - credentials(HttpHeaderCredentials) { - name = 'Job-Token' - value = ciJobToken - } - authentication { - header(HttpHeaderAuthentication) - } - } - } else { - maven { -// url "https://gitlab.e.foundation/api/v4/projects/900/packages/maven" - // Use privacymodule-e repository (id = 781) for now, - // because repository not activated on Advanced Privacy (id = 900) - url "https://gitlab.e.foundation/api/v4/projects/781/packages/maven" - credentials(HttpHeaderCredentials) { - name = "Private-Token" - value = gitLabPrivateToken - // the variable resides in ~/.gradle/gradle.properties - } - authentication { - header(HttpHeaderAuthentication) - } - } - } - } - } - } -} diff --git a/privacymodule-api/consumer-rules.pro b/privacymodule-api/consumer-rules.pro deleted file mode 100644 index e69de29..0000000 diff --git a/privacymodule-api/proguard-rules.pro b/privacymodule-api/proguard-rules.pro deleted file mode 100644 index 481bb43..0000000 --- a/privacymodule-api/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/privacymodule-api/src/main/AndroidManifest.xml b/privacymodule-api/src/main/AndroidManifest.xml deleted file mode 100644 index 937e285..0000000 --- a/privacymodule-api/src/main/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - \ No newline at end of file diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt deleted file mode 100644 index 9bf8aba..0000000 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/DependencyInjector.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules - -import foundation.e.privacymodules.trackers.IDNSBlocker - -object DependencyInjector { - fun initialize( - dnsBlocker: IDNSBlocker - ) { - this.dnsBlocker = dnsBlocker - } - - lateinit var dnsBlocker: IDNSBlocker - private set -} diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt deleted file mode 100644 index 64b2292..0000000 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2022 - 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.privacymodules.permissions - -import android.app.AppOpsManager -import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.content.pm.PermissionInfo -import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS -import android.graphics.drawable.Drawable -import android.os.Build -import android.util.Log -import foundation.e.privacymodules.permissions.data.AppOpModes -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.permissions.data.PermissionDescription -import foundation.e.privacymodules.permissions.data.ProfileType - -/** - * Implementation of the commons functionality between privileged and standard - * versions of the module. - * @param context an Android context, to retrieve packageManager for example. - */ -abstract class APermissionsPrivacyModule(protected val context: Context) : IPermissionsPrivacyModule { - - companion object { - private const val TAG = "PermissionsModule" - } - - /** - * @see IPermissionsPrivacyModule.getInstalledApplications - */ - override fun getApplicationDescription(packageName: String, withIcon: Boolean): ApplicationDescription { - val appDesc = buildApplicationDescription(context.packageManager.getApplicationInfo(packageName, 0)) - if (withIcon) { - appDesc.icon = getApplicationIcon(appDesc.packageName) - } - return appDesc - } - - /** - * * @see IPermissionsPrivacyModule.getPermissions - */ - override fun getPermissions(packageName: String): List { - val packageInfo = context.packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) - return packageInfo.requestedPermissions?.asList() ?: emptyList() - } - - override fun getPermissionDescription(permissionName: String): PermissionDescription { - val info = context.packageManager.getPermissionInfo(permissionName, 0) - return PermissionDescription( - name = permissionName, - isDangerous = isPermissionsDangerous(info), - group = null, - label = info.loadLabel(context.packageManager), - description = info.loadDescription(context.packageManager) - ) - } - - /** - * @see IPermissionsPrivacyModule.isDangerousPermissionGranted - */ - override fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean { - return context.packageManager - .checkPermission(permissionName, packageName) == PackageManager.PERMISSION_GRANTED - } - - // on google version, work only for the current package. - @Suppress("DEPRECATION") - override fun getAppOpMode( - appDesc: ApplicationDescription, - appOpPermissionName: String - ): AppOpModes { - - val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager - - val mode = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - appOps.checkOpNoThrow( - appOpPermissionName, - - appDesc.uid, appDesc.packageName - ) - } else { - appOps.unsafeCheckOpNoThrow( - appOpPermissionName, - appDesc.uid, appDesc.packageName - ) - } - - return AppOpModes.getByModeValue(mode) - } - - override fun isPermissionsDangerous(permissionName: String): Boolean { - try { - val permissionInfo = context.packageManager.getPermissionInfo(permissionName, 0) - return isPermissionsDangerous(permissionInfo) - } catch (e: Exception) { - Log.w(TAG, "exception in isPermissionsDangerous(String)", e) - return false - } - } - - @Suppress("DEPRECATION") - private fun isPermissionsDangerous(permissionInfo: PermissionInfo): Boolean { - try { - return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - permissionInfo.protectionLevel and PROTECTION_DANGEROUS == 1 - } else { - permissionInfo.protection == PROTECTION_DANGEROUS - } - } catch (e: Exception) { - Log.w(TAG, "exception in isPermissionsDangerous(PermissionInfo)", e) - return false - } - } - - override fun buildApplicationDescription( - appInfo: ApplicationInfo, - profileId: Int, - profileType: ProfileType - ): - ApplicationDescription { - return ApplicationDescription( - packageName = appInfo.packageName, - uid = appInfo.uid, - label = getAppLabel(appInfo), - icon = null, - profileId = profileId, - profileType = profileType, - ) - } - - private fun getAppLabel(appInfo: ApplicationInfo): CharSequence { - return context.packageManager.getApplicationLabel(appInfo) - } - - fun getApplicationIcon(appInfo: ApplicationInfo): Drawable? { - return context.packageManager.getApplicationIcon(appInfo) - } - - override fun getApplicationIcon(packageName: String): Drawable? { - return context.packageManager.getApplicationIcon(packageName) - } -} diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt deleted file mode 100644 index 39c726a..0000000 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2022 - 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.privacymodules.permissions - -import android.app.NotificationChannel -import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo -import android.graphics.drawable.Drawable -import foundation.e.privacymodules.permissions.data.AppOpModes -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.permissions.data.PermissionDescription -import foundation.e.privacymodules.permissions.data.ProfileType - -/** - * List applications and manage theirs permissions. - */ -interface IPermissionsPrivacyModule { - - fun buildApplicationDescription( - appInfo: ApplicationInfo, - profileId: Int = -1, - profileType: ProfileType = ProfileType.MAIN - ): ApplicationDescription - - fun getApplications( - filter: ((PackageInfo) -> Boolean)?, - ): List - - /** - * List of permissions names used by an app, specified by its [packageName]. - * @param packageName the appId of the app - * @return the list off permission, in the "android.permission.PERMISSION" format. - */ - fun getPermissions(packageName: String): List - - fun getPermissionDescription(permissionName: String): PermissionDescription - - /** - * Get the filled up [ApplicationDescription] for the app specified by its [packageName] - * @param packageName the appId of the app - * @return the informations about the app. - */ - fun getApplicationDescription(packageName: String, withIcon: Boolean = true): ApplicationDescription - - /** - * Check if the current runtime permission is granted for the specified app. - * - * @param packageName the packageName of the app - * @param permissionName the name of the permission in "android.permission.PERMISSION" format. - * @return the current status for this permission. - */ - fun isDangerousPermissionGranted(packageName: String, permissionName: String): Boolean - - /** - * Get the appOps mode for the specified [appOpPermissionName] of the specified application. - * - * @param appDesc the application - * @param appOpPermissionName the AppOps permission name. - * @return mode, as a [AppOpModes] - */ - fun getAppOpMode(appDesc: ApplicationDescription, appOpPermissionName: String): AppOpModes - - /** - * Grant or revoke the specified permission for the specigfied app. - * If their is not enough privileges to get the permission, return the false - * - * @param appDesc the application - * @param permissionName the name of the permission in "android.permission.PERMISSION" format. - * @param grant true grant the permission, false revoke it. - * @return true if the permission is or has just been granted, false if - * user has to do it himself. - */ - fun toggleDangerousPermission( - appDesc: ApplicationDescription, - permissionName: String, - grant: Boolean - ): Boolean - - /** - * Change the appOp Mode for the specified appOpPermission and application. - * @param appDesc the application - * @param appOpPermissionName the AppOps permission name. - * @return true if the mode has been changed, false if - * user has to do it himself. - */ - fun setAppOpMode( - appDesc: ApplicationDescription, - appOpPermissionName: String, - status: AppOpModes - ): Boolean - - /** - * Return true if the application is flagged Dangerous. - */ - fun isPermissionsDangerous(permissionName: String): Boolean - - /** - * Get the application icon. - */ - fun getApplicationIcon(packageName: String): Drawable? - - /** - * Get the application icon. - */ - fun getApplicationIcon(app: ApplicationDescription): Drawable? - - /** - * Authorize the specified package to be used as Vpn. - * @return true if authorization has been set, false if an error has occurred. - */ - fun setVpnPackageAuthorization(packageName: String): Boolean - - /** - * Returns the package name of the currently set always-on VPN application, or null. - */ - fun getAlwaysOnVpnPackage(): String? - - /** - * Allows users to block notifications sent through this channel, if this channel belongs to - * a package that is signed with the system signature. - * - * If the channel does not belong to a package that is signed with the system signature, this - * method does nothing, since such channels are blockable by default and cannot be set to be - * unblockable. - */ - fun setBlockable(notificationChannel: NotificationChannel) -} diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt deleted file mode 100644 index 4764596..0000000 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/AppOpModes.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.permissions.data - -import android.app.AppOpsManager.MODE_ALLOWED -import android.app.AppOpsManager.MODE_DEFAULT -import android.app.AppOpsManager.MODE_ERRORED -import android.app.AppOpsManager.MODE_FOREGROUND -import android.app.AppOpsManager.MODE_IGNORED -import android.os.Build - -enum class AppOpModes(val modeValue: Int) { - ALLOWED(MODE_ALLOWED), - IGNORED(MODE_IGNORED), - ERRORED(MODE_ERRORED), - DEFAULT(MODE_DEFAULT), - FOREGROUND(if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) MODE_ALLOWED else MODE_FOREGROUND); - - companion object { - private val byMode = mapOf( - FOREGROUND.modeValue to FOREGROUND, - ALLOWED.modeValue to ALLOWED, - IGNORED.modeValue to IGNORED, - ERRORED.modeValue to ERRORED, - DEFAULT.modeValue to DEFAULT, - ) - - fun getByModeValue(modeValue: Int): AppOpModes { - return byMode.get(modeValue) ?: DEFAULT - } - } -} diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt deleted file mode 100644 index 4fa1bb9..0000000 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.permissions.data - -import android.graphics.drawable.Drawable - -/** - * Useful informations to identify and describe an application. - */ -data class ApplicationDescription( - val packageName: String, - val uid: Int, - val profileId: Int, - val profileType: ProfileType, - var label: CharSequence?, - var icon: Drawable? -) { - val profileFlag = when (profileType) { - ProfileType.MAIN -> PROFILE_FLAG_MAIN - ProfileType.WORK -> PROFILE_FLAG_WORK - else -> profileId - } - - val apId: String get() = "${profileFlag}_$packageName" - - companion object { - const val PROFILE_FLAG_MAIN = -1 - const val PROFILE_FLAG_WORK = -2 - } -} - -enum class ProfileType { - MAIN, WORK, OTHER -} diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt deleted file mode 100644 index 127192b..0000000 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/PermissionDescription.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.permissions.data - -data class PermissionDescription( - val name: String, - var isDangerous: Boolean, - val group: String?, - var label: CharSequence?, - var description: CharSequence? -) diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt deleted file mode 100644 index a132aef..0000000 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/trackers/IDNSBlocker.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers - -interface IDNSBlocker { - companion object { - const val DUMMY_APP_UID = -1 - } - - fun shouldBlock(hostname: String, appUid: Int): Boolean -} diff --git a/settings.gradle b/settings.gradle index c9b8f06..1b54f0d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,13 +12,13 @@ include ':app' rootProject.name = "AdvancedPrivacy" include ':fakelocation' include ':fakelocation:fakelocationdemo' -include ':privacymodule-api' +include ':core' include ':permissionsstandalone' include ':trackers' include ':permissionse' include ':permissionse:libs:hidden-apis-stub' include ':ipscrambling' -include ':ipscrambling:orbotservice' +//include ':ipscrambling:orbotservice' dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) diff --git a/trackers/build.gradle b/trackers/build.gradle index bb9489a..737db5a 100644 --- a/trackers/build.gradle +++ b/trackers/build.gradle @@ -1,4 +1,5 @@ /* + Copyright (C) 2023 MURENA SAS Copyright (C) 2022 ECORP This program is free software; you can redistribute it and/or @@ -42,9 +43,15 @@ android { } dependencies { - implementation project(':privacymodule-api') + implementation project(':core') implementation( + libs.androidx.work.ktx, + libs.bundles.koin, libs.bundles.kotlin.android.coroutines, + libs.google.gson, + libs.retrofit, + libs.retrofit.scalars, + libs.timber ) } diff --git a/trackers/src/main/AndroidManifest.xml b/trackers/src/main/AndroidManifest.xml index debdf61..615d310 100644 --- a/trackers/src/main/AndroidManifest.xml +++ b/trackers/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + package="foundation.e.advancedprivacy.trackers"> @@ -29,7 +30,7 @@ diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt new file mode 100644 index 0000000..0cfb69c --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 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 . + */ + +package foundation.e.advancedprivacy.trackers + +import foundation.e.advancedprivacy.data.repositories.RemoteTrackersListRepository +import foundation.e.advancedprivacy.trackers.data.StatsDatabase +import foundation.e.advancedprivacy.trackers.data.TrackersRepository +import foundation.e.advancedprivacy.trackers.data.WhitelistRepository +import foundation.e.advancedprivacy.trackers.domain.usecases.DNSBlocker +import foundation.e.advancedprivacy.trackers.domain.usecases.StatisticsUseCase +import foundation.e.advancedprivacy.trackers.domain.usecases.TrackersLogger +import foundation.e.advancedprivacy.trackers.domain.usecases.UpdateTrackerListUseCase +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.factoryOf +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val trackersModule = module { + + factoryOf(::RemoteTrackersListRepository) + factoryOf(::UpdateTrackerListUseCase) + + singleOf(::TrackersRepository) + single { + StatsDatabase( + context = androidContext(), + trackersRepository = get() + ) + } + + single { + StatisticsUseCase( + database = get(), + appListsRepository = get() + ) + } + + single { + WhitelistRepository( + context = androidContext(), + appListsRepository = get() + ) + } + + factory { + DNSBlocker( + context = androidContext(), + trackersLogger = get(), + trackersRepository = get(), + whitelistRepository = get() + ) + } + + factory { + TrackersLogger(statisticsUseCase = get()) + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/ETrackersResponse.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/ETrackersResponse.kt new file mode 100644 index 0000000..1b38ecf --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/ETrackersResponse.kt @@ -0,0 +1,10 @@ +package foundation.e.advancedprivacy.trackers.data + +data class ETrackersResponse(val trackers: List) { + data class ETracker( + val id: String?, + val hostnames: List?, + val name: String?, + val exodusId: String? + ) +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt new file mode 100644 index 0000000..c2c0768 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/RemoteTrackersListRepository.kt @@ -0,0 +1,61 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.data.repositories + +import retrofit2.Retrofit +import retrofit2.converter.scalars.ScalarsConverterFactory +import retrofit2.http.GET +import timber.log.Timber +import java.io.File +import java.io.FileWriter +import java.io.IOException +import java.io.PrintWriter + +class RemoteTrackersListRepository { + + fun saveData(file: File, data: String): Boolean { + try { + val fos = FileWriter(file, false) + val ps = PrintWriter(fos) + ps.apply { + print(data) + flush() + close() + } + return true + } catch (e: IOException) { + Timber.e("While saving tracker file.", e) + } + return false + } +} + +interface ETrackersApi { + companion object { + fun build(): ETrackersApi { + val retrofit = Retrofit.Builder() + .baseUrl("https://gitlab.e.foundation/e/os/tracker-list/-/raw/main/") + .addConverterFactory(ScalarsConverterFactory.create()) + .build() + return retrofit.create(ETrackersApi::class.java) + } + } + + @GET("list/e_trackers.json") + suspend fun trackers(): String +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt new file mode 100644 index 0000000..6aa76cf --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt @@ -0,0 +1,461 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.data + +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.provider.BaseColumns +import androidx.core.database.getStringOrNull +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_APPID +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER +import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker +import timber.log.Timber +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.ChronoUnit +import java.time.temporal.TemporalUnit +import java.util.concurrent.TimeUnit + +class StatsDatabase( + context: Context, + private val trackersRepository: TrackersRepository +) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + + companion object { + const val DATABASE_VERSION = 2 + const val DATABASE_NAME = "TrackerFilterStats.db" + private const val SQL_CREATE_TABLE = "CREATE TABLE $TABLE_NAME (" + + "${BaseColumns._ID} INTEGER PRIMARY KEY," + + "$COLUMN_NAME_TIMESTAMP INTEGER," + + "$COLUMN_NAME_TRACKER TEXT," + + "$COLUMN_NAME_NUMBER_CONTACTED INTEGER," + + "$COLUMN_NAME_NUMBER_BLOCKED INTEGER," + + "$COLUMN_NAME_APPID TEXT)" + + private const val PROJECTION_NAME_PERIOD = "period" + private const val PROJECTION_NAME_CONTACTED_SUM = "contactedsum" + private const val PROJECTION_NAME_BLOCKED_SUM = "blockedsum" + private const val PROJECTION_NAME_LEAKED_SUM = "leakedsum" + private const val PROJECTION_NAME_TRACKERS_COUNT = "trackerscount" + + private val MIGRATE_1_2 = listOf( + "ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_NAME_APPID TEXT" + // "ALTER TABLE $TABLE_NAME DROP COLUMN app_uid" + // DROP COLUMN is available since sqlite 3.35.0, and sdk29 as 3.22.0, sdk32 as 3.32.2 + ) + } + + object AppTrackerEntry : BaseColumns { + const val TABLE_NAME = "tracker_filter_stats" + const val COLUMN_NAME_TIMESTAMP = "timestamp" + const val COLUMN_NAME_TRACKER = "tracker" + const val COLUMN_NAME_NUMBER_CONTACTED = "sum_contacted" + const val COLUMN_NAME_NUMBER_BLOCKED = "sum_blocked" + const val COLUMN_NAME_APPID = "app_apid" + } + + private var projection = arrayOf( + COLUMN_NAME_TIMESTAMP, + COLUMN_NAME_TRACKER, + COLUMN_NAME_NUMBER_CONTACTED, + COLUMN_NAME_NUMBER_BLOCKED, + COLUMN_NAME_APPID + ) + + private val lock = Any() + + override fun onCreate(db: SQLiteDatabase) { + db.execSQL(SQL_CREATE_TABLE) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + if (oldVersion == 1 && newVersion == 2) { + MIGRATE_1_2.forEach(db::execSQL) + } else { + Timber.e( + "Unexpected database versions: oldVersion: $oldVersion ; newVersion: $newVersion" + ) + } + } + + private fun getCallsByPeriod( + periodsCount: Int, + periodUnit: TemporalUnit, + sqlitePeriodFormat: String + ): Map> { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodsCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + minTimestamp) + + val projection = ( + "$COLUMN_NAME_TIMESTAMP, " + + "STRFTIME('$sqlitePeriodFormat', DATETIME($COLUMN_NAME_TIMESTAMP, 'unixepoch', 'localtime')) $PROJECTION_NAME_PERIOD," + + "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM, " + + "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" + ) + + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME WHERE $selection" + + " GROUP BY $PROJECTION_NAME_PERIOD" + + " ORDER BY $COLUMN_NAME_TIMESTAMP DESC LIMIT $periodsCount", + selectionArg + ) + val callsByPeriod = HashMap>() + while (cursor.moveToNext()) { + val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) + val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) + callsByPeriod[cursor.getString(PROJECTION_NAME_PERIOD)] = blocked to contacted - blocked + } + cursor.close() + db.close() + return callsByPeriod + } + } + + private fun callsByPeriodToPeriodsList( + callsByPeriod: Map>, + periodsCount: Int, + periodUnit: TemporalUnit, + javaPeriodFormat: String + ): List> { + var currentDate = ZonedDateTime.now().minus(periodsCount.toLong(), periodUnit) + val formatter = DateTimeFormatter.ofPattern(javaPeriodFormat) + val calls = mutableListOf>() + for (i in 0 until periodsCount) { + currentDate = currentDate.plus(1, periodUnit) + val currentPeriod = formatter.format(currentDate) + calls.add(callsByPeriod.getOrDefault(currentPeriod, 0 to 0)) + } + return calls + } + + fun getTrackersCallsOnPeriod( + periodsCount: Int, + periodUnit: TemporalUnit + ): List> { + var sqlitePeriodFormat = "%Y%m" + var javaPeriodFormat = "yyyyMM" + if (periodUnit === ChronoUnit.MONTHS) { + sqlitePeriodFormat = "%Y%m" + javaPeriodFormat = "yyyyMM" + } else if (periodUnit === ChronoUnit.DAYS) { + sqlitePeriodFormat = "%Y%m%d" + javaPeriodFormat = "yyyyMMdd" + } else if (periodUnit === ChronoUnit.HOURS) { + sqlitePeriodFormat = "%Y%m%d%H" + javaPeriodFormat = "yyyyMMddHH" + } + val callsByPeriod = getCallsByPeriod(periodsCount, periodUnit, sqlitePeriodFormat) + return callsByPeriodToPeriodsList(callsByPeriod, periodsCount, periodUnit, javaPeriodFormat) + } + + fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodsCount, periodUnit) + val db = writableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ? AND " + + "$COLUMN_NAME_NUMBER_CONTACTED > $COLUMN_NAME_NUMBER_BLOCKED" + val selectionArg = arrayOf("" + minTimestamp) + val projection = + "COUNT(DISTINCT $COLUMN_NAME_TRACKER) $PROJECTION_NAME_TRACKERS_COUNT" + + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME WHERE $selection", + selectionArg + ) + var count = 0 + if (cursor.moveToNext()) { + count = cursor.getInt(0) + } + cursor.close() + db.close() + return count + } + } + + fun getContactedTrackersCount(): Int { + synchronized(lock) { + val db = readableDatabase + var query = "SELECT DISTINCT $COLUMN_NAME_TRACKER FROM $TABLE_NAME" + + val cursor = db.rawQuery(query, arrayOf()) + var count = 0 + while (cursor.moveToNext()) { + trackersRepository.getTracker(cursor.getString(COLUMN_NAME_TRACKER))?.let { + count++ + } + } + cursor.close() + db.close() + return count + } + } + + fun getContactedTrackersCountByAppId(): Map { + synchronized(lock) { + val db = readableDatabase + val projection = "$COLUMN_NAME_APPID, $COLUMN_NAME_TRACKER" + val cursor = db.rawQuery( + "SELECT DISTINCT $projection FROM $TABLE_NAME", // + + arrayOf() + ) + val countByApp = mutableMapOf() + while (cursor.moveToNext()) { + trackersRepository.getTracker(cursor.getString(COLUMN_NAME_TRACKER))?.let { + val appId = cursor.getString(COLUMN_NAME_APPID) + countByApp[appId] = countByApp.getOrDefault(appId, 0) + 1 + } + } + cursor.close() + db.close() + return countByApp + } + } + + fun getCallsByAppIds(periodCount: Int, periodUnit: TemporalUnit): Map> { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + minTimestamp) + val projection = "$COLUMN_NAME_APPID, " + + "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," + + "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME" + + " WHERE $selection" + + " GROUP BY $COLUMN_NAME_APPID", + selectionArg + ) + val callsByApp = HashMap>() + while (cursor.moveToNext()) { + val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) + val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) + callsByApp[cursor.getString(COLUMN_NAME_APPID)] = blocked to contacted - blocked + } + cursor.close() + db.close() + return callsByApp + } + } + + fun getCalls(appId: String, periodCount: Int, periodUnit: TemporalUnit): Pair { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_APPID = ? AND " + + "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + appId, "" + minTimestamp) + val projection = + "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," + + "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME WHERE $selection", + selectionArg + ) + var calls: Pair = 0 to 0 + if (cursor.moveToNext()) { + val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) + val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) + calls = blocked to contacted - blocked + } + cursor.close() + db.close() + return calls + } + } + + fun getMostLeakedAppId(periodCount: Int, periodUnit: TemporalUnit): String { + synchronized(lock) { + val minTimestamp = getPeriodStartTs(periodCount, periodUnit) + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ?" + val selectionArg = arrayOf("" + minTimestamp) + val projection = "$COLUMN_NAME_APPID, " + + "SUM($COLUMN_NAME_NUMBER_CONTACTED - $COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_LEAKED_SUM" + val cursor = db.rawQuery( + "SELECT $projection FROM $TABLE_NAME" + + " WHERE $selection" + + " GROUP BY $COLUMN_NAME_APPID" + + " ORDER BY $PROJECTION_NAME_LEAKED_SUM DESC LIMIT 1", + selectionArg + ) + var appId = "" + if (cursor.moveToNext()) { + appId = cursor.getString(COLUMN_NAME_APPID) + } + cursor.close() + db.close() + return appId + } + } + + fun logAccess(trackerId: String?, appId: String, blocked: Boolean) { + synchronized(lock) { + val currentHour = getCurrentHourTs() + val db = writableDatabase + val values = ContentValues() + values.put(COLUMN_NAME_APPID, appId) + values.put(COLUMN_NAME_TRACKER, trackerId) + values.put(COLUMN_NAME_TIMESTAMP, currentHour) + + /*String query = "UPDATE product SET "+COLUMN_NAME_NUMBER_CONTACTED+" = "+COLUMN_NAME_NUMBER_CONTACTED+" + 1 "; + if(blocked) + query+=COLUMN_NAME_NUMBER_BLOCKED+" = "+COLUMN_NAME_NUMBER_BLOCKED+" + 1 "; +*/ + val selection = "$COLUMN_NAME_TIMESTAMP = ? AND " + + "$COLUMN_NAME_APPID = ? AND " + + "$COLUMN_NAME_TRACKER = ? " + val selectionArg = arrayOf("" + currentHour, "" + appId, trackerId) + val cursor = db.query( + TABLE_NAME, + projection, + selection, + selectionArg, + null, + null, + null + ) + if (cursor.count > 0) { + cursor.moveToFirst() + val entry = cursorToEntry(cursor) + if (blocked) values.put( + COLUMN_NAME_NUMBER_BLOCKED, + entry.sum_blocked + 1 + ) else values.put(COLUMN_NAME_NUMBER_BLOCKED, entry.sum_blocked) + values.put(COLUMN_NAME_NUMBER_CONTACTED, entry.sum_contacted + 1) + db.update(TABLE_NAME, values, selection, selectionArg) + + // db.execSQL(query, new String[]{""+hour, ""+day, ""+month, ""+year, ""+appUid, ""+trackerId}); + } else { + if (blocked) values.put( + COLUMN_NAME_NUMBER_BLOCKED, + 1 + ) else values.put(COLUMN_NAME_NUMBER_BLOCKED, 0) + values.put(COLUMN_NAME_NUMBER_CONTACTED, 1) + db.insert(TABLE_NAME, null, values) + } + cursor.close() + db.close() + } + } + + private fun cursorToEntry(cursor: Cursor): StatEntry { + val entry = StatEntry() + entry.timestamp = cursor.getLong(COLUMN_NAME_TIMESTAMP) + entry.appId = cursor.getString(COLUMN_NAME_APPID) + entry.sum_blocked = cursor.getInt(COLUMN_NAME_NUMBER_BLOCKED) + entry.sum_contacted = cursor.getInt(COLUMN_NAME_NUMBER_CONTACTED) + entry.tracker = cursor.getInt(COLUMN_NAME_TRACKER) + return entry + } + + fun getTrackers(appIds: List?): List { + synchronized(lock) { + val columns = arrayOf(COLUMN_NAME_TRACKER, COLUMN_NAME_APPID) + var selection: String? = null + + var selectionArg: Array? = null + appIds?.let { appIds -> + selection = "$COLUMN_NAME_APPID IN (${appIds.joinToString(", ") { "'$it'" }})" + selectionArg = arrayOf() + } + + val db = readableDatabase + val cursor = db.query( + true, + TABLE_NAME, + columns, + selection, + selectionArg, + null, + null, + null, + null + ) + val trackers: MutableList = ArrayList() + while (cursor.moveToNext()) { + val trackerId = cursor.getString(COLUMN_NAME_TRACKER) + val tracker = trackersRepository.getTracker(trackerId) + if (tracker != null) { + trackers.add(tracker) + } + } + cursor.close() + db.close() + return trackers + } + } + + class StatEntry { + var appId = "" + var sum_contacted = 0 + var sum_blocked = 0 + var timestamp: Long = 0 + var tracker = 0 + } + + private fun getCurrentHourTs(): Long { + val hourInMs = TimeUnit.HOURS.toMillis(1L) + val hourInS = TimeUnit.HOURS.toSeconds(1L) + return System.currentTimeMillis() / hourInMs * hourInS + } + + private fun getPeriodStartTs( + periodsCount: Int, + periodUnit: TemporalUnit + ): Long { + var start = ZonedDateTime.now() + .minus(periodsCount.toLong(), periodUnit) + .plus(1, periodUnit) + var truncatePeriodUnit = periodUnit + if (periodUnit === ChronoUnit.MONTHS) { + start = start.withDayOfMonth(1) + truncatePeriodUnit = ChronoUnit.DAYS + } + return start.truncatedTo(truncatePeriodUnit).toEpochSecond() + } + + private fun Cursor.getInt(columnName: String): Int { + val columnIndex = getColumnIndex(columnName) + return if (columnIndex >= 0) getInt(columnIndex) else 0 + } + + private fun Cursor.getLong(columnName: String): Long { + val columnIndex = getColumnIndex(columnName) + return if (columnIndex >= 0) getLong(columnIndex) else 0 + } + + private fun Cursor.getString(columnName: String): String { + val columnIndex = getColumnIndex(columnName) + return if (columnIndex >= 0) { + getStringOrNull(columnIndex) ?: "" + } else "" + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt new file mode 100644 index 0000000..a7d5e49 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/TrackersRepository.kt @@ -0,0 +1,109 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.data + +import android.content.Context +import com.google.gson.Gson +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import timber.log.Timber +import java.io.File +import java.io.FileInputStream +import java.io.InputStreamReader + +class TrackersRepository( + private val context: Context, + coroutineScope: CoroutineScope +) { + + private var trackersById: Map = HashMap() + private var hostnameToId: Map = HashMap() + + private val eTrackerFileName = "e_trackers.json" + val eTrackerFile = File(context.filesDir.absolutePath, eTrackerFileName) + + init { + coroutineScope.launch(Dispatchers.IO) { + initTrackersFile() + } + } + fun initTrackersFile() { + try { + var inputStream = context.assets.open(eTrackerFileName) + if (eTrackerFile.exists()) { + inputStream = FileInputStream(eTrackerFile) + } + val reader = InputStreamReader(inputStream, "UTF-8") + val trackerResponse = + Gson().fromJson(reader, ETrackersResponse::class.java) + + setTrackersList(mapper(trackerResponse)) + + reader.close() + inputStream.close() + } catch (e: Exception) { + Timber.e("While parsing trackers in assets", e) + } + } + + private fun mapper(response: ETrackersResponse): List { + return response.trackers.mapNotNull { + try { + it.toTracker() + } catch (e: Exception) { + null + } + } + } + + private fun ETrackersResponse.ETracker.toTracker(): Tracker { + return Tracker( + id = id!!, + hostnames = hostnames!!.toSet(), + label = name!!, + exodusId = exodusId + ) + } + + private fun setTrackersList(list: List) { + val trackersById: MutableMap = HashMap() + val hostnameToId: MutableMap = HashMap() + list.forEach { tracker -> + trackersById[tracker.id] = tracker + for (hostname in tracker.hostnames) { + hostnameToId[hostname] = tracker.id + } + } + this.trackersById = trackersById + this.hostnameToId = hostnameToId + } + + fun isTracker(hostname: String?): Boolean { + return hostnameToId.containsKey(hostname) + } + + fun getTrackerId(hostname: String?): String? { + return hostnameToId[hostname] + } + + fun getTracker(id: String?): Tracker? { + return trackersById[id] + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/WhitelistRepository.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/WhitelistRepository.kt new file mode 100644 index 0000000..429c5e9 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/WhitelistRepository.kt @@ -0,0 +1,195 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.data + +import android.content.Context +import android.content.SharedPreferences +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker +import java.io.File + +class WhitelistRepository( + context: Context, + private val appListsRepository: AppListsRepository +) { + private var appsWhitelist: Set = HashSet() + private var appUidsWhitelist: Set = HashSet() + + private var trackersWhitelistByApp: MutableMap> = HashMap() + private var trackersWhitelistByUid: Map> = HashMap() + + private val prefs: SharedPreferences + + companion object { + private const val SHARED_PREFS_FILE = "trackers_whitelist_v2" + private const val KEY_BLOCKING_ENABLED = "blocking_enabled" + private const val KEY_APPS_WHITELIST = "apps_whitelist" + private const val KEY_APP_TRACKERS_WHITELIST_PREFIX = "app_trackers_whitelist_" + + private const val SHARED_PREFS_FILE_V1 = "trackers_whitelist.prefs" + } + + init { + prefs = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE) + reloadCache() + migrate(context) + } + + private fun migrate(context: Context) { + if (context.sharedPreferencesExists(SHARED_PREFS_FILE_V1)) { + migrate1To2(context) + } + } + + private fun Context.sharedPreferencesExists(fileName: String): Boolean { + return File( + "${applicationInfo.dataDir}/shared_prefs/$fileName.xml" + ).exists() + } + + private fun migrate1To2(context: Context) { + val prefsV1 = context.getSharedPreferences(SHARED_PREFS_FILE_V1, Context.MODE_PRIVATE) + val editorV2 = prefs.edit() + + editorV2.putBoolean(KEY_BLOCKING_ENABLED, prefsV1.getBoolean(KEY_BLOCKING_ENABLED, false)) + + val apIds = prefsV1.getStringSet(KEY_APPS_WHITELIST, HashSet())?.mapNotNull { + try { + val uid = it.toInt() + appListsRepository.getApp(uid)?.apId + } catch (e: Exception) { null } + }?.toSet() ?: HashSet() + + editorV2.putStringSet(KEY_APPS_WHITELIST, apIds) + + prefsV1.all.keys.forEach { key -> + if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) { + try { + val uid = key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length).toInt() + val apId = appListsRepository.getApp(uid)?.apId + apId?.let { + val trackers = prefsV1.getStringSet(key, emptySet()) + editorV2.putStringSet(buildAppTrackersKey(apId), trackers) + } + } catch (e: Exception) { } + } + } + editorV2.commit() + + context.deleteSharedPreferences(SHARED_PREFS_FILE_V1) + + reloadCache() + } + + private fun reloadCache() { + isBlockingEnabled = prefs.getBoolean(KEY_BLOCKING_ENABLED, false) + reloadAppsWhiteList() + reloadAllAppTrackersWhiteList() + } + + private fun reloadAppsWhiteList() { + appsWhitelist = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet()) ?: HashSet() + appUidsWhitelist = appsWhitelist + .mapNotNull { apId -> appListsRepository.getApp(apId)?.uid } + .toSet() + } + + private fun refreshAppUidTrackersWhiteList() { + trackersWhitelistByUid = trackersWhitelistByApp.mapNotNull { (apId, value) -> + appListsRepository.getApp(apId)?.uid?.let { uid -> + uid to value + } + }.toMap() + } + private fun reloadAllAppTrackersWhiteList() { + val map: MutableMap> = HashMap() + prefs.all.keys.forEach { key -> + if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) { + map[key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length)] = ( + prefs.getStringSet(key, HashSet()) ?: HashSet() + ) + } + } + trackersWhitelistByApp = map + } + + var isBlockingEnabled: Boolean = false + get() = field + set(enabled) { + prefs.edit().putBoolean(KEY_BLOCKING_ENABLED, enabled).apply() + field = enabled + } + + fun setWhiteListed(apId: String, isWhiteListed: Boolean) { + val current = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet())?.toHashSet() ?: HashSet() + + if (isWhiteListed) { + current.add(apId) + } else { + current.remove(apId) + } + prefs.edit().putStringSet(KEY_APPS_WHITELIST, current).commit() + reloadAppsWhiteList() + } + + private fun buildAppTrackersKey(apId: String): String { + return KEY_APP_TRACKERS_WHITELIST_PREFIX + apId + } + + fun setWhiteListed(tracker: Tracker, apId: String, isWhiteListed: Boolean) { + val trackers = trackersWhitelistByApp.getOrDefault(apId, HashSet()) + trackersWhitelistByApp[apId] = trackers + + if (isWhiteListed) { + trackers.add(tracker.id) + } else { + trackers.remove(tracker.id) + } + refreshAppUidTrackersWhiteList() + prefs.edit().putStringSet(buildAppTrackersKey(apId), trackers).commit() + } + + fun isAppWhiteListed(app: ApplicationDescription): Boolean { + return appsWhitelist.contains(app.apId) + } + + fun isWhiteListed(appUid: Int, trackerId: String?): Boolean { + return appUidsWhitelist.contains(appUid) || + trackersWhitelistByUid.getOrDefault(appUid, HashSet()).contains(trackerId) + } + + fun areWhiteListEmpty(): Boolean { + return appsWhitelist.isEmpty() && trackersWhitelistByApp.all { (_, trackers) -> trackers.isEmpty() } + } + + fun getWhiteListedApp(): List { + return appsWhitelist.mapNotNull(appListsRepository::getApp) + } + + fun getWhiteListForApp(app: ApplicationDescription): List { + return trackersWhitelistByApp[app.apId]?.toList() ?: emptyList() + } + + fun clearWhiteList(apId: String) { + trackersWhitelistByApp.remove(apId) + refreshAppUidTrackersWhiteList() + prefs.edit().remove(buildAppTrackersKey(apId)).commit() + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/entities/Tracker.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/entities/Tracker.kt new file mode 100644 index 0000000..5c31294 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/entities/Tracker.kt @@ -0,0 +1,28 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.domain.entities + +/** + * Describe a tracker. + */ +data class Tracker( + val id: String, + val hostnames: Set, + val label: String, + val exodusId: String? +) diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt new file mode 100644 index 0000000..fb08910 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/DNSBlocker.kt @@ -0,0 +1,143 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.domain.usecases + +import android.content.Context +import android.content.pm.PackageManager +import android.net.LocalServerSocket +import android.system.ErrnoException +import android.system.Os +import android.system.OsConstants +import foundation.e.advancedprivacy.core.utils.runSuspendCatching +import foundation.e.advancedprivacy.trackers.data.TrackersRepository +import foundation.e.advancedprivacy.trackers.data.WhitelistRepository +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import timber.log.Timber +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.PrintWriter + +class DNSBlocker( + context: Context, + val trackersLogger: TrackersLogger, + private val trackersRepository: TrackersRepository, + private val whitelistRepository: WhitelistRepository +) { + private var resolverReceiver: LocalServerSocket? = null + private var eBrowserAppUid = -1 + + companion object { + private const val SOCKET_NAME = "foundation.e.advancedprivacy" + private const val E_BROWSER_DOT_SERVER = "chrome.cloudflare-dns.com" + } + + init { + initEBrowserDoTFix(context) + } + + private fun closeSocket() { + // Known bug and workaround that LocalServerSocket::close is not working well + // https://issuetracker.google.com/issues/36945762 + if (resolverReceiver != null) { + try { + Os.shutdown(resolverReceiver!!.fileDescriptor, OsConstants.SHUT_RDWR) + resolverReceiver!!.close() + resolverReceiver = null + } catch (e: ErrnoException) { + if (e.errno != OsConstants.EBADF) { + Timber.w("Socket already closed") + } else { + Timber.e(e, "Exception: cannot close DNS port on stop $SOCKET_NAME !") + } + } catch (e: Exception) { + Timber.e(e, "Exception: cannot close DNS port on stop $SOCKET_NAME !") + } + } + } + + fun listenJob(scope: CoroutineScope): Job = scope.launch(Dispatchers.IO) { + val resolverReceiver = runSuspendCatching { + LocalServerSocket(SOCKET_NAME) + }.getOrElse { + Timber.e(it, "Exception: cannot open DNS port on $SOCKET_NAME") + return@launch + } + + this@DNSBlocker.resolverReceiver = resolverReceiver + Timber.d("DNSFilterProxy running on port $SOCKET_NAME") + + while (isActive) { + runSuspendCatching { + val socket = resolverReceiver.accept() + val reader = BufferedReader(InputStreamReader(socket.inputStream)) + val line = reader.readLine() + val params = line.split(",").toTypedArray() + val output = socket.outputStream + val writer = PrintWriter(output, true) + val domainName = params[0] + val appUid = params[1].toInt() + var isBlocked = false + if (isEBrowserDoTBlockFix(appUid, domainName)) { + isBlocked = true + } else if (trackersRepository.isTracker(domainName)) { + val trackerId = trackersRepository.getTrackerId(domainName) + if (shouldBlock(appUid, trackerId)) { + writer.println("block") + isBlocked = true + } + trackersLogger.logAccess(trackerId, appUid, isBlocked) + } + if (!isBlocked) { + writer.println("pass") + } + socket.close() + }.onFailure { + if (it is CancellationException) { + closeSocket() + throw it + } else { + Timber.w(it, "Exception while listening DNS resolver") + } + } + } + } + + private fun initEBrowserDoTFix(context: Context) { + try { + eBrowserAppUid = + context.packageManager.getApplicationInfo("foundation.e.browser", 0).uid + } catch (e: PackageManager.NameNotFoundException) { + Timber.i(e, "no E Browser package found.") + } + } + + private fun isEBrowserDoTBlockFix(appUid: Int, hostname: String): Boolean { + return appUid == eBrowserAppUid && E_BROWSER_DOT_SERVER == hostname + } + + private fun shouldBlock(appUid: Int, trackerId: String?): Boolean { + return whitelistRepository.isBlockingEnabled && + !whitelistRepository.isWhiteListed(appUid, trackerId) + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt new file mode 100644 index 0000000..55efeb9 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt @@ -0,0 +1,86 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.trackers.data.StatsDatabase +import foundation.e.advancedprivacy.trackers.domain.entities.Tracker +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import java.time.temporal.TemporalUnit + +class StatisticsUseCase( + private val database: StatsDatabase, + private val appListsRepository: AppListsRepository +) { + private val _newDataAvailable = MutableSharedFlow() + val newDataAvailable: SharedFlow = _newDataAvailable + + suspend fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) { + appListsRepository.getApp(appUid)?.let { app -> + database.logAccess(trackerId, app.apId, blocked) + _newDataAvailable.emit(Unit) + } + } + + fun getTrackersCallsOnPeriod( + periodsCount: Int, + periodUnit: TemporalUnit + ): List> { + return database.getTrackersCallsOnPeriod(periodsCount, periodUnit) + } + + fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int { + return database.getActiveTrackersByPeriod(periodsCount, periodUnit) + } + + fun getContactedTrackersCountByApp(): Map { + return database.getContactedTrackersCountByAppId().mapByAppIdToApp() + } + + fun getContactedTrackersCount(): Int { + return database.getContactedTrackersCount() + } + + fun getTrackers(apps: List?): List { + return database.getTrackers(apps?.map { it.apId }) + } + + fun getCallsByApps( + periodCount: Int, + periodUnit: TemporalUnit + ): Map> { + return database.getCallsByAppIds(periodCount, periodUnit).mapByAppIdToApp() + } + + fun getCalls(app: ApplicationDescription, periodCount: Int, periodUnit: TemporalUnit): Pair { + return database.getCalls(app.apId, periodCount, periodUnit) + } + + fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): ApplicationDescription? { + return appListsRepository.getApp(database.getMostLeakedAppId(periodCount, periodUnit)) + } + + private fun Map.mapByAppIdToApp(): Map { + return entries.mapNotNull { (apId, value) -> + appListsRepository.getApp(apId)?.let { it to value } + }.toMap() + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt new file mode 100644 index 0000000..411b4ab --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/TrackersLogger.kt @@ -0,0 +1,60 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.domain.usecases + +import foundation.e.advancedprivacy.core.utils.runSuspendCatching +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import timber.log.Timber +import java.util.concurrent.LinkedBlockingQueue + +class TrackersLogger( + private val statisticsUseCase: StatisticsUseCase, +) { + private val queue = LinkedBlockingQueue() + + fun logAccess(trackerId: String?, appUid: Int, wasBlocked: Boolean) { + queue.offer(DetectedTracker(trackerId, appUid, wasBlocked)) + } + + fun writeLogJob(scope: CoroutineScope): Job { + return scope.launch(Dispatchers.IO) { + while (isActive) { + runSuspendCatching { + logAccess(queue.take()) + }.onFailure { + Timber.e(it, "writeLogLoop detectedTrackersQueue.take() interrupted: ") + } + } + } + } + + private suspend fun logAccess(detectedTracker: DetectedTracker) { + statisticsUseCase.logAccess( + detectedTracker.trackerId, + detectedTracker.appUid, + detectedTracker.wasBlocked + ) + } + + inner class DetectedTracker(var trackerId: String?, var appUid: Int, var wasBlocked: Boolean) +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/UpdateTrackerListUseCase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/UpdateTrackerListUseCase.kt new file mode 100644 index 0000000..3593dbb --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/UpdateTrackerListUseCase.kt @@ -0,0 +1,29 @@ +package foundation.e.advancedprivacy.trackers.domain.usecases + +import foundation.e.advancedprivacy.data.repositories.ETrackersApi +import foundation.e.advancedprivacy.data.repositories.RemoteTrackersListRepository +import foundation.e.advancedprivacy.trackers.data.TrackersRepository +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber + +class UpdateTrackerListUseCase( + private val remoteTrackersListRepository: RemoteTrackersListRepository, + private val trackersRepository: TrackersRepository, + private val coroutineScope: CoroutineScope, + +) { + fun updateTrackers() = coroutineScope.launch { + update() + } + + suspend fun update() { + val api = ETrackersApi.build() + try { + remoteTrackersListRepository.saveData(trackersRepository.eTrackerFile, api.trackers()) + trackersRepository.initTrackersFile() + } catch (e: Exception) { + Timber.e("While updating trackers", e) + } + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt new file mode 100644 index 0000000..25539e1 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/DNSBlockerService.kt @@ -0,0 +1,68 @@ +/* + * 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.trackers.services + +import android.app.Service +import android.content.Intent +import android.os.IBinder +import foundation.e.advancedprivacy.trackers.domain.usecases.DNSBlocker +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import org.koin.java.KoinJavaComponent.get + +class DNSBlockerService : Service() { + companion object { + const val ACTION_START = "foundation.e.privacymodules.trackers.intent.action.START" + const val EXTRA_ENABLE_NOTIFICATION = + "foundation.e.privacymodules.trackers.intent.extra.ENABLED_NOTIFICATION" + } + + private var coroutineScope = CoroutineScope(Dispatchers.IO) + private var dnsBlocker: DNSBlocker? = null + + override fun onBind(intent: Intent): IBinder? { + throw UnsupportedOperationException("Not yet implemented") + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (ACTION_START == intent?.action) { + if (intent.getBooleanExtra(EXTRA_ENABLE_NOTIFICATION, true)) { + ForegroundStarter.startForeground(this) + } + stop() + start() + } + return START_REDELIVER_INTENT + } + + private fun start() { + coroutineScope = CoroutineScope(Dispatchers.IO) + get(DNSBlocker::class.java).apply { + this@DNSBlockerService.dnsBlocker = this + trackersLogger.writeLogJob(coroutineScope) + listenJob(coroutineScope) + } + } + + private fun stop() { + kotlin.runCatching { coroutineScope.cancel() } + dnsBlocker = null + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/ForegroundStarter.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/ForegroundStarter.kt new file mode 100644 index 0000000..a0cea43 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/ForegroundStarter.kt @@ -0,0 +1,45 @@ +/* + * 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.trackers.services + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Context +import android.os.Build + +object ForegroundStarter { + private const val NOTIFICATION_CHANNEL_ID = "blocker_service" + fun startForeground(service: Service) { + val mNotificationManager = + service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (Build.VERSION.SDK_INT >= 26) { + mNotificationManager.createNotificationChannel( + NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_ID, + NotificationManager.IMPORTANCE_LOW + ) + ) + val notification = Notification.Builder(service, NOTIFICATION_CHANNEL_ID) + .setContentTitle("Trackers filter").build() + service.startForeground(1337, notification) + } + } +} diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/UpdateTrackersWorker.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/UpdateTrackersWorker.kt new file mode 100644 index 0000000..50aa082 --- /dev/null +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/services/UpdateTrackersWorker.kt @@ -0,0 +1,60 @@ +/* + * 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 . + */ + +package foundation.e.advancedprivacy.trackers.services + +import android.content.Context +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import foundation.e.advancedprivacy.trackers.domain.usecases.UpdateTrackerListUseCase +import org.koin.java.KoinJavaComponent.get +import java.util.concurrent.TimeUnit + +class UpdateTrackersWorker(appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + + override suspend fun doWork(): Result { + val updateTrackersUsecase: UpdateTrackerListUseCase = get(UpdateTrackerListUseCase::class.java) + + updateTrackersUsecase.updateTrackers() + return Result.success() + } + + companion object { + private val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + fun periodicUpdate(context: Context) { + val request = PeriodicWorkRequestBuilder( + 7, TimeUnit.DAYS + ) + .setConstraints(constraints).build() + + WorkManager.getInstance(context).enqueueUniquePeriodicWork( + UpdateTrackersWorker::class.qualifiedName ?: "", + ExistingPeriodicWorkPolicy.KEEP, + request + ) + } + } +} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt deleted file mode 100644 index 44793a4..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers - -import android.content.Context -import android.content.pm.PackageManager -import android.net.LocalServerSocket -import android.system.ErrnoException -import android.system.Os -import android.system.OsConstants -import android.util.Log -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository -import java.io.BufferedReader -import java.io.IOException -import java.io.InputStreamReader -import java.io.PrintWriter - -class DNSBlockerRunnable( - context: Context, - private val trackersLogger: TrackersLogger, - private val trackersRepository: TrackersRepository, - private val whitelistRepository: WhitelistRepository -) : Runnable { - var resolverReceiver: LocalServerSocket? = null - var stopped = false - private var eBrowserAppUid = -1 - - companion object { - private const val SOCKET_NAME = "foundation.e.advancedprivacy" - private const val E_BROWSER_DOT_SERVER = "chrome.cloudflare-dns.com" - private const val TAG = "DNSBlockerRunnable" - } - - init { - initEBrowserDoTFix(context) - } - - @Synchronized - fun stop() { - stopped = true - closeSocket() - } - - private fun closeSocket() { - // Known bug and workaround that LocalServerSocket::close is not working well - // https://issuetracker.google.com/issues/36945762 - if (resolverReceiver != null) { - try { - Os.shutdown(resolverReceiver!!.fileDescriptor, OsConstants.SHUT_RDWR) - resolverReceiver!!.close() - resolverReceiver = null - } catch (e: ErrnoException) { - if (e.errno != OsConstants.EBADF) { - Log.w(TAG, "Socket already closed") - } else { - Log.e(TAG, "Exception: cannot close DNS port on stop $SOCKET_NAME !", e) - } - } catch (e: Exception) { - Log.e(TAG, "Exception: cannot close DNS port on stop $SOCKET_NAME !", e) - } - } - } - - override fun run() { - val resolverReceiver = try { - LocalServerSocket(SOCKET_NAME) - } catch (eio: IOException) { - Log.e(TAG, "Exception:Cannot open DNS port $SOCKET_NAME !", eio) - return - } - - this.resolverReceiver = resolverReceiver - Log.d(TAG, "DNSFilterProxy running on port $SOCKET_NAME !") - - while (!stopped) { - try { - val socket = resolverReceiver.accept() - val reader = BufferedReader(InputStreamReader(socket.inputStream)) - val line = reader.readLine() - val params = line.split(",").toTypedArray() - val output = socket.outputStream - val writer = PrintWriter(output, true) - val domainName = params[0] - val appUid = params[1].toInt() - var isBlocked = false - if (isEBrowserDoTBlockFix(appUid, domainName)) { - isBlocked = true - } else if (trackersRepository.isTracker(domainName)) { - val trackerId = trackersRepository.getTrackerId(domainName) - if (shouldBlock(appUid, trackerId)) { - writer.println("block") - isBlocked = true - } - trackersLogger.logAccess(trackerId, appUid, isBlocked) - } - if (!isBlocked) { - writer.println("pass") - } - socket.close() - // Printing bufferedreader data - } catch (e: IOException) { - Log.w(TAG, "Exception while listening DNS resolver", e) - } - } - } - - private fun initEBrowserDoTFix(context: Context) { - try { - eBrowserAppUid = - context.packageManager.getApplicationInfo("foundation.e.browser", 0).uid - } catch (e: PackageManager.NameNotFoundException) { - Log.i(TAG, "no E Browser package found.") - } - } - - private fun isEBrowserDoTBlockFix(appUid: Int, hostname: String): Boolean { - return appUid == eBrowserAppUid && E_BROWSER_DOT_SERVER == hostname - } - - private fun shouldBlock(appUid: Int, trackerId: String?): Boolean { - return whitelistRepository.isBlockingEnabled && - !whitelistRepository.isWhiteListed(appUid, trackerId) - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt deleted file mode 100644 index c2ad16b..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerService.kt +++ /dev/null @@ -1,79 +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 . - */ - -package foundation.e.privacymodules.trackers - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import android.util.Log -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository - -class DNSBlockerService : Service() { - private var trackersLogger: TrackersLogger? = null - - companion object { - private const val TAG = "DNSBlockerService" - private var sDNSBlocker: DNSBlockerRunnable? = null - const val ACTION_START = "foundation.e.privacymodules.trackers.intent.action.START" - const val EXTRA_ENABLE_NOTIFICATION = - "foundation.e.privacymodules.trackers.intent.extra.ENABLED_NOTIFICATION" - } - - override fun onBind(intent: Intent): IBinder? { - // TODO: Return the communication channel to the service. - throw UnsupportedOperationException("Not yet implemented") - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (ACTION_START == intent?.action) { - if (intent.getBooleanExtra(EXTRA_ENABLE_NOTIFICATION, true)) { - ForegroundStarter.startForeground(this) - } - stop() - start() - } - return START_REDELIVER_INTENT - } - - private fun start() { - try { - val trackersLogger = TrackersLogger(this) - this.trackersLogger = trackersLogger - - sDNSBlocker = DNSBlockerRunnable( - this, - trackersLogger, - TrackersRepository.getInstance(), - WhitelistRepository.getInstance(this) - ) - Thread(sDNSBlocker).start() - } catch (e: Exception) { - Log.e(TAG, "Error while starting DNSBlocker service", e) - stop() - } - } - - private fun stop() { - sDNSBlocker?.stop() - sDNSBlocker = null - - trackersLogger?.stop() - trackersLogger = null - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt deleted file mode 100644 index 69b4f28..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/ForegroundStarter.kt +++ /dev/null @@ -1,45 +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 . - */ - -package foundation.e.privacymodules.trackers - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.Service -import android.content.Context -import android.os.Build - -object ForegroundStarter { - private const val NOTIFICATION_CHANNEL_ID = "blocker_service" - fun startForeground(service: Service) { - val mNotificationManager = - service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (Build.VERSION.SDK_INT >= 26) { - mNotificationManager.createNotificationChannel( - NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_ID, - NotificationManager.IMPORTANCE_LOW - ) - ) - val notification = Notification.Builder(service, NOTIFICATION_CHANNEL_ID) - .setContentTitle("Trackers filter").build() - service.startForeground(1337, notification) - } - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt deleted file mode 100644 index f3c4745..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers - -import android.content.Context -import android.util.Log -import foundation.e.privacymodules.trackers.data.StatsRepository -import java.util.concurrent.LinkedBlockingQueue - -class TrackersLogger(context: Context) { - private val statsRepository = StatsRepository.getInstance(context) - private val queue = LinkedBlockingQueue() - private var stopped = false - - companion object { - private const val TAG = "TrackerModule" - } - - init { - startWriteLogLoop() - } - - fun stop() { - stopped = true - } - - fun logAccess(trackerId: String?, appUid: Int, wasBlocked: Boolean) { - queue.offer(DetectedTracker(trackerId, appUid, wasBlocked)) - } - - private fun startWriteLogLoop() { - val writeLogRunner = Runnable { - while (!stopped) { - try { - logAccess(queue.take()) - } catch (e: InterruptedException) { - Log.e(TAG, "writeLogLoop detectedTrackersQueue.take() interrupted: ", e) - } - } - } - Thread(writeLogRunner).start() - } - - fun logAccess(detectedTracker: DetectedTracker) { - statsRepository.logAccess( - detectedTracker.trackerId, - detectedTracker.appUid, - detectedTracker.wasBlocked - ) - } - - inner class DetectedTracker(var trackerId: String?, var appUid: Int, var wasBlocked: Boolean) -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt deleted file mode 100644 index 7463b22..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.api - -import android.content.Context -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository - -class BlockTrackersPrivacyModule(context: Context) : IBlockTrackersPrivacyModule { - private val mListeners = mutableListOf() - private val trackersRepository = TrackersRepository.getInstance() - private val whitelistRepository = WhitelistRepository.getInstance(context) - - companion object { - private var instance: BlockTrackersPrivacyModule? = null - - fun getInstance(context: Context): BlockTrackersPrivacyModule { - return instance ?: BlockTrackersPrivacyModule(context).apply { instance = this } - } - } - - override fun addListener(listener: IBlockTrackersPrivacyModule.Listener) { - mListeners.add(listener) - } - - override fun clearListeners() { - mListeners.clear() - } - - override fun disableBlocking() { - whitelistRepository.isBlockingEnabled = false - mListeners.forEach { listener -> listener.onBlockingToggle(false) } - } - - override fun enableBlocking() { - whitelistRepository.isBlockingEnabled = true - mListeners.forEach { listener -> listener.onBlockingToggle(true) } - } - - override fun getWhiteList(app: ApplicationDescription): List { - return whitelistRepository.getWhiteListForApp(app).mapNotNull { - trackersRepository.getTracker(it) - } - } - - override fun getWhiteListedApp(): List { - return whitelistRepository.getWhiteListedApp() - } - - override fun isBlockingEnabled(): Boolean { - return whitelistRepository.isBlockingEnabled - } - - override fun isWhiteListEmpty(): Boolean { - return whitelistRepository.areWhiteListEmpty() - } - - override fun isWhitelisted(app: ApplicationDescription): Boolean { - return whitelistRepository.isAppWhiteListed(app) - } - - override fun removeListener(listener: IBlockTrackersPrivacyModule.Listener) { - mListeners.remove(listener) - } - - override fun setWhiteListed( - tracker: Tracker, - app: ApplicationDescription, - isWhiteListed: Boolean - ) { - whitelistRepository.setWhiteListed(tracker, app.apId, isWhiteListed) - } - - override fun setWhiteListed(app: ApplicationDescription, isWhiteListed: Boolean) { - whitelistRepository.setWhiteListed(app.apId, isWhiteListed) - } - - override fun clearWhiteList(app: ApplicationDescription) { - whitelistRepository.clearWhiteList(app.apId) - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt deleted file mode 100644 index 3547b8e..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.api - -import foundation.e.privacymodules.permissions.data.ApplicationDescription - -/** - * Manage trackers blocking and whitelisting. - */ -interface IBlockTrackersPrivacyModule { - - /** - * Get the state of the blockin module - * @return true when blocking is enabled, false otherwise. - */ - fun isBlockingEnabled(): Boolean - - /** - * Enable blocking, using the previously configured whitelists - */ - fun enableBlocking() - - /** - * Disable blocking - */ - fun disableBlocking() - - /** - * Set or unset in whitelist the App with the specified uid. - * @param app the ApplicationDescription of the app - * @param isWhiteListed true, the app will appears in whitelist, false, it won't - */ - fun setWhiteListed(app: ApplicationDescription, isWhiteListed: Boolean) - - /** - * Set or unset in whitelist the specifid tracked, for the App specified by its uid. - * @param tracker the tracker - * @param app the ApplicationDescription of the app - * @param isWhiteListed true, the app will appears in whitelist, false, it won't - */ - fun setWhiteListed(tracker: Tracker, app: ApplicationDescription, isWhiteListed: Boolean) - - /** - * Return true if nothing has been added to the whitelist : everything is blocked. - */ - fun isWhiteListEmpty(): Boolean - - /** - * Return the white listed App, by their UID - */ - fun getWhiteListedApp(): List - - /** - * Return true if the App is whitelisted for trackers blocking. - */ - fun isWhitelisted(app: ApplicationDescription): Boolean - - /** - * List the white listed trackers for an App specified by it uid - */ - fun getWhiteList(app: ApplicationDescription): List - - fun clearWhiteList(app: ApplicationDescription) - - /** - * Callback interface to get updates about the state of the Block trackers module. - */ - interface Listener { - - /** - * Called when the trackers blocking is activated or deactivated. - * @param isBlocking true when activated, false otherwise. - */ - fun onBlockingToggle(isBlocking: Boolean) - } - - fun addListener(listener: Listener) - - fun removeListener(listener: Listener) - - fun clearListeners() -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt deleted file mode 100644 index 8aaed4a..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.api - -import foundation.e.privacymodules.permissions.data.ApplicationDescription - -/** - * Get reporting about trackers calls. - */ -interface ITrackTrackersPrivacyModule { - - fun start( - trackers: List, - getAppByUid: (Int) -> ApplicationDescription?, - getAppByAPId: (String) -> ApplicationDescription?, - enableNotification: Boolean = true - ) - - /** - * List all the trackers encountered for a specific app. - */ - fun getTrackersForApp(app: ApplicationDescription): List - - /** - * List all the trackers encountere trackers since "ever", for the given [appUids], - * or all apps if [appUids] is null - */ - fun getTrackers(apps: List? = null): List - - /** - * Return the number of encountered trackers since "ever", for the given [appUids], - * or all apps if [appUids] is null - */ - fun getTrackersCount(): Int - - /** - * Return the number of encountere trackers since "ever", for each app uid. - */ - fun getTrackersCountByApp(): Map - - /** - * Return the number of encountered trackers for the last 24 hours - */ - fun getPastDayTrackersCount(): Int - - /** - * Return the number of encountered trackers for the last month - */ - fun getPastMonthTrackersCount(): Int - - /** - * Return the number of encountered trackers for the last year - */ - fun getPastYearTrackersCount(): Int - - /** - * Return number of trackers calls by hours, for the last 24hours. - * @return list of 24 numbers of trackers calls by hours - */ - fun getPastDayTrackersCalls(): List> - - /** - * Return number of trackers calls by day, for the last 30 days. - * @return list of 30 numbers of trackers calls by day - */ - fun getPastMonthTrackersCalls(): List> - - /** - * Return number of trackers calls by month, for the last 12 month. - * @return list of 12 numbers of trackers calls by month - */ - fun getPastYearTrackersCalls(): List> - - fun getPastDayTrackersCallsByApps(): Map> - - fun getPastDayTrackersCallsForApp(app: ApplicationDescription): Pair - - fun getPastDayMostLeakedApp(): ApplicationDescription? - - interface Listener { - - /** - * Called when a new tracker attempt is logged. Consumer may choose to call other methods - * to refresh the data. - */ - fun onNewData() - } - - fun addListener(listener: Listener) - - fun removeListener(listener: Listener) - - fun clearListeners() -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt deleted file mode 100644 index 5fc5b6b..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.privacymodules.trackers.api - -import android.content.Context -import android.content.Intent -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.DNSBlockerService -import foundation.e.privacymodules.trackers.data.StatsRepository -import foundation.e.privacymodules.trackers.data.TrackersRepository -import foundation.e.privacymodules.trackers.data.WhitelistRepository -import java.time.temporal.ChronoUnit - -class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersPrivacyModule { - private val statsRepository = StatsRepository.getInstance(context) - private val listeners: MutableList = mutableListOf() - - companion object { - private var instance: TrackTrackersPrivacyModule? = null - - fun getInstance(context: Context): TrackTrackersPrivacyModule { - return instance ?: TrackTrackersPrivacyModule(context).apply { instance = this } - } - } - - init { - statsRepository.setNewDataCallback { - listeners.forEach { listener -> listener.onNewData() } - } - } - - override fun start( - trackers: List, - getAppByUid: (Int) -> ApplicationDescription?, - getAppByAPId: (String) -> ApplicationDescription?, - enableNotification: Boolean - ) { - TrackersRepository.getInstance().setTrackersList(trackers) - StatsRepository.getInstance(context).setAppGetters(getAppByUid, getAppByAPId) - WhitelistRepository.getInstance(context).setAppGetters(context, getAppByAPId, getAppByUid) - val intent = Intent(context, DNSBlockerService::class.java) - intent.action = DNSBlockerService.ACTION_START - intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, enableNotification) - context.startService(intent) - } - - override fun getPastDayTrackersCalls(): List> { - return statsRepository.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS) - } - - override fun getPastMonthTrackersCalls(): List> { - return statsRepository.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS) - } - - override fun getPastYearTrackersCalls(): List> { - return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS) - } - - override fun getTrackersCount(): Int { - return statsRepository.getContactedTrackersCount() - } - - override fun getTrackersCountByApp(): Map { - return statsRepository.getContactedTrackersCountByApp() - } - - override fun getTrackersForApp(app: ApplicationDescription): List { - return statsRepository.getTrackers(listOf(app)) - } - - override fun getTrackers(apps: List?): List { - return statsRepository.getTrackers(apps) - } - - override fun getPastDayTrackersCount(): Int { - return statsRepository.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) - } - - override fun getPastMonthTrackersCount(): Int { - return statsRepository.getActiveTrackersByPeriod(30, ChronoUnit.DAYS) - } - - override fun getPastYearTrackersCount(): Int { - return statsRepository.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS) - } - - override fun getPastDayMostLeakedApp(): ApplicationDescription? { - return statsRepository.getMostLeakedApp(24, ChronoUnit.HOURS) - } - - override fun getPastDayTrackersCallsByApps(): Map> { - return statsRepository.getCallsByApps(24, ChronoUnit.HOURS) - } - - override fun getPastDayTrackersCallsForApp(app: ApplicationDescription): Pair { - return statsRepository.getCalls(app, 24, ChronoUnit.HOURS) - } - - override fun addListener(listener: ITrackTrackersPrivacyModule.Listener) { - listeners.add(listener) - } - - override fun removeListener(listener: ITrackTrackersPrivacyModule.Listener) { - listeners.remove(listener) - } - - override fun clearListeners() { - listeners.clear() - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt deleted file mode 100644 index 2da5b16..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/Tracker.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.api - -/** - * Describe a tracker. - */ -data class Tracker( - val id: String, - val hostnames: Set, - val label: String, - val exodusId: String? -) diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt deleted file mode 100644 index 4d287d4..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt +++ /dev/null @@ -1,459 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.data - -import android.content.ContentValues -import android.content.Context -import android.database.Cursor -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import android.provider.BaseColumns -import androidx.core.database.getStringOrNull -import foundation.e.privacymodules.trackers.api.Tracker -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_APPID -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER -import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME -import timber.log.Timber -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.time.temporal.ChronoUnit -import java.time.temporal.TemporalUnit -import java.util.concurrent.TimeUnit - -class StatsDatabase(context: Context) : - SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { - - companion object { - const val DATABASE_VERSION = 2 - const val DATABASE_NAME = "TrackerFilterStats.db" - private const val SQL_CREATE_TABLE = "CREATE TABLE $TABLE_NAME (" + - "${BaseColumns._ID} INTEGER PRIMARY KEY," + - "$COLUMN_NAME_TIMESTAMP INTEGER," + - "$COLUMN_NAME_TRACKER TEXT," + - "$COLUMN_NAME_NUMBER_CONTACTED INTEGER," + - "$COLUMN_NAME_NUMBER_BLOCKED INTEGER," + - "$COLUMN_NAME_APPID TEXT)" - - private const val PROJECTION_NAME_PERIOD = "period" - private const val PROJECTION_NAME_CONTACTED_SUM = "contactedsum" - private const val PROJECTION_NAME_BLOCKED_SUM = "blockedsum" - private const val PROJECTION_NAME_LEAKED_SUM = "leakedsum" - private const val PROJECTION_NAME_TRACKERS_COUNT = "trackerscount" - - private val MIGRATE_1_2 = listOf( - "ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_NAME_APPID TEXT" - // "ALTER TABLE $TABLE_NAME DROP COLUMN app_uid" - // DROP COLUMN is available since sqlite 3.35.0, and sdk29 as 3.22.0, sdk32 as 3.32.2 - ) - } - - object AppTrackerEntry : BaseColumns { - const val TABLE_NAME = "tracker_filter_stats" - const val COLUMN_NAME_TIMESTAMP = "timestamp" - const val COLUMN_NAME_TRACKER = "tracker" - const val COLUMN_NAME_NUMBER_CONTACTED = "sum_contacted" - const val COLUMN_NAME_NUMBER_BLOCKED = "sum_blocked" - const val COLUMN_NAME_APPID = "app_apid" - } - - private var projection = arrayOf( - COLUMN_NAME_TIMESTAMP, - COLUMN_NAME_TRACKER, - COLUMN_NAME_NUMBER_CONTACTED, - COLUMN_NAME_NUMBER_BLOCKED, - COLUMN_NAME_APPID - ) - - private val lock = Any() - private val trackersRepository = TrackersRepository.getInstance() - - override fun onCreate(db: SQLiteDatabase) { - db.execSQL(SQL_CREATE_TABLE) - } - - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - if (oldVersion == 1 && newVersion == 2) { - MIGRATE_1_2.forEach(db::execSQL) - } else { - Timber.e( - "Unexpected database versions: oldVersion: $oldVersion ; newVersion: $newVersion" - ) - } - } - - private fun getCallsByPeriod( - periodsCount: Int, - periodUnit: TemporalUnit, - sqlitePeriodFormat: String - ): Map> { - synchronized(lock) { - val minTimestamp = getPeriodStartTs(periodsCount, periodUnit) - val db = readableDatabase - val selection = "$COLUMN_NAME_TIMESTAMP >= ?" - val selectionArg = arrayOf("" + minTimestamp) - - val projection = ( - "$COLUMN_NAME_TIMESTAMP, " + - "STRFTIME('$sqlitePeriodFormat', DATETIME($COLUMN_NAME_TIMESTAMP, 'unixepoch', 'localtime')) $PROJECTION_NAME_PERIOD," + - "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM, " + - "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" - ) - - val cursor = db.rawQuery( - "SELECT $projection FROM $TABLE_NAME WHERE $selection" + - " GROUP BY $PROJECTION_NAME_PERIOD" + - " ORDER BY $COLUMN_NAME_TIMESTAMP DESC LIMIT $periodsCount", - selectionArg - ) - val callsByPeriod = HashMap>() - while (cursor.moveToNext()) { - val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) - val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) - callsByPeriod[cursor.getString(PROJECTION_NAME_PERIOD)] = blocked to contacted - blocked - } - cursor.close() - db.close() - return callsByPeriod - } - } - - private fun callsByPeriodToPeriodsList( - callsByPeriod: Map>, - periodsCount: Int, - periodUnit: TemporalUnit, - javaPeriodFormat: String - ): List> { - var currentDate = ZonedDateTime.now().minus(periodsCount.toLong(), periodUnit) - val formatter = DateTimeFormatter.ofPattern(javaPeriodFormat) - val calls = mutableListOf>() - for (i in 0 until periodsCount) { - currentDate = currentDate.plus(1, periodUnit) - val currentPeriod = formatter.format(currentDate) - calls.add(callsByPeriod.getOrDefault(currentPeriod, 0 to 0)) - } - return calls - } - - fun getTrackersCallsOnPeriod( - periodsCount: Int, - periodUnit: TemporalUnit - ): List> { - var sqlitePeriodFormat = "%Y%m" - var javaPeriodFormat = "yyyyMM" - if (periodUnit === ChronoUnit.MONTHS) { - sqlitePeriodFormat = "%Y%m" - javaPeriodFormat = "yyyyMM" - } else if (periodUnit === ChronoUnit.DAYS) { - sqlitePeriodFormat = "%Y%m%d" - javaPeriodFormat = "yyyyMMdd" - } else if (periodUnit === ChronoUnit.HOURS) { - sqlitePeriodFormat = "%Y%m%d%H" - javaPeriodFormat = "yyyyMMddHH" - } - val callsByPeriod = getCallsByPeriod(periodsCount, periodUnit, sqlitePeriodFormat) - return callsByPeriodToPeriodsList(callsByPeriod, periodsCount, periodUnit, javaPeriodFormat) - } - - fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int { - synchronized(lock) { - val minTimestamp = getPeriodStartTs(periodsCount, periodUnit) - val db = writableDatabase - val selection = "$COLUMN_NAME_TIMESTAMP >= ? AND " + - "$COLUMN_NAME_NUMBER_CONTACTED > $COLUMN_NAME_NUMBER_BLOCKED" - val selectionArg = arrayOf("" + minTimestamp) - val projection = - "COUNT(DISTINCT $COLUMN_NAME_TRACKER) $PROJECTION_NAME_TRACKERS_COUNT" - - val cursor = db.rawQuery( - "SELECT $projection FROM $TABLE_NAME WHERE $selection", - selectionArg - ) - var count = 0 - if (cursor.moveToNext()) { - count = cursor.getInt(0) - } - cursor.close() - db.close() - return count - } - } - - fun getContactedTrackersCount(): Int { - synchronized(lock) { - val db = readableDatabase - var query = "SELECT DISTINCT $COLUMN_NAME_TRACKER FROM $TABLE_NAME" - - val cursor = db.rawQuery(query, arrayOf()) - var count = 0 - while (cursor.moveToNext()) { - trackersRepository.getTracker(cursor.getString(COLUMN_NAME_TRACKER))?.let { - count++ - } - } - cursor.close() - db.close() - return count - } - } - - fun getContactedTrackersCountByAppId(): Map { - synchronized(lock) { - val db = readableDatabase - val projection = "$COLUMN_NAME_APPID, $COLUMN_NAME_TRACKER" - val cursor = db.rawQuery( - "SELECT DISTINCT $projection FROM $TABLE_NAME", // + - arrayOf() - ) - val countByApp = mutableMapOf() - while (cursor.moveToNext()) { - trackersRepository.getTracker(cursor.getString(COLUMN_NAME_TRACKER))?.let { - val appId = cursor.getString(COLUMN_NAME_APPID) - countByApp[appId] = countByApp.getOrDefault(appId, 0) + 1 - } - } - cursor.close() - db.close() - return countByApp - } - } - - fun getCallsByAppIds(periodCount: Int, periodUnit: TemporalUnit): Map> { - synchronized(lock) { - val minTimestamp = getPeriodStartTs(periodCount, periodUnit) - val db = readableDatabase - val selection = "$COLUMN_NAME_TIMESTAMP >= ?" - val selectionArg = arrayOf("" + minTimestamp) - val projection = "$COLUMN_NAME_APPID, " + - "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," + - "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" - val cursor = db.rawQuery( - "SELECT $projection FROM $TABLE_NAME" + - " WHERE $selection" + - " GROUP BY $COLUMN_NAME_APPID", - selectionArg - ) - val callsByApp = HashMap>() - while (cursor.moveToNext()) { - val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) - val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) - callsByApp[cursor.getString(COLUMN_NAME_APPID)] = blocked to contacted - blocked - } - cursor.close() - db.close() - return callsByApp - } - } - - fun getCalls(appId: String, periodCount: Int, periodUnit: TemporalUnit): Pair { - synchronized(lock) { - val minTimestamp = getPeriodStartTs(periodCount, periodUnit) - val db = readableDatabase - val selection = "$COLUMN_NAME_APPID = ? AND " + - "$COLUMN_NAME_TIMESTAMP >= ?" - val selectionArg = arrayOf("" + appId, "" + minTimestamp) - val projection = - "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," + - "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM" - val cursor = db.rawQuery( - "SELECT $projection FROM $TABLE_NAME WHERE $selection", - selectionArg - ) - var calls: Pair = 0 to 0 - if (cursor.moveToNext()) { - val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) - val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) - calls = blocked to contacted - blocked - } - cursor.close() - db.close() - return calls - } - } - - fun getMostLeakedAppId(periodCount: Int, periodUnit: TemporalUnit): String { - synchronized(lock) { - val minTimestamp = getPeriodStartTs(periodCount, periodUnit) - val db = readableDatabase - val selection = "$COLUMN_NAME_TIMESTAMP >= ?" - val selectionArg = arrayOf("" + minTimestamp) - val projection = "$COLUMN_NAME_APPID, " + - "SUM($COLUMN_NAME_NUMBER_CONTACTED - $COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_LEAKED_SUM" - val cursor = db.rawQuery( - "SELECT $projection FROM $TABLE_NAME" + - " WHERE $selection" + - " GROUP BY $COLUMN_NAME_APPID" + - " ORDER BY $PROJECTION_NAME_LEAKED_SUM DESC LIMIT 1", - selectionArg - ) - var appId = "" - if (cursor.moveToNext()) { - appId = cursor.getString(COLUMN_NAME_APPID) - } - cursor.close() - db.close() - return appId - } - } - - fun logAccess(trackerId: String?, appId: String, blocked: Boolean) { - synchronized(lock) { - val currentHour = getCurrentHourTs() - val db = writableDatabase - val values = ContentValues() - values.put(COLUMN_NAME_APPID, appId) - values.put(COLUMN_NAME_TRACKER, trackerId) - values.put(COLUMN_NAME_TIMESTAMP, currentHour) - - /*String query = "UPDATE product SET "+COLUMN_NAME_NUMBER_CONTACTED+" = "+COLUMN_NAME_NUMBER_CONTACTED+" + 1 "; - if(blocked) - query+=COLUMN_NAME_NUMBER_BLOCKED+" = "+COLUMN_NAME_NUMBER_BLOCKED+" + 1 "; -*/ - val selection = "$COLUMN_NAME_TIMESTAMP = ? AND " + - "$COLUMN_NAME_APPID = ? AND " + - "$COLUMN_NAME_TRACKER = ? " - val selectionArg = arrayOf("" + currentHour, "" + appId, trackerId) - val cursor = db.query( - TABLE_NAME, - projection, - selection, - selectionArg, - null, - null, - null - ) - if (cursor.count > 0) { - cursor.moveToFirst() - val entry = cursorToEntry(cursor) - if (blocked) values.put( - COLUMN_NAME_NUMBER_BLOCKED, - entry.sum_blocked + 1 - ) else values.put(COLUMN_NAME_NUMBER_BLOCKED, entry.sum_blocked) - values.put(COLUMN_NAME_NUMBER_CONTACTED, entry.sum_contacted + 1) - db.update(TABLE_NAME, values, selection, selectionArg) - - // db.execSQL(query, new String[]{""+hour, ""+day, ""+month, ""+year, ""+appUid, ""+trackerId}); - } else { - if (blocked) values.put( - COLUMN_NAME_NUMBER_BLOCKED, - 1 - ) else values.put(COLUMN_NAME_NUMBER_BLOCKED, 0) - values.put(COLUMN_NAME_NUMBER_CONTACTED, 1) - db.insert(TABLE_NAME, null, values) - } - cursor.close() - db.close() - } - } - - private fun cursorToEntry(cursor: Cursor): StatEntry { - val entry = StatEntry() - entry.timestamp = cursor.getLong(COLUMN_NAME_TIMESTAMP) - entry.appId = cursor.getString(COLUMN_NAME_APPID) - entry.sum_blocked = cursor.getInt(COLUMN_NAME_NUMBER_BLOCKED) - entry.sum_contacted = cursor.getInt(COLUMN_NAME_NUMBER_CONTACTED) - entry.tracker = cursor.getInt(COLUMN_NAME_TRACKER) - return entry - } - - fun getTrackers(appIds: List?): List { - synchronized(lock) { - val columns = arrayOf(COLUMN_NAME_TRACKER, COLUMN_NAME_APPID) - var selection: String? = null - - var selectionArg: Array? = null - appIds?.let { appIds -> - selection = "$COLUMN_NAME_APPID IN (${appIds.joinToString(", ") { "'$it'" }})" - selectionArg = arrayOf() - } - - val db = readableDatabase - val cursor = db.query( - true, - TABLE_NAME, - columns, - selection, - selectionArg, - null, - null, - null, - null - ) - val trackers: MutableList = ArrayList() - while (cursor.moveToNext()) { - val trackerId = cursor.getString(COLUMN_NAME_TRACKER) - val tracker = trackersRepository.getTracker(trackerId) - if (tracker != null) { - trackers.add(tracker) - } - } - cursor.close() - db.close() - return trackers - } - } - - class StatEntry { - var appId = "" - var sum_contacted = 0 - var sum_blocked = 0 - var timestamp: Long = 0 - var tracker = 0 - } - - private fun getCurrentHourTs(): Long { - val hourInMs = TimeUnit.HOURS.toMillis(1L) - val hourInS = TimeUnit.HOURS.toSeconds(1L) - return System.currentTimeMillis() / hourInMs * hourInS - } - - private fun getPeriodStartTs( - periodsCount: Int, - periodUnit: TemporalUnit - ): Long { - var start = ZonedDateTime.now() - .minus(periodsCount.toLong(), periodUnit) - .plus(1, periodUnit) - var truncatePeriodUnit = periodUnit - if (periodUnit === ChronoUnit.MONTHS) { - start = start.withDayOfMonth(1) - truncatePeriodUnit = ChronoUnit.DAYS - } - return start.truncatedTo(truncatePeriodUnit).toEpochSecond() - } - - private fun Cursor.getInt(columnName: String): Int { - val columnIndex = getColumnIndex(columnName) - return if (columnIndex >= 0) getInt(columnIndex) else 0 - } - - private fun Cursor.getLong(columnName: String): Long { - val columnIndex = getColumnIndex(columnName) - return if (columnIndex >= 0) getLong(columnIndex) else 0 - } - - private fun Cursor.getString(columnName: String): String { - val columnIndex = getColumnIndex(columnName) - return if (columnIndex >= 0) { - getStringOrNull(columnIndex) ?: "" - } else "" - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt deleted file mode 100644 index 8f02adb..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.data - -import android.content.Context -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.Tracker -import java.time.temporal.TemporalUnit - -class StatsRepository private constructor(context: Context) { - private val database: StatsDatabase - private var newDataCallback: (() -> Unit)? = null - private var getAppByUid: ((Int) -> ApplicationDescription?)? = null - private var getAppByAPId: ((String) -> ApplicationDescription?)? = null - - companion object { - private var instance: StatsRepository? = null - fun getInstance(context: Context): StatsRepository { - return instance ?: StatsRepository(context).apply { instance = this } - } - } - - fun setAppGetters( - getAppByUid: (Int) -> ApplicationDescription?, - getAppByAPId: (String) -> ApplicationDescription? - ) { - this.getAppByUid = getAppByUid - this.getAppByAPId = getAppByAPId - } - - init { - database = StatsDatabase(context) - } - - fun setNewDataCallback(callback: () -> Unit) { - newDataCallback = callback - } - - fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) { - getAppByUid?.invoke(appUid)?.let { app -> - database.logAccess(trackerId, app.apId, blocked) - newDataCallback?.invoke() - } - } - - fun getTrackersCallsOnPeriod( - periodsCount: Int, - periodUnit: TemporalUnit - ): List> { - return database.getTrackersCallsOnPeriod(periodsCount, periodUnit) - } - - fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int { - return database.getActiveTrackersByPeriod(periodsCount, periodUnit) - } - - fun getContactedTrackersCountByApp(): Map { - return database.getContactedTrackersCountByAppId().mapByAppIdToApp() - } - - fun getContactedTrackersCount(): Int { - return database.getContactedTrackersCount() - } - - fun getTrackers(apps: List?): List { - return database.getTrackers(apps?.map { it.apId }) - } - - fun getCallsByApps( - periodCount: Int, - periodUnit: TemporalUnit - ): Map> { - return database.getCallsByAppIds(periodCount, periodUnit).mapByAppIdToApp() - } - - fun getCalls(app: ApplicationDescription, periodCount: Int, periodUnit: TemporalUnit): Pair { - return database.getCalls(app.apId, periodCount, periodUnit) - } - - fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): ApplicationDescription? { - return getAppByAPId?.invoke(database.getMostLeakedAppId(periodCount, periodUnit)) - } - - private fun Map.mapByAppIdToApp(): Map { - return entries.mapNotNull { (apId, value) -> - getAppByAPId?.invoke(apId)?.let { it to value } - }.toMap() - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt deleted file mode 100644 index 994bccf..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/TrackersRepository.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.data - -import foundation.e.privacymodules.trackers.api.Tracker - -class TrackersRepository private constructor() { - private var trackersById: Map = HashMap() - private var hostnameToId: Map = HashMap() - - companion object { - private var instance: TrackersRepository? = null - fun getInstance(): TrackersRepository { - return instance ?: TrackersRepository().apply { instance = this } - } - } - - fun setTrackersList(list: List) { - val trackersById: MutableMap = HashMap() - val hostnameToId: MutableMap = HashMap() - list.forEach { tracker -> - trackersById[tracker.id] = tracker - for (hostname in tracker.hostnames) { - hostnameToId[hostname] = tracker.id - } - } - this.trackersById = trackersById - this.hostnameToId = hostnameToId - } - - fun isTracker(hostname: String?): Boolean { - return hostnameToId.containsKey(hostname) - } - - fun getTrackerId(hostname: String?): String? { - return hostnameToId[hostname] - } - - fun getTracker(id: String?): Tracker? { - return trackersById[id] - } -} diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt deleted file mode 100644 index 2763d06..0000000 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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 . - */ - -package foundation.e.privacymodules.trackers.data - -import android.content.Context -import android.content.SharedPreferences -import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.Tracker -import java.io.File - -class WhitelistRepository private constructor(context: Context) { - private var appsWhitelist: Set = HashSet() - private var appUidsWhitelist: Set = HashSet() - - private var trackersWhitelistByApp: MutableMap> = HashMap() - private var trackersWhitelistByUid: Map> = HashMap() - - private val prefs: SharedPreferences - private var getAppByAPId: ((String) -> ApplicationDescription?)? = null - - companion object { - private const val SHARED_PREFS_FILE = "trackers_whitelist_v2" - private const val KEY_BLOCKING_ENABLED = "blocking_enabled" - private const val KEY_APPS_WHITELIST = "apps_whitelist" - private const val KEY_APP_TRACKERS_WHITELIST_PREFIX = "app_trackers_whitelist_" - - private const val SHARED_PREFS_FILE_V1 = "trackers_whitelist.prefs" - - private var instance: WhitelistRepository? = null - fun getInstance(context: Context): WhitelistRepository { - return instance ?: WhitelistRepository(context).apply { instance = this } - } - } - - init { - prefs = context.getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE) - reloadCache() - } - - fun setAppGetters( - context: Context, - getAppByAPId: (String) -> ApplicationDescription?, - getAppByUid: (Int) -> ApplicationDescription? - ) { - this.getAppByAPId = getAppByAPId - migrate(context, getAppByUid) - } - - private fun migrate(context: Context, getAppByUid: (Int) -> ApplicationDescription?) { - if (context.sharedPreferencesExists(SHARED_PREFS_FILE_V1)) { - migrate1To2(context, getAppByUid) - } - } - - private fun Context.sharedPreferencesExists(fileName: String): Boolean { - return File( - "${applicationInfo.dataDir}/shared_prefs/$fileName.xml" - ).exists() - } - - private fun migrate1To2(context: Context, getAppByUid: (Int) -> ApplicationDescription?) { - val prefsV1 = context.getSharedPreferences(SHARED_PREFS_FILE_V1, Context.MODE_PRIVATE) - val editorV2 = prefs.edit() - - editorV2.putBoolean(KEY_BLOCKING_ENABLED, prefsV1.getBoolean(KEY_BLOCKING_ENABLED, false)) - - val apIds = prefsV1.getStringSet(KEY_APPS_WHITELIST, HashSet())?.mapNotNull { - try { - val uid = it.toInt() - getAppByUid(uid)?.apId - } catch (e: Exception) { null } - }?.toSet() ?: HashSet() - - editorV2.putStringSet(KEY_APPS_WHITELIST, apIds) - - prefsV1.all.keys.forEach { key -> - if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) { - try { - val uid = key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length).toInt() - val apId = getAppByUid(uid)?.apId - apId?.let { - val trackers = prefsV1.getStringSet(key, emptySet()) - editorV2.putStringSet(buildAppTrackersKey(apId), trackers) - } - } catch (e: Exception) { } - } - } - editorV2.commit() - - context.deleteSharedPreferences(SHARED_PREFS_FILE_V1) - - reloadCache() - } - - private fun reloadCache() { - isBlockingEnabled = prefs.getBoolean(KEY_BLOCKING_ENABLED, false) - reloadAppsWhiteList() - reloadAllAppTrackersWhiteList() - } - - private fun reloadAppsWhiteList() { - appsWhitelist = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet()) ?: HashSet() - appUidsWhitelist = appsWhitelist - .mapNotNull { apId -> getAppByAPId?.invoke(apId)?.uid } - .toSet() - } - - private fun refreshAppUidTrackersWhiteList() { - trackersWhitelistByUid = trackersWhitelistByApp.mapNotNull { (apId, value) -> - getAppByAPId?.invoke(apId)?.uid?.let { uid -> - uid to value - } - }.toMap() - } - private fun reloadAllAppTrackersWhiteList() { - val map: MutableMap> = HashMap() - prefs.all.keys.forEach { key -> - if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) { - map[key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length)] = ( - prefs.getStringSet(key, HashSet()) ?: HashSet() - ) - } - } - trackersWhitelistByApp = map - } - - var isBlockingEnabled: Boolean = false - get() = field - set(enabled) { - prefs.edit().putBoolean(KEY_BLOCKING_ENABLED, enabled).apply() - field = enabled - } - - fun setWhiteListed(apId: String, isWhiteListed: Boolean) { - val current = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet())?.toHashSet() ?: HashSet() - - if (isWhiteListed) { - current.add(apId) - } else { - current.remove(apId) - } - prefs.edit().putStringSet(KEY_APPS_WHITELIST, current).commit() - reloadAppsWhiteList() - } - - private fun buildAppTrackersKey(apId: String): String { - return KEY_APP_TRACKERS_WHITELIST_PREFIX + apId - } - - fun setWhiteListed(tracker: Tracker, apId: String, isWhiteListed: Boolean) { - val trackers = trackersWhitelistByApp.getOrDefault(apId, HashSet()) - trackersWhitelistByApp[apId] = trackers - - if (isWhiteListed) { - trackers.add(tracker.id) - } else { - trackers.remove(tracker.id) - } - refreshAppUidTrackersWhiteList() - prefs.edit().putStringSet(buildAppTrackersKey(apId), trackers).commit() - } - - fun isAppWhiteListed(app: ApplicationDescription): Boolean { - return appsWhitelist.contains(app.apId) - } - - fun isWhiteListed(appUid: Int, trackerId: String?): Boolean { - return appUidsWhitelist.contains(appUid) || - trackersWhitelistByUid.getOrDefault(appUid, HashSet()).contains(trackerId) - } - - fun areWhiteListEmpty(): Boolean { - return appsWhitelist.isEmpty() && trackersWhitelistByApp.all { (_, trackers) -> trackers.isEmpty() } - } - - fun getWhiteListedApp(): List { - return getAppByAPId?.let { - appsWhitelist.mapNotNull(it) - } ?: emptyList() - } - - fun getWhiteListForApp(app: ApplicationDescription): List { - return trackersWhitelistByApp[app.apId]?.toList() ?: emptyList() - } - - fun clearWhiteList(apId: String) { - trackersWhitelistByApp.remove(apId) - refreshAppUidTrackersWhiteList() - prefs.edit().remove(buildAppTrackersKey(apId)).commit() - } -} -- cgit v1.2.1