diff options
Diffstat (limited to 'app/src')
14 files changed, 342 insertions, 230 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index 6ad84a7..aab81d5 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -47,6 +48,7 @@ import foundation.e.privacymodules.ipscrambler.IpScramblerModule import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule 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 @@ -70,7 +72,9 @@ class DependencyContainer(val app: Application) { packageName = context.packageName, uid = Process.myUid(), label = context.resources.getString(R.string.app_name), - icon = null + icon = null, + profileId = -1, + profileType = ProfileType.MAIN ) } @@ -168,12 +172,12 @@ class ViewModelsFactory( override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T { return when (modelClass) { AppTrackersViewModel::class.java -> { - val fallbackUid = android.os.Process.myPid() - val appUid = extras[DEFAULT_ARGS_KEY] - ?.getInt(AppTrackersFragment.PARAM_APP_UID, fallbackUid) ?: fallbackUid + val app = extras[DEFAULT_ARGS_KEY]?.getInt(AppTrackersFragment.PARAM_APP_UID)?.let { + appListUseCase.getApp(it) + } ?: appListUseCase.dummySystemApp AppTrackersViewModel( - appUid = appUid, + app = app, trackersStateUseCase = trackersStateUseCase, trackersStatisticsUseCase = trackersStatisticsUseCase, getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt index 7b09c51..2fbbc34 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS + * Copyright (C) 2021 E FOUNDATION, 2022 - 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,12 +38,12 @@ class AppsAdapter( val icon: ImageView = view.findViewById(R.id.icon) fun bind(item: AppWithCounts) { appName.text = item.label - counts.text = itemView.context.getString( + counts.text = if (item.trackersCount > 0) itemView.context.getString( R.string.trackers_app_trackers_counts, item.blockedTrackersCount, item.trackersCount, item.leaks - ) + ) else "" icon.setImageDrawable(item.icon) itemView.setOnClickListener { listener(item.uid) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt index a97888f..a4f7487 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION, 2022 MURENA SAS + * 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 @@ -25,6 +25,7 @@ import android.content.pm.PackageInfo import foundation.e.privacycentralapp.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 @@ -32,6 +33,7 @@ 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, @@ -44,7 +46,7 @@ class AppListsRepository( private const val PNAME_INTENT_VERIFICATION = "com.android.statementservice" private const val PNAME_MICROG_SERVICES_CORE = "com.google.android.gms" - val appsCompatibiltyPNames = setOf( + val compatibiltyPNames = setOf( PNAME_PWAPLAYER, PNAME_INTENT_VERIFICATION, PNAME_MICROG_SERVICES_CORE ) } @@ -53,18 +55,22 @@ class AppListsRepository( packageName = "foundation.e.dummysystemapp", uid = -1, label = context.getString(R.string.dummy_system_app_label), - icon = context.getDrawable(R.drawable.ic_e_app_logo) + icon = context.getDrawable(R.drawable.ic_e_app_logo), + profileId = -1, + profileType = ProfileType.MAIN ) - val dummyAppsCompatibilityApp = ApplicationDescription( + 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) + icon = context.getDrawable(R.drawable.ic_apps_compatibility_components), + profileId = -1, + profileType = ProfileType.MAIN ) - private suspend fun fetchAppDescriptions() { - val launcherPackageNames = pm.queryIntentActivities( + 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 } @@ -79,104 +85,151 @@ class AppListsRepository( isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames) } - val aCFilter = { packageInfo: PackageInfo -> - packageInfo.packageName in appsCompatibiltyPNames + val compatibilityAppsFilter = { packageInfo: PackageInfo -> + packageInfo.packageName in compatibiltyPNames } - val visibleApps = permissionsModule.getApplications(visibleAppsFilter, true) - val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter, false) - val aCApps = permissionsModule.getApplications(aCFilter, false) + 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<ApplicationDescription>, + fetchMissingIcons: Boolean + ): List<ApplicationDescription> { + 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 + ) + } + } - val workProfileVisibleApps = permissionsModule.getWorkProfileApplications(visibleAppsFilter, true) - val workProfileHiddenApps = permissionsModule.getWorkProfileApplications(hiddenAppsFilter, false) - val workProfileACApps = permissionsModule.getApplications(aCFilter, false) + private fun updateMaps(apps: List<ApplicationDescription>) { + val byUid = mutableMapOf<Int, ApplicationDescription>() + val byApId = mutableMapOf<String, ApplicationDescription>() + 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 + } - appDescriptions.emit((visibleApps + dummySystemApp + dummyAppsCompatibilityApp) to hiddenApps) - allProfilesAppDescriptions.emit(Triple( - (visibleApps + workProfileVisibleApps + dummySystemApp + dummyAppsCompatibilityApp), - (hiddenApps + workProfileHiddenApps), - (aCApps + workProfileACApps) - )) + byApId[app.apId] = app + } + appsByUid = byUid + appsByAPId = byApId } + private var lastFetchApps = 0 private var refreshAppJob: Job? = null - private fun refreshAppDescriptions() { - if (refreshAppJob != null) { - return - } else { + private fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? { + if (refreshAppJob == null) { refreshAppJob = coroutineScope.launch(Dispatchers.IO) { - fetchAppDescriptions() - refreshAppJob = null + if (force || context.packageManager.getChangedPackages(lastFetchApps) != null) { + fetchAppDescriptions(fetchMissingIcons = fetchMissingIcons) + if (fetchMissingIcons) { + lastFetchApps = context.packageManager.getChangedPackages(lastFetchApps) + ?.sequenceNumber ?: lastFetchApps + } + + refreshAppJob = null + } } } + + return refreshAppJob } - fun getVisibleApps(): Flow<List<ApplicationDescription>> { + fun mainProfileApps(): Flow<List<ApplicationDescription>> { refreshAppDescriptions() - return appDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } } + return allProfilesAppDescriptions.map { + it.first.filter { app -> app.profileType == ProfileType.MAIN } + .sortedBy { app -> app.label.toString().lowercase() } + } } - fun getHiddenSystemApps(): List<ApplicationDescription> { - return appDescriptions.value.second + fun getMainProfileHiddenSystemApps(): List<ApplicationDescription> { + return allProfilesAppDescriptions.value.second.filter { it.profileType == ProfileType.MAIN } } - fun getAllProfilesVisibleApps(): Flow<List<ApplicationDescription>> { + fun apps(): Flow<List<ApplicationDescription>> { refreshAppDescriptions() - return allProfilesAppDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } } + return allProfilesAppDescriptions.map { + it.first.sortedBy { app -> app.label.toString().lowercase() } + } + } + + fun allApps(): Flow<List<ApplicationDescription>> { + return allProfilesAppDescriptions.map { + it.first + it.second + it.third + } } - fun getAllProfilesHiddenSystemApps(): List<ApplicationDescription> { + private fun getHiddenSystemApps(): List<ApplicationDescription> { return allProfilesAppDescriptions.value.second } - fun getAllProfilesACApps(): List<ApplicationDescription> { + private fun getCompatibilityApps(): List<ApplicationDescription> { return allProfilesAppDescriptions.value.third } - fun getAllApps(): Flow<List<ApplicationDescription>> = getAllProfilesVisibleApps() - .map { it + getAllProfilesHiddenSystemApps() + getAllProfilesACApps()} - - fun getApplicationDescription(appUid: Int): ApplicationDescription? { - return allProfilesAppDescriptions.value.first.find { it.uid == appUid } + 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 foldForHiddenApp(appUid: Int, appValueGetter: (Int) -> Int): Int { - return if (appUid == dummySystemApp.uid) { - getAllProfilesHiddenSystemApps().fold(0) { acc, app -> - acc + appValueGetter(app.uid) - } - } else if (appUid == dummyAppsCompatibilityApp.uid) { - getAllProfilesACApps().fold(0) { acc, app -> - acc + appValueGetter(app.uid) - } - } else appValueGetter(appUid) + fun applyForHiddenApps(app: ApplicationDescription, action: (ApplicationDescription) -> Unit) { + mapReduceForHiddenApps(app = app, map = action, reduce = {}) } - fun anyForHiddenApps(appUid: Int, test: (Int) -> Boolean): Boolean { - return if (appUid == dummySystemApp.uid) { - getAllProfilesHiddenSystemApps().any { test(it.uid) } - } else if (appUid == dummyAppsCompatibilityApp.uid) { - getAllProfilesACApps().any { test(it.uid) } - } else test(appUid) + fun <T, R> mapReduceForHiddenApps( + app: ApplicationDescription, + map: (ApplicationDescription) -> T, + reduce: (List<T>) -> R + ): R { + return if (app == dummySystemApp) { + reduce(getHiddenSystemApps().map(map)) + } else if (app == dummyCompatibilityApp) { + reduce(getCompatibilityApps().map(map)) + } else reduce(listOf(map(app))) } - fun applyForHiddenApps(appUid: Int, action: (Int) -> Unit) { - if (appUid == dummySystemApp.uid) { - getAllProfilesHiddenSystemApps().forEach { action(it.uid) } - } else if (appUid == dummyAppsCompatibilityApp.uid) { - getAllProfilesACApps().forEach { action(it.uid) } - } else action(appUid) - } + private var appsByUid = mapOf<Int, ApplicationDescription>() + private var appsByAPId = mapOf<String, ApplicationDescription>() + fun getApp(appUid: Int): ApplicationDescription? { + return appsByUid[appUid] ?: run { + runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() } + appsByUid[appUid] + } + } - private val pm get() = context.packageManager + fun getApp(apId: String): ApplicationDescription? { + if (apId.isBlank()) return null - private val appDescriptions = MutableStateFlow( - Pair( - emptyList<ApplicationDescription>(), - emptyList<ApplicationDescription>() - ) - ) + return appsByAPId[apId] ?: run { + runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() } + appsByAPId[apId] + } + } private val allProfilesAppDescriptions = MutableStateFlow( Triple( @@ -209,7 +262,7 @@ class AppListsRepository( private fun isStandardApp(app: ApplicationInfo, launcherApps: List<String>): Boolean { return when { app.packageName == PNAME_SETTINGS -> false - app.packageName in appsCompatibiltyPNames -> 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 @@ -219,7 +272,7 @@ class AppListsRepository( private fun isHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean { return when { - app.packageName in appsCompatibiltyPNames -> false + app.packageName in compatibiltyPNames -> false else -> !isNotHiddenSystemApp(app, launcherApps) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt index 0b76c7b..afdd2d5 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2023 MURENA SAS * Copyright (C) 2022 E FOUNDATION * * This program is free software: you can redistribute it and/or modify @@ -21,6 +22,7 @@ import android.graphics.drawable.Drawable import foundation.e.privacymodules.permissions.data.ApplicationDescription data class AppWithCounts( + val appDesc: ApplicationDescription, val packageName: String, val uid: Int, var label: CharSequence?, @@ -40,6 +42,7 @@ data class AppWithCounts( leaks: Int, ) : this( + appDesc = app, packageName = app.packageName, uid = app.uid, label = app.label, @@ -52,5 +55,5 @@ data class AppWithCounts( ) val blockedTrackersCount get() = if (isWhitelisted) 0 - else trackersCount - whiteListedTrackersCount + else Math.max(trackersCount - whiteListedTrackersCount, 0) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt index 4821349..dd62839 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt @@ -24,8 +24,16 @@ import kotlinx.coroutines.flow.Flow class AppListUseCase( private val appListsRepository: AppListsRepository ) { - + val dummySystemApp = appListsRepository.dummySystemApp + fun getApp(uid: Int): ApplicationDescription { + return when (uid) { + dummySystemApp.uid -> dummySystemApp + appListsRepository.dummyCompatibilityApp.uid -> + appListsRepository.dummyCompatibilityApp + else -> appListsRepository.getApp(uid) ?: dummySystemApp + } + } fun getAppsUsingInternet(): Flow<List<ApplicationDescription>> { - return appListsRepository.getVisibleApps() + return appListsRepository.mainProfileApps() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt index caba132..dcb417b 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt @@ -17,7 +17,6 @@ package foundation.e.privacycentralapp.domain.usecases -import android.util.Log import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode @@ -86,7 +85,7 @@ class IpScramblingStateUseCase( } private fun getHiddenPackageNames(): List<String> { - return appListsRepository.getHiddenSystemApps().map { it.packageName } + return appListsRepository.getMainProfileHiddenSystemApps().map { it.packageName } } val bypassTorApps: Set<String> get() { @@ -97,10 +96,10 @@ class IpScramblingStateUseCase( mutable.add(appListsRepository.dummySystemApp.packageName) whitelist = mutable } - if (AppListsRepository.appsCompatibiltyPNames.any { it in whitelist }) { + if (AppListsRepository.compatibiltyPNames.any { it in whitelist }) { val mutable = whitelist.toMutableSet() - mutable.removeAll(AppListsRepository.appsCompatibiltyPNames) - mutable.add(appListsRepository.dummyAppsCompatibilityApp.packageName) + mutable.removeAll(AppListsRepository.compatibiltyPNames) + mutable.add(appListsRepository.dummyCompatibilityApp.packageName) whitelist = mutable } return whitelist @@ -113,16 +112,16 @@ class IpScramblingStateUseCase( if (visibleList.contains(packageName)) { if (packageName == appListsRepository.dummySystemApp.packageName) { rawList.removeAll(getHiddenPackageNames()) - } else if (packageName == appListsRepository.dummyAppsCompatibilityApp.packageName) { - rawList.removeAll(AppListsRepository.appsCompatibiltyPNames) + } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) { + rawList.removeAll(AppListsRepository.compatibiltyPNames) } else { rawList.remove(packageName) } } else { if (packageName == appListsRepository.dummySystemApp.packageName) { rawList.addAll(getHiddenPackageNames()) - } else if (packageName == appListsRepository.dummyAppsCompatibilityApp.packageName) { - rawList.addAll(AppListsRepository.appsCompatibiltyPNames) + } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) { + rawList.addAll(AppListsRepository.compatibiltyPNames) } else { rawList.add(packageName) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt index 820073b..afb6d1e 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS + * Copyright (C) 2021 E FOUNDATION, 2022 - 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,7 +36,12 @@ class TrackersStateUseCase( private val coroutineScope: CoroutineScope ) { init { - trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false) + trackersPrivacyModule.start( + trackers = trackersRepository.trackers, + getAppByAPId = appListsRepository::getApp, + getAppByUid = appListsRepository::getApp, + enableNotification = false + ) coroutineScope.launch { localStateRepository.blockTrackers.collect { enabled -> if (enabled) { @@ -54,39 +59,47 @@ class TrackersStateUseCase( blockTrackersPrivacyModule.isWhiteListEmpty() } - fun getApplicationDescription(appUid: Int): ApplicationDescription? { - return appListsRepository.getApplicationDescription(appUid) + fun isWhitelisted(app: ApplicationDescription): Boolean { + return isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule) } - fun isWhitelisted(appUid: Int): Boolean { - return isWhitelisted(appUid, appListsRepository, blockTrackersPrivacyModule) + fun toggleAppWhitelist(app: ApplicationDescription, isWhitelisted: Boolean) { + appListsRepository.applyForHiddenApps(app) { + blockTrackersPrivacyModule.setWhiteListed(it, isWhitelisted) + } + updateAllTrackersBlockedState() } - fun toggleAppWhitelist(appUid: Int, isWhitelisted: Boolean) { - appListsRepository.applyForHiddenApps(appUid) { uid -> - blockTrackersPrivacyModule.setWhiteListed(uid, isWhitelisted) + fun blockTracker(app: ApplicationDescription, tracker: Tracker, isBlocked: Boolean) { + appListsRepository.applyForHiddenApps(app) { + blockTrackersPrivacyModule.setWhiteListed(tracker, it, !isBlocked) } - updateAllTrackersBlockedState() } - fun blockTracker(appUid: Int, tracker: Tracker, isBlocked: Boolean) { - appListsRepository.applyForHiddenApps(appUid) { uid -> - blockTrackersPrivacyModule.setWhiteListed(tracker, uid, !isBlocked) - } + fun clearWhitelist(app: ApplicationDescription) { + appListsRepository.applyForHiddenApps( + app, + blockTrackersPrivacyModule::clearWhiteList + ) updateAllTrackersBlockedState() } fun updateTrackers() = coroutineScope.launch { trackersRepository.update() - trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false) + trackersPrivacyModule.start( + trackers = trackersRepository.trackers, + getAppByAPId = appListsRepository::getApp, + getAppByUid = appListsRepository::getApp, + enableNotification = false + ) } } fun isWhitelisted( - appUid: Int, + app: ApplicationDescription, appListsRepository: AppListsRepository, blockTrackersPrivacyModule: IBlockTrackersPrivacyModule ): Boolean { - return appListsRepository.anyForHiddenApps(appUid, blockTrackersPrivacyModule::isWhitelisted) + return appListsRepository.anyForHiddenApps(app, blockTrackersPrivacyModule::isWhitelisted) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt index cc6ec45..5ca7039 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS + * Copyright (C) 2021 E FOUNDATION, 2022 - 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,7 +47,7 @@ class TrackersStatisticsUseCase( private val resources: Resources ) { fun initAppList() { - appListsRepository.getAllProfilesVisibleApps() + appListsRepository.apps() } private fun rawUpdates(): Flow<Unit> = callbackFlow { @@ -74,10 +74,25 @@ class TrackersStatisticsUseCase( ) to trackTrackersPrivacyModule.getTrackersCount() } + fun getNonBlockedTrackersCount(): Flow<Int> { + return if (blockTrackersPrivacyModule.isBlockingEnabled()) + appListsRepository.allApps().map { apps -> + val whiteListedTrackers = mutableSetOf<Tracker>() + val whiteListedApps = blockTrackersPrivacyModule.getWhiteListedApp() + apps.forEach { app -> + if (app in whiteListedApps) { + whiteListedTrackers.addAll(trackTrackersPrivacyModule.getTrackersForApp(app)) + } else { + whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app)) + } + } + whiteListedTrackers.size + } + else flowOf(trackTrackersPrivacyModule.getTrackersCount()) + } + fun getMostLeakedApp(): ApplicationDescription? { - return appListsRepository.getApplicationDescription( - trackTrackersPrivacyModule.getPastDayMostLeakedApp() - ) + return trackTrackersPrivacyModule.getPastDayMostLeakedApp() } fun getDayTrackersCalls() = trackTrackersPrivacyModule.getPastDayTrackersCalls() @@ -161,103 +176,93 @@ class TrackersStatisticsUseCase( } } - fun getTrackers(appUid: Int): List<Tracker> { - val trackers = if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getAllProfilesHiddenSystemApps().map { - trackTrackersPrivacyModule.getTrackersForApp(it.uid) - }.flatten().distinctBy { it.id } - } else if (appUid == appListsRepository.dummyAppsCompatibilityApp.uid) { - appListsRepository.getAllProfilesACApps().map { - trackTrackersPrivacyModule.getTrackersForApp(it.uid) - }.flatten().distinctBy { it.id } - } else trackTrackersPrivacyModule.getTrackersForApp(appUid) - - return trackers.sortedBy { it.label.lowercase() } - } - - fun getTrackersWithWhiteList(appUid: Int): List<Pair<Tracker, Boolean>> { - val trackers: List<Tracker> - val whiteListedTrackersIds: Set<String> - if (appUid == appListsRepository.dummySystemApp.uid) { - val hiddenApps = appListsRepository.getAllProfilesHiddenSystemApps() - trackers = trackTrackersPrivacyModule.getTrackers(hiddenApps.map { it.uid }) - - whiteListedTrackersIds = hiddenApps.fold(HashSet<String>()) { acc, app -> - acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id }) - acc - } - } else if (appUid == appListsRepository.dummyAppsCompatibilityApp.uid) { - val acApps = appListsRepository.getAllProfilesACApps() - trackers = trackTrackersPrivacyModule.getTrackers(acApps.map { it.uid }) + fun getTrackersWithWhiteList(app: ApplicationDescription): List<Pair<Tracker, Boolean>> { + return appListsRepository.mapReduceForHiddenApps( + app = app, + map = { appDesc: ApplicationDescription -> + ( + trackTrackersPrivacyModule.getTrackersForApp(appDesc) to + blockTrackersPrivacyModule.getWhiteList(appDesc) + ) + }, + reduce = { lists -> + lists.unzip().let { (trackerLists, whiteListedIdLists) -> + val whiteListedIds = whiteListedIdLists.flatten().map { it.id }.toSet() - whiteListedTrackersIds = acApps.fold(HashSet<String>()) { acc, app -> - acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id }) - acc + trackerLists.flatten().distinctBy { it.id }.sortedBy { it.label.lowercase() } + .map { tracker -> tracker to (tracker.id in whiteListedIds) } + } } - } else { - trackers = trackTrackersPrivacyModule.getTrackersForApp(appUid) - whiteListedTrackersIds = blockTrackersPrivacyModule.getWhiteList(appUid) - .map { it.id }.toSet() - } + ) + } - return trackers.sortedBy { it.label.lowercase() }.map { tracker -> tracker to whiteListedTrackersIds.any { tracker.id == it } } + fun isWhiteListEmpty(app: ApplicationDescription): Boolean { + return appListsRepository.mapReduceForHiddenApps( + app = app, + map = { appDesc: ApplicationDescription -> + blockTrackersPrivacyModule.getWhiteList(appDesc).isEmpty() + }, + reduce = { areEmpty -> areEmpty.all { it } } + ) } - fun getCalls(appUid: Int): Pair<Int, Int> { - return if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getAllProfilesHiddenSystemApps().map { - trackTrackersPrivacyModule.getPastDayTrackersCallsForApp(it.uid) - }.reduce { (accBlocked, accLeaked), (blocked, leaked) -> - accBlocked + blocked to accLeaked + leaked - } - } else if (appUid == appListsRepository.dummyAppsCompatibilityApp.uid) { - appListsRepository.getAllProfilesACApps().map { - trackTrackersPrivacyModule.getPastDayTrackersCallsForApp(it.uid) - }.reduce { (accBlocked, accLeaked), (blocked, leaked) -> - accBlocked + blocked to accLeaked + leaked + fun getCalls(app: ApplicationDescription): Pair<Int, Int> { + return appListsRepository.mapReduceForHiddenApps( + app = app, + map = trackTrackersPrivacyModule::getPastDayTrackersCallsForApp, + reduce = { zip -> + zip.unzip().let { (blocked, leaked) -> + blocked.sum() to leaked.sum() + } } - } else trackTrackersPrivacyModule.getPastDayTrackersCallsForApp(appUid) + ) |