From 50e5c15e356b464d401b1d9808ca4fe9d164d96a Mon Sep 17 00:00:00 2001 From: jacquarg Date: Fri, 19 Jul 2024 18:29:58 +0200 Subject: [PATCH 1/7] tech:148: Refac app list repository to handle apps with location permission. --- .../e/advancedprivacy/KoinModule.kt | 72 ++-- .../domain/entities/TrackersAndAppsLists.kt | 3 +- .../domain/usecases/AppListUseCase.kt | 39 -- .../domain/usecases/AppTrackersUseCase.kt | 41 +- .../usecases/IpScramblingStateUseCase.kt | 79 ++-- .../domain/usecases/TrackerDetailsUseCase.kt | 19 +- .../usecases/TrackersAndAppsListsUseCase.kt | 32 +- .../domain/usecases/TrackersStateUseCase.kt | 21 +- .../usecases/TrackersStatisticsUseCase.kt | 13 +- .../features/dashboard/DashboardViewModel.kt | 8 +- .../dashboard/ShameListsTabPagerAdapter.kt | 6 +- .../InternetPrivacyFragment.kt | 12 +- .../internetprivacy/InternetPrivacyState.kt | 11 +- .../InternetPrivacyViewModel.kt | 23 +- .../internetprivacy/ToggleAppsAdapter.kt | 16 +- .../features/trackers/ListsTabPagerAdapter.kt | 6 +- .../trackers/TrackersPeriodViewModel.kt | 6 +- .../apptrackers/AppTrackersFragment.kt | 6 +- .../trackers/apptrackers/AppTrackersState.kt | 6 +- .../apptrackers/AppTrackersViewModel.kt | 8 +- .../trackerdetails/TrackerAppsAdapter.kt | 9 +- .../trackerdetails/TrackerDetailsState.kt | 5 +- .../trackerdetails/TrackerDetailsViewModel.kt | 5 +- app/src/main/res/navigation/nav_graph.xml | 6 +- .../e/advancedprivacy/core/KoinModule.kt | 14 +- .../data/repositories/AppListsRepository.kt | 376 ++++++++++-------- .../domain/entities/DisplayableApp.kt | 30 ++ .../permissions/IPermissionsPrivacyModule.kt | 6 +- .../PermissionsPrivacyModuleBase.kt | 6 +- .../PermissionsPrivacyModuleImpl.kt | 9 +- 30 files changed, 447 insertions(+), 446 deletions(-) delete mode 100644 app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppListUseCase.kt create mode 100644 core/src/main/java/foundation/e/advancedprivacy/domain/entities/DisplayableApp.kt diff --git a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt index 938e5a15..7503646f 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt @@ -18,16 +18,16 @@ package foundation.e.advancedprivacy import android.content.res.Resources +import android.graphics.drawable.Drawable import android.os.Process import foundation.e.advancedprivacy.core.coreModule +import foundation.e.advancedprivacy.data.repositories.AppListsRepository import foundation.e.advancedprivacy.data.repositories.LocalStateRepositoryImpl import foundation.e.advancedprivacy.data.repositories.ResourcesRepository import foundation.e.advancedprivacy.domain.entities.ApplicationDescription -import foundation.e.advancedprivacy.domain.entities.CHANNEL_TRACKER_FLAG -import foundation.e.advancedprivacy.domain.entities.NotificationContent +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.ProfileType import foundation.e.advancedprivacy.domain.repositories.LocalStateRepository -import foundation.e.advancedprivacy.domain.usecases.AppListUseCase import foundation.e.advancedprivacy.domain.usecases.AppTrackersUseCase import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase @@ -55,16 +55,31 @@ import foundation.e.advancedprivacy.trackers.data.TrackersRepository import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import foundation.e.advancedprivacy.trackers.service.trackerServiceModule import foundation.e.advancedprivacy.trackers.trackersModule +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob 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 +import timber.log.Timber val appModule = module { includes(coreModule, trackersModule, fakelocationModule, ipScramblerModule, trackerServiceModule) + single { + CoroutineScope( + SupervisorJob() + + Dispatchers.IO + + CoroutineExceptionHandler { _, throwable -> + Timber.e("Uncaught error in backgroundScope", throwable) + } + ) + } + factory { androidContext().resources } single { LocalStateRepositoryImpl(context = androidContext()) @@ -81,42 +96,23 @@ val appModule = module { ) } - 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("SystemAppLabel")) { + androidContext().getString(R.string.dummy_system_app_label) } - - 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(named("SystemAppIcon")) { + androidContext().getDrawable(R.drawable.ic_e_app_logo)!! } - single(named("notificationTrackerFlag")) { - NotificationContent( - channelId = CHANNEL_TRACKER_FLAG, - icon = R.drawable.ic_e_app_logo, - title = R.string.notifications_tracker_title, - description = R.string.notifications_tracker_content, - pendingIntent = null - ) + single(named("CompatibilityAppLabel")) { + androidContext().getString(R.string.dummy_apps_compatibility_app_label) + } + single(named("CompatibilityAppIcon")) { + androidContext().getDrawable(R.drawable.ic_apps_compatibility_components)!! } single { CityDataSource } single { ResourcesRepository(androidContext()) } - singleOf(::AppListUseCase) single { FakeLocationStateUseCase( fakeLocationModule = get(), @@ -135,7 +131,7 @@ val appModule = module { orbotSupervisor = get(), localStateRepository = get(), appListsRepository = get(), - coroutineScope = get() + backgroundScope = get() ) } @@ -155,8 +151,16 @@ val appModule = module { } viewModel { parameters -> - val appListUseCase: AppListUseCase = get() - val app = appListUseCase.getApp(parameters.get()) + val appListsRepository: AppListsRepository = get() + val app = appListsRepository.getAppById(parameters.get()) ?: DisplayableApp( + id = "dummy-app", + label = androidContext().resources.getString(R.string.app_name), + icon = get(named("SystemAppIcon")), + apps = setOf(get(named("AdvancedPrivacy"))), + hasLocationPermission = true, + hasInternetPermission = true, + profileType = ProfileType.MAIN + ) AppTrackersViewModel( app = app, diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersAndAppsLists.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersAndAppsLists.kt index e8444732..ebd6e07b 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersAndAppsLists.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/entities/TrackersAndAppsLists.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2024 E FOUNDATION * Copyright (C) 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify @@ -25,7 +26,7 @@ data class TrackersAndAppsLists( ) data class AppWithCount( - val app: ApplicationDescription, + val app: DisplayableApp, val count: Int = 0 ) 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 deleted file mode 100644 index dfd32b6c..00000000 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppListUseCase.kt +++ /dev/null @@ -1,39 +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.advancedprivacy.domain.usecases - -import foundation.e.advancedprivacy.data.repositories.AppListsRepository -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription -import kotlinx.coroutines.flow.Flow - -class AppListUseCase( - private val appListsRepository: AppListsRepository -) { - val dummySystemApp = appListsRepository.dummySystemApp - fun getApp(uid: Int): ApplicationDescription { - return when (uid) { - dummySystemApp.uid -> dummySystemApp - appListsRepository.dummyCompatibilityApp.uid -> - appListsRepository.dummyCompatibilityApp - else -> appListsRepository.getApp(uid) ?: dummySystemApp - } - } - fun getAppsUsingInternet(): Flow> { - return appListsRepository.mainProfileApps() - } -} diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppTrackersUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppTrackersUseCase.kt index b156246d..56ab3f63 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppTrackersUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/AppTrackersUseCase.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2024 E FOUNDATION * Copyright (C) 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify @@ -16,8 +17,7 @@ */ package foundation.e.advancedprivacy.domain.usecases -import foundation.e.advancedprivacy.data.repositories.AppListsRepository -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.trackers.data.StatsDatabase import foundation.e.advancedprivacy.trackers.data.TrackersRepository import foundation.e.advancedprivacy.trackers.data.WhitelistRepository @@ -27,13 +27,12 @@ import foundation.e.advancedprivacy.trackers.domain.usecases.FilterHostnameUseCa class AppTrackersUseCase( private val whitelistRepository: WhitelistRepository, private val trackersStateUseCase: TrackersStateUseCase, - private val appListsRepository: AppListsRepository, private val statsDatabase: StatsDatabase, private val trackersRepository: TrackersRepository, private val filterHostnameUseCase: FilterHostnameUseCase ) { - suspend fun toggleAppWhitelist(app: ApplicationDescription, trackers: List, isBlocked: Boolean) { - val realApIds = appListsRepository.getRealApps(app).map { it.apId } + suspend fun toggleAppWhitelist(app: DisplayableApp, trackers: List, isBlocked: Boolean) { + val realApIds = app.apps.map { it.apId } val trackerIds = trackers.map { it.id } whitelistRepository.setWhiteListed(realApIds, !isBlocked) @@ -41,39 +40,31 @@ class AppTrackersUseCase( trackersStateUseCase.updateAllTrackersBlockedState() } - suspend fun clearWhitelist(app: ApplicationDescription) { - appListsRepository.applyForHiddenApps( - app - ) { + suspend fun clearWhitelist(app: DisplayableApp) { + app.apps.forEach { whitelistRepository.clearWhiteList(it.apId) } trackersStateUseCase.updateAllTrackersBlockedState() } - suspend fun getCalls(app: ApplicationDescription): Pair { - return appListsRepository.mapReduceForHiddenApps( - app = app, - map = { - statsDatabase.getCallsForApp(app.apId) - }, - reduce = { zip -> - zip.unzip().let { (blocked, leaked) -> - blocked.sum() to leaked.sum() - } - } - ) + suspend fun getCalls(app: DisplayableApp): Pair { + return app.apps.map { + statsDatabase.getCallsForApp(it.apId) + }.unzip().let { (blocked, leaked) -> + blocked.sum() to leaked.sum() + } } - suspend fun getTrackersWithBlockedList(app: ApplicationDescription): List> { - val realApIds = appListsRepository.getRealApps(app).map { it.apId } + suspend fun getTrackersWithBlockedList(app: DisplayableApp): List> { + val realApIds = app.apps.map { it.apId } val trackers = statsDatabase.getTrackerIds(realApIds) .mapNotNull { trackersRepository.getTracker(it) } return enrichWithBlockedState(app, trackers) } - suspend fun enrichWithBlockedState(app: ApplicationDescription, trackers: List): List> { - val realAppUids = appListsRepository.getRealApps(app).map { it.uid } + suspend fun enrichWithBlockedState(app: DisplayableApp, trackers: List): List> { + val realAppUids = app.apps.map { it.uid } return trackers.map { tracker -> tracker to !realAppUids.any { uid -> filterHostnameUseCase.isWhitelisted(uid, tracker.id) 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 00613dd4..bc12a326 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 @@ -1,6 +1,6 @@ /* * Copyright (C) 2023 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -19,77 +19,72 @@ package foundation.e.advancedprivacy.domain.usecases import foundation.e.advancedprivacy.data.repositories.AppListsRepository +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.FeatureState +import foundation.e.advancedprivacy.domain.entities.ProfileType import foundation.e.advancedprivacy.domain.repositories.LocalStateRepository import foundation.e.advancedprivacy.ipscrambler.OrbotSupervisor import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update class IpScramblingStateUseCase( private val orbotSupervisor: OrbotSupervisor, private val localStateRepository: LocalStateRepository, private val appListsRepository: AppListsRepository, - private val coroutineScope: CoroutineScope + private val backgroundScope: CoroutineScope ) { val internetPrivacyMode: StateFlow = orbotSupervisor.state + private val whitelistedPackages = MutableStateFlow(orbotSupervisor.appList) + init { orbotSupervisor.requestStatus() orbotSupervisor.state.map { localStateRepository.internetPrivacyMode.value = it - }.launchIn(coroutineScope) + }.launchIn(backgroundScope) + + whitelistedPackages.map { + orbotSupervisor.appList = it + }.launchIn(backgroundScope) } fun toggle(hideIp: Boolean) { localStateRepository.setIpScramblingSetting(enabled = hideIp) } - private fun getHiddenPackageNames(): List { - return appListsRepository.getMainProfileHiddenSystemApps().map { it.packageName } - } - - val bypassTorApps: Set get() { - var whitelist = orbotSupervisor.appList - if (getHiddenPackageNames().any { it in whitelist }) { - val mutable = whitelist.toMutableSet() - mutable.removeAll(getHiddenPackageNames()) - mutable.add(appListsRepository.dummySystemApp.packageName) - whitelist = mutable - } - if (AppListsRepository.compatibiltyPNames.any { it in whitelist }) { - val mutable = whitelist.toMutableSet() - mutable.removeAll(AppListsRepository.compatibiltyPNames) - mutable.add(appListsRepository.dummyCompatibilityApp.packageName) - whitelist = mutable + suspend fun getAppsWithUseTor(): Flow>> { + return combine( + appListsRepository.displayableApp.map { apps -> + apps.filter { app -> + app.hasInternetPermission && app.profileType == ProfileType.MAIN + } + }, + whitelistedPackages + ) { apps, pNames -> + apps.map { app -> + app to !app.isWhitelisted(pNames) + } } - return whitelist } - fun toggleBypassTor(packageName: String) { - val visibleList = bypassTorApps.toMutableSet() - val rawList = orbotSupervisor.appList.toMutableSet() - - if (visibleList.contains(packageName)) { - if (packageName == appListsRepository.dummySystemApp.packageName) { - rawList.removeAll(getHiddenPackageNames()) - } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) { - rawList.removeAll(AppListsRepository.compatibiltyPNames) - } else { - rawList.remove(packageName) - } - } else { - if (packageName == appListsRepository.dummySystemApp.packageName) { - rawList.addAll(getHiddenPackageNames()) - } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) { - rawList.addAll(AppListsRepository.compatibiltyPNames) + fun toggleBypassTor(app: DisplayableApp) { + whitelistedPackages.update { whitelist -> + val mutable = whitelist.toMutableSet() + val packageNames = app.apps.map { it.packageName } + if (app.isWhitelisted()) { + mutable.removeAll(packageNames) } else { - rawList.add(packageName) + mutable.addAll(packageNames) } + mutable } - orbotSupervisor.appList = rawList } val availablesLocations: List = orbotSupervisor.getAvailablesLocations().sorted() @@ -101,4 +96,8 @@ class IpScramblingStateUseCase( orbotSupervisor.setExitCountryCode(locationId) } } + + private fun DisplayableApp.isWhitelisted(whitelistedPNames: Set = whitelistedPackages.value): Boolean = apps.any { + it.packageName in whitelistedPNames + } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt index eb247d7b..36ea4dde 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2024 E FOUNDATION * Copyright (C) 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify @@ -17,7 +18,7 @@ package foundation.e.advancedprivacy.domain.usecases import foundation.e.advancedprivacy.data.repositories.AppListsRepository -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.trackers.data.StatsDatabase import foundation.e.advancedprivacy.trackers.data.WhitelistRepository import foundation.e.advancedprivacy.trackers.domain.entities.Tracker @@ -30,30 +31,28 @@ class TrackerDetailsUseCase( private val statsDatabase: StatsDatabase, private val filterHostnameUseCase: FilterHostnameUseCase ) { - suspend fun toggleTrackerWhitelist(tracker: Tracker, apps: List, isBlocked: Boolean) { + suspend fun toggleTrackerWhitelist(tracker: Tracker, apps: List, isBlocked: Boolean) { whitelistRepository.setWhiteListed(tracker, !isBlocked) whitelistRepository.setWhitelistedAppsForTracker( - apps.flatMap { appListsRepository.getRealApps(it) }.map { it.apId }, + apps.flatMap { it.apps }.map { it.apId }, tracker.id, !isBlocked ) trackersStateUseCase.updateAllTrackersBlockedState() } - suspend fun getAppsWithBlockedState(tracker: Tracker): List> { + suspend fun getAppsWithBlockedState(tracker: Tracker): List> { return enrichWithBlockedState( statsDatabase.getApIds(tracker.id).mapNotNull { - appListsRepository.getDisplayableApp(it) - }.sortedBy { it.label?.toString() }, + appListsRepository.getInternetAppByApId(it) + }.distinct().sortedBy { it.label.toString() }, tracker ) } - suspend fun enrichWithBlockedState(apps: List, tracker: Tracker): List> { + suspend fun enrichWithBlockedState(apps: List, tracker: Tracker): List> { return apps.map { app -> - app to appListsRepository.anyForHiddenApps(app) { realApp -> - !filterHostnameUseCase.isWhitelisted(realApp.uid, tracker.id) - } + app to app.apps.any { !filterHostnameUseCase.isWhitelisted(it.uid, tracker.id) } } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt index b02f43b6..d697b4da 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt @@ -18,7 +18,7 @@ package foundation.e.advancedprivacy.domain.usecases import foundation.e.advancedprivacy.data.repositories.AppListsRepository import foundation.e.advancedprivacy.domain.entities.AppWithCount -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.TrackerWithCount import foundation.e.advancedprivacy.domain.entities.TrackersAndAppsLists import foundation.e.advancedprivacy.features.trackers.Period @@ -60,11 +60,11 @@ class TrackersAndAppsListsUseCase( } private suspend fun get5MostTrackedAppsLastMonth(): List { - val countByAppIds = statsDatabase.getCallsByAppIds(since = Period.MONTH.getPeriodStart().epochSecond) + val countByApIds = statsDatabase.getCallsByAppIds(since = Period.MONTH.getPeriodStart().epochSecond) - val countByApps = mutableMapOf() - countByAppIds.forEach { (appId, count) -> - appListsRepository.getDisplayableApp(appId)?.let { app -> + val countByApps = mutableMapOf() + countByApIds.forEach { (apId, count) -> + appListsRepository.getInternetAppByApId(apId)?.let { app -> countByApps[app] = count + (countByApps[app] ?: 0) } } @@ -86,22 +86,24 @@ class TrackersAndAppsListsUseCase( }.sortedByDescending { it.count } } - private suspend fun buildAllAppList(countByApp: Map): List { - return appListsRepository.apps().first().map { app: ApplicationDescription -> - AppWithCount(app = app, count = countByApp[app] ?: 0) - }.sortedByDescending { it.count } + private suspend fun buildAllAppList(countByApp: Map): List { + return appListsRepository.displayableApp.first() + .filter { it.hasInternetPermission } + .map { app: DisplayableApp -> + AppWithCount(app = app, count = countByApp[app] ?: 0) + }.sortedByDescending { it.count } } - private fun buildAppList(countByApp: Map): List { + private fun buildAppList(countByApp: Map): List { return countByApp.map { (app, count) -> AppWithCount(app = app, count = count) }.sortedByDescending { it.count } } - private suspend fun mapIdsToEntities(trackersAndAppsIds: List>): List> { + private suspend fun mapIdsToEntities(trackersAndAppsIds: List>): List> { return trackersAndAppsIds.mapNotNull { (trackerId, apId) -> trackersRepository.getTracker(trackerId)?.let { tracker -> - appListsRepository.getDisplayableApp(apId)?.let { app -> + appListsRepository.getInternetAppByApId(apId)?.let { app -> tracker to app } } @@ -110,9 +112,9 @@ class TrackersAndAppsListsUseCase( }.distinct() } - private fun foldToCountByEntityMaps(trackersAndApps: List>): CountByEntitiesMaps { + private fun foldToCountByEntityMaps(trackersAndApps: List>): CountByEntitiesMaps { return trackersAndApps.fold( - mutableMapOf() to mutableMapOf() + mutableMapOf() to mutableMapOf() ) { (countByApp, countByTracker), (tracker, app) -> countByApp[app] = countByApp.getOrDefault(app, 0) + 1 countByTracker[tracker] = countByTracker.getOrDefault(tracker, 0) + 1 @@ -126,7 +128,7 @@ class TrackersAndAppsListsUseCase( } private data class CountByEntitiesMaps( - val countByApps: Map, + val countByApps: Map, val countByTrackers: Map ) } 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 3ae3f446..c2dddfc7 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 @@ -1,6 +1,6 @@ /* * Copyright (C) 2022 - 2023 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -18,8 +18,7 @@ package foundation.e.advancedprivacy.domain.usecases -import foundation.e.advancedprivacy.data.repositories.AppListsRepository -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.repositories.LocalStateRepository import foundation.e.advancedprivacy.trackers.data.WhitelistRepository import foundation.e.advancedprivacy.trackers.domain.entities.Tracker @@ -29,7 +28,6 @@ import kotlinx.coroutines.launch class TrackersStateUseCase( private val whitelistRepository: WhitelistRepository, private val localStateRepository: LocalStateRepository, - private val appListsRepository: AppListsRepository, coroutineScope: CoroutineScope ) { init { @@ -46,17 +44,17 @@ class TrackersStateUseCase( whitelistRepository.areWhiteListEmpty() } - fun isWhitelisted(app: ApplicationDescription): Boolean { - return isWhitelisted(app, appListsRepository, whitelistRepository) + fun isWhitelisted(app: DisplayableApp): Boolean { + return isWhitelisted(app, whitelistRepository) } fun isWhitelisted(tracker: Tracker): Boolean { return whitelistRepository.isWhiteListed(tracker) } - suspend fun blockTracker(app: ApplicationDescription, tracker: Tracker, isBlocked: Boolean) { + suspend fun blockTracker(app: DisplayableApp, tracker: Tracker, isBlocked: Boolean) { whitelistRepository.setWhitelistedAppsForTracker( - appListsRepository.getRealApps(app).map { it.apId }, + app.apps.map { it.apId }, tracker.id, !isBlocked ) @@ -64,9 +62,6 @@ class TrackersStateUseCase( } } -fun isWhitelisted(app: ApplicationDescription, appListsRepository: AppListsRepository, whitelistRepository: WhitelistRepository): Boolean { - return appListsRepository.anyForHiddenApps( - app, - whitelistRepository::isAppWhiteListed - ) +fun isWhitelisted(app: DisplayableApp, whitelistRepository: WhitelistRepository): Boolean { + return app.apps.any(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 0b50c8e1..14a77ea1 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 @@ -23,6 +23,7 @@ import foundation.e.advancedprivacy.common.throttleFirst import foundation.e.advancedprivacy.data.repositories.AppListsRepository import foundation.e.advancedprivacy.data.repositories.ResourcesRepository import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.TrackersPeriodicStatistics import foundation.e.advancedprivacy.features.trackers.Period import foundation.e.advancedprivacy.trackers.data.StatsDatabase @@ -44,7 +45,7 @@ class TrackersStatisticsUseCase( private val resourcesRepository: ResourcesRepository ) { fun initAppList() { - appListsRepository.apps() + appListsRepository.refreshAppDescriptions() } @OptIn(FlowPreview::class) @@ -163,14 +164,8 @@ class TrackersStatisticsUseCase( ) } - suspend fun isWhiteListEmpty(app: ApplicationDescription): Boolean { - return appListsRepository.mapReduceForHiddenApps( - app = app, - map = { appDesc: ApplicationDescription -> - getWhiteList(appDesc).isEmpty() - }, - reduce = { areEmpty -> areEmpty.all { it } } - ) + suspend fun isWhiteListEmpty(app: DisplayableApp): Boolean { + return app.apps.all { getWhiteList(it).isEmpty() } } suspend fun getLastMonthBlockedLeaksCount(): Int { diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt index badf0ab9..e9835e87 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt @@ -1,6 +1,6 @@ /* * Copyright (C) 2023 - 2024 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -23,7 +23,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavDirections import foundation.e.advancedprivacy.R -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersAndAppsListsUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersScreenUseCase @@ -121,8 +121,8 @@ class DashboardViewModel( _navigate.emit(DashboardFragmentDirections.gotoSettingsPermissionsActivity()) } - fun onClickShameApp(app: ApplicationDescription) = viewModelScope.launch { - _navigate.emit(DashboardFragmentDirections.gotoAppTrackersFragment(appUid = app.uid)) + fun onClickShameApp(app: DisplayableApp) = viewModelScope.launch { + _navigate.emit(DashboardFragmentDirections.gotoAppTrackersFragment(appId = app.id)) } fun onClickShameTracker(tracker: Tracker) = viewModelScope.launch { diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/ShameListsTabPagerAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/ShameListsTabPagerAdapter.kt index 4ff00958..e1fc7e43 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/ShameListsTabPagerAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/ShameListsTabPagerAdapter.kt @@ -32,13 +32,13 @@ import foundation.e.advancedprivacy.common.extensions.dpToPx import foundation.e.advancedprivacy.databinding.DashboardShameListBinding import foundation.e.advancedprivacy.databinding.TrackersItemAppBinding import foundation.e.advancedprivacy.domain.entities.AppWithCount -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.TrackerWithCount import foundation.e.advancedprivacy.features.trackers.TrackerTab import foundation.e.advancedprivacy.trackers.domain.entities.Tracker class ShameListsTabPagerAdapter( - private val onClickShameApp: (ApplicationDescription) -> Unit, + private val onClickShameApp: (DisplayableApp) -> Unit, private val onClickShameTracker: (Tracker) -> Unit, private val onClickViewAllApps: () -> Unit, private val onClickViewAllTrackers: () -> Unit @@ -99,7 +99,7 @@ class ShameListsTabPagerAdapter( class AppsListViewHolder( private val binding: DashboardShameListBinding, - private val onClickShameApp: (ApplicationDescription) -> Unit, + private val onClickShameApp: (DisplayableApp) -> Unit, private val onClickViewAllApps: () -> Unit ) : ListsTabViewHolder(binding.root) { 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 9fec91e5..0d98cd3f 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 @@ -1,6 +1,6 @@ /* * Copyright (C) 2023 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -55,10 +55,8 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac binding.apps.apply { layoutManager = LinearLayoutManager(requireContext()) setHasFixedSize(true) - adapter = ToggleAppsAdapter(R.layout.ipscrambling_item_app_toggle) { packageName -> - viewModel.submitAction( - InternetPrivacyViewModel.Action.ToggleAppIpScrambled(packageName) - ) + adapter = ToggleAppsAdapter(R.layout.ipscrambling_item_app_toggle) { app -> + viewModel.onClickToggleAppIpScrambled(app) } } @@ -143,7 +141,7 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac // TODO: this should not be mandatory. binding.apps.post { (binding.apps.adapter as ToggleAppsAdapter?)?.setData( - list = state.getApps(), + list = state.appsWithUseTor, isEnabled = state.mode == FeatureState.ON ) } @@ -158,7 +156,7 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac when { state.mode.isLoading || - state.availableApps.isEmpty() -> { + state.appsWithUseTor.isEmpty() -> { binding.loader.visibility = View.VISIBLE viewIdsToHide.forEach { it.visibility = View.GONE } } 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 4966431f..da0f32fd 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2022 - 2024 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 @@ -17,20 +17,15 @@ package foundation.e.advancedprivacy.features.internetprivacy -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.FeatureState data class InternetPrivacyState( val mode: FeatureState = FeatureState.OFF, - val availableApps: List = emptyList(), - val bypassTorApps: Collection = emptyList(), + val appsWithUseTor: List> = emptyList(), val selectedLocation: String = "", val availableLocationIds: List = emptyList(), val forceRedraw: Boolean = false ) { - fun getApps(): List> { - return availableApps.map { it to (it.packageName !in bypassTorApps) } - } - val selectedLocationPosition get() = availableLocationIds.indexOf(selectedLocation) } 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 748ddd3a..698eff83 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 @@ -1,6 +1,6 @@ /* * Copyright (C) 2023 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -22,8 +22,8 @@ import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.advancedprivacy.R +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.FeatureState -import foundation.e.advancedprivacy.domain.usecases.AppListUseCase import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.IpScramblingStateUseCase import kotlinx.coroutines.Dispatchers @@ -41,8 +41,7 @@ import kotlinx.coroutines.withContext class InternetPrivacyViewModel( private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, - private val ipScramblingStateUseCase: IpScramblingStateUseCase, - private val appListUseCase: AppListUseCase + private val ipScramblingStateUseCase: IpScramblingStateUseCase ) : ViewModel() { companion object { private const val WARNING_LOADING_LONG_DELAY = 5 * 1000L @@ -72,13 +71,8 @@ class InternetPrivacyViewModel( suspend fun doOnStartedState() = withContext(Dispatchers.IO) { launch { merge( - appListUseCase.getAppsUsingInternet().map { apps -> - _state.update { s -> - s.copy( - availableApps = apps, - bypassTorApps = ipScramblingStateUseCase.bypassTorApps - ) - } + ipScramblingStateUseCase.getAppsWithUseTor().map { apps -> + _state.update { it.copy(appsWithUseTor = apps) } }, ipScramblingStateUseCase.internetPrivacyMode.map { _state.update { s -> s.copy(mode = it) } @@ -116,7 +110,6 @@ class InternetPrivacyViewModel( when (action) { is Action.UseRealIPAction -> actionUseRealIP() is Action.UseHiddenIPAction -> actionUseHiddenIP() - is Action.ToggleAppIpScrambled -> actionToggleAppIpScrambled(action) is Action.SelectLocationAction -> actionSelectLocation(action) } } @@ -129,9 +122,8 @@ class InternetPrivacyViewModel( ipScramblingStateUseCase.toggle(hideIp = true) } - private suspend fun actionToggleAppIpScrambled(action: Action.ToggleAppIpScrambled) = withContext(Dispatchers.IO) { - ipScramblingStateUseCase.toggleBypassTor(action.packageName) - _state.update { it.copy(bypassTorApps = ipScramblingStateUseCase.bypassTorApps) } + fun onClickToggleAppIpScrambled(app: DisplayableApp) = viewModelScope.launch(Dispatchers.IO) { + ipScramblingStateUseCase.toggleBypassTor(app) } private suspend fun actionSelectLocation(action: Action.SelectLocationAction) = withContext(Dispatchers.IO) { @@ -150,7 +142,6 @@ class InternetPrivacyViewModel( sealed class Action { object UseRealIPAction : Action() object UseHiddenIPAction : Action() - data class ToggleAppIpScrambled(val packageName: String) : Action() data class SelectLocationAction(val position: Int) : Action() } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt index 205abce4..b0abf33f 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -25,30 +25,30 @@ import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import foundation.e.advancedprivacy.R -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp class ToggleAppsAdapter( private val itemsLayout: Int, - private val listener: (String) -> Unit + private val listener: (DisplayableApp) -> Unit ) : RecyclerView.Adapter() { - class ViewHolder(view: View, private val listener: (String) -> Unit) : RecyclerView.ViewHolder(view) { + class ViewHolder(view: View, private val listener: (DisplayableApp) -> Unit) : RecyclerView.ViewHolder(view) { val appName: TextView = view.findViewById(R.id.title) val togglePermission: CheckBox = view.findViewById(R.id.toggle) - fun bind(item: Pair, isEnabled: Boolean) { + fun bind(item: Pair, isEnabled: Boolean) { appName.text = item.first.label togglePermission.isChecked = item.second togglePermission.isEnabled = isEnabled itemView.findViewById(R.id.icon).setImageDrawable(item.first.icon) - togglePermission.setOnClickListener { listener(item.first.packageName) } + togglePermission.setOnClickListener { listener(item.first) } } } - var dataSet: List> = emptyList() + var dataSet: List> = emptyList() set(value) { field = value notifyDataSetChanged() @@ -56,7 +56,7 @@ class ToggleAppsAdapter( var isEnabled: Boolean = true - fun setData(list: List>, isEnabled: Boolean = true) { + fun setData(list: List>, isEnabled: Boolean = true) { this.isEnabled = isEnabled dataSet = list } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt index 66d8e71e..3897c637 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt @@ -32,13 +32,13 @@ import foundation.e.advancedprivacy.databinding.TrackersAppsListBinding import foundation.e.advancedprivacy.databinding.TrackersItemAppBinding import foundation.e.advancedprivacy.databinding.TrackersListBinding import foundation.e.advancedprivacy.domain.entities.AppWithCount -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.TrackerWithCount import foundation.e.advancedprivacy.trackers.domain.entities.Tracker class ListsTabPagerAdapter( private val onClickTracker: (Tracker) -> Unit, - private val onClickApp: (ApplicationDescription) -> Unit, + private val onClickApp: (DisplayableApp) -> Unit, private val onToggleHideNoTrackersApps: () -> Unit ) : RecyclerView.Adapter() { private var uiState: TrackersPeriodState = TrackersPeriodState() @@ -102,7 +102,7 @@ class ListsTabPagerAdapter( class AppsListViewHolder( private val binding: TrackersAppsListBinding, - private val onClickApp: (ApplicationDescription) -> Unit, + private val onClickApp: (DisplayableApp) -> Unit, private val onToggleHideNoTrackersApps: () -> Unit ) : ListsTabViewHolder(binding.root) { private val adapter = object : BindingListAdapter() { diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersPeriodViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersPeriodViewModel.kt index 587ef32e..b416f2d8 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersPeriodViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/TrackersPeriodViewModel.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavDirections import foundation.e.advancedprivacy.R -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.usecases.TrackersAndAppsListsUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersScreenUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase @@ -96,8 +96,8 @@ class TrackersPeriodViewModel( _navigate.emit(TrackersFragmentDirections.gotoTrackerDetailsFragment(trackerId = tracker.id)) } - fun onClickApp(app: ApplicationDescription) = viewModelScope.launch { - _navigate.emit(TrackersFragmentDirections.gotoAppTrackersFragment(appUid = app.uid)) + fun onClickApp(app: DisplayableApp) = viewModelScope.launch { + _navigate.emit(TrackersFragmentDirections.gotoAppTrackersFragment(appId = app.id)) } fun onToggleHideNoTrackersApps() = viewModelScope.launch { 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 adb20cea..99280ddd 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 @@ -44,7 +44,7 @@ import org.koin.core.parameter.parametersOf class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { private val args: AppTrackersFragmentArgs by navArgs() - private val viewModel: AppTrackersViewModel by viewModel { parametersOf(args.appUid) } + private val viewModel: AppTrackersViewModel by viewModel { parametersOf(args.appId) } private val numberFormatter: BigNumberFormatter by lazy { BigNumberFormatter(requireContext()) } @@ -132,8 +132,8 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) { } private fun render(state: AppTrackersState) { - setTitle(state.appDesc?.label) - binding.subtitle.text = getString(R.string.apptrackers_subtitle, state.appDesc?.label) + setTitle(state.app?.label) + binding.subtitle.text = getString(R.string.apptrackers_subtitle, state.app?.label) binding.dataDetectedTrackers.apply { primaryMessage.setText(R.string.apptrackers_detected_tracker_primary) number.text = state.getTrackersCount().toString() 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 ede918f3..4bb754bd 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 @@ -1,6 +1,6 @@ /* * Copyright (C) 2023 MURENA SAS - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2022 - 2024 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 @@ -18,11 +18,11 @@ package foundation.e.advancedprivacy.features.trackers.apptrackers -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.trackers.domain.entities.Tracker data class AppTrackersState( - val appDesc: ApplicationDescription? = null, + val app: DisplayableApp? = null, val isBlockingActivated: Boolean = false, val trackersWithBlockedList: List> = emptyList(), val leaked: Int = 0, 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 4d1401f6..726be53c 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 @@ -1,6 +1,6 @@ /* * Copyright (C) 2023 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -22,7 +22,7 @@ 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.DisplayableApp import foundation.e.advancedprivacy.domain.entities.TrackerMode import foundation.e.advancedprivacy.domain.usecases.AppTrackersUseCase import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase @@ -42,7 +42,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class AppTrackersViewModel( - private val app: ApplicationDescription, + private val app: DisplayableApp, private val appTrackersUseCase: AppTrackersUseCase, private val trackersStateUseCase: TrackersStateUseCase, private val trackersStatisticsUseCase: TrackersStatisticsUseCase, @@ -57,7 +57,7 @@ class AppTrackersViewModel( init { _state.update { it.copy( - appDesc = app + app = app ) } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt index da714274..6fd1dc74 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2024 E FOUNDATION * Copyright (C) 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify @@ -21,7 +22,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import foundation.e.advancedprivacy.databinding.ApptrackersItemTrackerToggleBinding -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp class TrackerAppsAdapter( private val viewModel: TrackerDetailsViewModel @@ -32,7 +33,7 @@ class TrackerAppsAdapter( private val viewModel: TrackerDetailsViewModel ) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: Pair) { + fun bind(item: Pair) { val (app, isWhiteListed) = item binding.title.text = app.label binding.toggle.apply { @@ -44,9 +45,9 @@ class TrackerAppsAdapter( } } - private var dataSet: List> = emptyList() + private var dataSet: List> = emptyList() - fun updateDataSet(new: List>) { + fun updateDataSet(new: List>) { dataSet = new notifyDataSetChanged() } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt index 9cbc3993..52e750b3 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2024 E FOUNDATION * Copyright (C) 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify @@ -17,7 +18,7 @@ package foundation.e.advancedprivacy.features.trackers.trackerdetails -import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.trackers.domain.entities.Tracker data class TrackerDetailsState( @@ -26,6 +27,6 @@ data class TrackerDetailsState( val detectedCount: Int = 0, val blockedCount: Int = 0, val leakedCount: Int = 0, - val appList: List> = emptyList(), + val appList: List> = emptyList(), val isTrackersBlockingEnabled: Boolean = false ) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt index 1a87f94b..eda495ab 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt @@ -1,4 +1,5 @@ /* + * Copyright (C) 2024 E FOUNDATION * Copyright (C) 2023 MURENA SAS * * This program is free software: you can redistribute it and/or modify @@ -21,7 +22,7 @@ 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.DisplayableApp import foundation.e.advancedprivacy.domain.entities.TrackerMode import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackerDetailsUseCase @@ -62,7 +63,7 @@ class TrackerDetailsViewModel( ).collect { } } - fun onToggleUnblockApp(app: ApplicationDescription, isBlocked: Boolean) { + fun onToggleUnblockApp(app: DisplayableApp, isBlocked: Boolean) { viewModelScope.launch(Dispatchers.IO) { if (!state.value.isTrackersBlockingEnabled) { _singleEvents.emit(SingleEvent.ToastTrackersControlDisabled) diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index ea5bb250..39abf099 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -69,9 +69,9 @@ android:label="AppTrackersFragment" > { GlobalScope } single { AppListsRepository( permissionsModule = get(), - dummySystemApp = get(named("DummySystemApp")), - dummyCompatibilityApp = get(named("DummyCompatibilityApp")), context = androidContext(), - coroutineScope = get() + compatibilityAppLabel = get(named("CompatibilityAppLabel")), + compatibilityAppIcon = get(named("CompatibilityAppIcon")), + systemAppLabel = get(named("SystemAppLabel")), + systemAppIcon = get(named("SystemAppIcon")), + backgroundScope = get() ) } } 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 index bb209d89..3c41fc3a 100644 --- a/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt +++ b/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt @@ -1,5 +1,6 @@ /* - * Copyright (C) 2022 E FOUNDATION, 2022 - 2023 MURENA SAS + * Copyright (C) 2022 - 2023 MURENA SAS + * Copyright (C) 2022 - 2024 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 @@ -22,25 +23,28 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo +import android.graphics.drawable.Drawable import foundation.e.advancedprivacy.domain.entities.ApplicationDescription +import foundation.e.advancedprivacy.domain.entities.DisplayableApp 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.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext class AppListsRepository( private val permissionsModule: IPermissionsPrivacyModule, - val dummySystemApp: ApplicationDescription, - val dummyCompatibilityApp: ApplicationDescription, private val context: Context, - private val coroutineScope: CoroutineScope + private val compatibilityAppLabel: CharSequence, + private val compatibilityAppIcon: Drawable, + private val systemAppLabel: CharSequence, + private val systemAppIcon: Drawable, + private val backgroundScope: CoroutineScope ) { companion object { private const val PNAME_SETTINGS = "com.android.settings" @@ -48,57 +52,154 @@ class AppListsRepository( private const val PNAME_INTENT_VERIFICATION = "com.android.statementservice" private const val PNAME_MICROG_SERVICES_CORE = "com.google.android.gms" - val compatibiltyPNames = setOf( + private val compatibilityInternetPNames = setOf( PNAME_PWAPLAYER, PNAME_INTENT_VERIFICATION, PNAME_MICROG_SERVICES_CORE ) + + private val compatibilityLocationPNames = setOf( + PNAME_PWAPLAYER, + PNAME_INTENT_VERIFICATION + ) + + private val compatibilityPNames = compatibilityInternetPNames + compatibilityLocationPNames + } + + private val _displayableApp = MutableStateFlow>(emptySet()) + val displayableApp: StateFlow> = _displayableApp + + fun getAppById(id: String): DisplayableApp? = _displayableApp.value.find { it.id == id } + + fun getInternetAppByApId(apId: String): DisplayableApp? { + return _displayableApp.value.find { app -> app.hasInternetPermission && app.apps.any { it.apId == apId } } + } + + 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 appResources = mutableMapOf>() + private suspend fun fetchAppDescriptions(fetchMissingIcons: Boolean = false) { + val compatibilyInternetApps = mutableSetOf() + val compatibilyLocationApps = mutableSetOf() + val systemInternetApps = mutableSetOf() + val systemLocationApps = mutableSetOf() + val appDescs = mutableSetOf() + val tempDisplayableApp = mutableSetOf() + + val fetchResourcesJobs: MutableList = mutableListOf() + 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) - } + permissionsModule.getApplications().forEach { (appDesc: ApplicationDescription, pkgInfo: PackageInfo) -> + when { + pkgInfo.packageName in compatibilityPNames -> { + if (pkgInfo.packageName in compatibilityInternetPNames) { + compatibilyInternetApps.add(appDesc) + } - val hiddenAppsFilter = { packageInfo: PackageInfo -> - hasInternetPermission(packageInfo) && - isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames) - } + if (pkgInfo.packageName in compatibilityLocationPNames) { + compatibilyLocationApps.add(appDesc) + } + appDescs.add(appDesc) + } - val compatibilityAppsFilter = { packageInfo: PackageInfo -> - packageInfo.packageName in compatibiltyPNames - } + !isNotHiddenSystemApp(pkgInfo.applicationInfo, launcherPackageNames) -> { + if (hasInternetPermission(pkgInfo)) { + systemInternetApps.add(appDesc) + } - val visibleApps = recycleIcons( - newApps = permissionsModule.getApplications(visibleAppsFilter), - fetchMissingIcons = fetchMissingIcons - ) - val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter) - val compatibilityApps = permissionsModule.getApplications(compatibilityAppsFilter) + if (hasLocationPermission(pkgInfo)) { + systemLocationApps.add(appDesc) + } + appDescs.add(appDesc) + } - updateMaps(visibleApps + hiddenApps + compatibilityApps) + else -> { + val hasInternetPermission = hasInternetPermission(pkgInfo) + val hasLocationPermission = hasLocationPermission(pkgInfo) + + if (hasInternetPermission || hasLocationPermission) { + appDescs.add(appDesc) + + val id = appDesc.apId + if (appResources.contains(id)) { + buildDisplayableApp(appDesc, hasInternetPermission, hasLocationPermission)?.let { + tempDisplayableApp.add(it) + } + } else { + fetchResourcesJobs.add( + backgroundScope.launch { + val label = permissionsModule.getApplicationLabel(pkgInfo.applicationInfo) + val icon = permissionsModule.getApplicationIcon(appDesc) + + if (icon != null) { + appResources[id] = label to icon + buildDisplayableApp(appDesc, hasInternetPermission, hasLocationPermission)?.let { + tempDisplayableApp.add(it) + } + } + } + ) + } + } + } + } + } + + updateMaps(appDescs.toList()) - allProfilesAppDescriptions.emit( - Triple( - visibleApps + dummySystemApp + dummyCompatibilityApp, - hiddenApps, - compatibilityApps + val displayableAppsJob = backgroundScope.launch { + fetchResourcesJobs.joinAll() + updateDisplayableApps( + tempDisplayableApp, + compatibilyInternetApps, + compatibilyLocationApps, + systemInternetApps, + systemLocationApps ) - ) + } + + if (fetchMissingIcons) { + displayableAppsJob.join() + } } - 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 buildDisplayableApp( + appDesc: ApplicationDescription, + hasInternetPermission: Boolean, + hasLocationPermission: Boolean + ): DisplayableApp? { + val id = appDesc.apId + return appResources[id]?.let { (label, icon) -> + DisplayableApp( + id = id, + label = label, + icon = icon, + apps = setOf(appDesc), + hasLocationPermission = hasLocationPermission, + hasInternetPermission = hasInternetPermission, + profileType = appDesc.profileType ) } } @@ -118,12 +219,72 @@ class AppListsRepository( appsByAPId = byApId } + private fun updateDisplayableApps( + tempDisplayableApp: MutableSet, + compatibilyInternetApps: MutableSet, + compatibilyLocationApps: MutableSet, + systemInternetApps: MutableSet, + systemLocationApps: MutableSet + ) { + tempDisplayableApp.add( + DisplayableApp( + id = "compatibility-internet", + label = compatibilityAppLabel, + icon = compatibilityAppIcon, + apps = compatibilyInternetApps, + hasLocationPermission = false, + hasInternetPermission = true, + profileType = ProfileType.MAIN + ) + ) + + tempDisplayableApp.add( + DisplayableApp( + id = "compatibility-location", + label = compatibilityAppLabel, + icon = compatibilityAppIcon, + apps = compatibilyLocationApps, + hasLocationPermission = true, + hasInternetPermission = false, + profileType = ProfileType.MAIN + ) + ) + + tempDisplayableApp.add( + DisplayableApp( + id = "system-internet", + label = systemAppLabel, + icon = systemAppIcon, + apps = systemInternetApps, + hasLocationPermission = false, + hasInternetPermission = true, + profileType = ProfileType.MAIN + ) + ) + + tempDisplayableApp.add( + DisplayableApp( + id = "system-location", + label = systemAppLabel, + icon = systemAppIcon, + apps = systemLocationApps, + hasLocationPermission = true, + hasInternetPermission = false, + profileType = ProfileType.MAIN + ) + ) + + _displayableApp.update { + tempDisplayableApp + } + } + private var lastFetchApps = 0 private var refreshAppJob: Job? = null - private fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? { + fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? { if (refreshAppJob == null || refreshAppJob?.isCompleted == true) { - refreshAppJob = coroutineScope.launch(Dispatchers.IO) { + refreshAppJob = backgroundScope.launch { if (appsByUid.isEmpty() || appsByAPId.isEmpty() || force || context.packageManager.getChangedPackages(lastFetchApps) != null ) { @@ -139,120 +300,15 @@ class AppListsRepository( 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 getRealApps(app: ApplicationDescription): List { - return when (app) { - dummySystemApp -> getHiddenSystemApps() - dummyCompatibilityApp -> getCompatibilityApps() - else -> listOf(app) - } - } - - suspend fun applyForHiddenApps(app: ApplicationDescription, action: suspend (ApplicationDescription) -> Unit) { - mapReduceForHiddenApps(app = app, map = action, reduce = {}) - } - - suspend fun mapReduceForHiddenApps( - app: ApplicationDescription, - map: suspend (ApplicationDescription) -> T, - reduce: suspend (List) -> R - ): R { - return reduce(getRealApps(app).map { map(it) }) - } - - 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] - } - } - - suspend fun getDisplayableApp(apId: String): ApplicationDescription? = withContext(Dispatchers.IO) { - if (apId.isBlank()) return@withContext null - - refreshAppDescriptions() - - appsByAPId[apId]?.let { app -> - when (app) { - in getCompatibilityApps() -> dummyCompatibilityApp - in getHiddenSystemApps() -> dummySystemApp - else -> { - if (app.icon == null) { - app.copy(icon = permissionsModule.getApplicationIcon(app)) - } else { - app - } - } - } - } - } - - private val allProfilesAppDescriptions = MutableStateFlow( - Triple( - emptyList(), - emptyList(), - emptyList() - ) - ) - private fun hasInternetPermission(packageInfo: PackageInfo): Boolean { return packageInfo.requestedPermissions?.contains(Manifest.permission.INTERNET) == true } + private fun hasLocationPermission(packageInfo: PackageInfo): Boolean { + return packageInfo.requestedPermissions?.contains(Manifest.permission.ACCESS_FINE_LOCATION) == true || + packageInfo.requestedPermissions?.contains(Manifest.permission.ACCESS_COARSE_LOCATION) == true + } + @Suppress("ReturnCount") private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List): Boolean { if (app.packageName == PNAME_SETTINGS) { @@ -269,23 +325,5 @@ class AppListsRepository( 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/DisplayableApp.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/DisplayableApp.kt new file mode 100644 index 00000000..e9441d4a --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/DisplayableApp.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 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 + +data class DisplayableApp( + val id: String, + val label: CharSequence, + val icon: Drawable, + val apps: Set, + val hasLocationPermission: Boolean, + val hasInternetPermission: Boolean, + val profileType: ProfileType +) 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 index 2b6ffcee..599cb376 100644 --- a/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/IPermissionsPrivacyModule.kt +++ b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/IPermissionsPrivacyModule.kt @@ -1,6 +1,6 @@ /* * Copyright (C) 2022 - 2023 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -37,7 +37,7 @@ interface IPermissionsPrivacyModule { profileType: ProfileType = ProfileType.MAIN ): ApplicationDescription - fun getApplications(filter: ((PackageInfo) -> Boolean)?): List + fun getApplications(): List> /** * List of permissions names used by an app, specified by its [packageName]. @@ -109,6 +109,8 @@ interface IPermissionsPrivacyModule { */ fun getApplicationIcon(app: ApplicationDescription): Drawable? + fun getApplicationLabel(appInfo: ApplicationInfo): CharSequence + /** * Authorize the specified package to be used as Vpn. * @return true if authorization has been set, false if an error has occurred. diff --git a/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleBase.kt b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleBase.kt index d52a4671..0a19519c 100644 --- a/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleBase.kt +++ b/core/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleBase.kt @@ -1,6 +1,6 @@ /* * Copyright (C) 2022 - 2023 MURENA SAS - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 - 2024 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 @@ -132,14 +132,14 @@ abstract class PermissionsPrivacyModuleBase(protected val context: Context) : IP return ApplicationDescription( packageName = appInfo.packageName, uid = appInfo.uid, - label = getAppLabel(appInfo), + label = null, icon = null, profileId = profileId, profileType = profileType ) } - private fun getAppLabel(appInfo: ApplicationInfo): CharSequence { + override fun getApplicationLabel(appInfo: ApplicationInfo): CharSequence { return context.packageManager.getApplicationLabel(appInfo) } diff --git a/permissionseos/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleImpl.kt b/permissionseos/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleImpl.kt index c61912d4..43e33520 100644 --- a/permissionseos/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleImpl.kt +++ b/permissionseos/src/main/java/foundation/e/advancedprivacy/externalinterfaces/permissions/PermissionsPrivacyModuleImpl.kt @@ -85,7 +85,7 @@ class PermissionsPrivacyModuleImpl(context: Context) : PermissionsPrivacyModuleB return true } - override fun getApplications(filter: ((PackageInfo) -> Boolean)?): List { + override fun getApplications(): List> { val pm = context.packageManager val mainUserId = UserHandle.myUserId() val workProfileId = getWorkProfile()?.id @@ -93,13 +93,12 @@ class PermissionsPrivacyModuleImpl(context: Context) : PermissionsPrivacyModuleB val userIds = listOf(mainUserId, workProfileId).filterNotNull() return userIds.map { profileId -> pm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, profileId) - .filter { filter?.invoke(it) ?: true } - .map { + .map { pkgInfo: PackageInfo -> buildApplicationDescription( - appInfo = it.applicationInfo, + appInfo = pkgInfo.applicationInfo, profileId = profileId, profileType = if (profileId == mainUserId) MAIN else WORK - ) + ) to pkgInfo } }.flatten() } -- GitLab From 9e8be9526614d599c52fad56719f239bb667c048 Mon Sep 17 00:00:00 2001 From: jacquarg Date: Mon, 22 Jul 2024 10:16:57 +0200 Subject: [PATCH 2/7] tech:148: revices fixes, add ToggleableApp type for readability --- .../usecases/IpScramblingStateUseCase.kt | 9 ++++---- .../domain/usecases/TrackerDetailsUseCase.kt | 10 +++++--- .../InternetPrivacyFragment.kt | 4 ++-- .../internetprivacy/InternetPrivacyState.kt | 4 ++-- .../InternetPrivacyViewModel.kt | 4 ++-- .../internetprivacy/ToggleAppsAdapter.kt | 15 ++++++------ .../trackerdetails/TrackerAppsAdapter.kt | 18 +++++++-------- .../trackerdetails/TrackerDetailsState.kt | 4 ++-- .../trackerdetails/TrackerDetailsViewModel.kt | 4 ++-- .../domain/entities/ToggleableApp.kt | 23 +++++++++++++++++++ 10 files changed, 61 insertions(+), 34 deletions(-) create mode 100644 core/src/main/java/foundation/e/advancedprivacy/domain/entities/ToggleableApp.kt 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 bc12a326..335d1ab3 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 @@ -22,6 +22,7 @@ import foundation.e.advancedprivacy.data.repositories.AppListsRepository import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.FeatureState import foundation.e.advancedprivacy.domain.entities.ProfileType +import foundation.e.advancedprivacy.domain.entities.ToggleableApp import foundation.e.advancedprivacy.domain.repositories.LocalStateRepository import foundation.e.advancedprivacy.ipscrambler.OrbotSupervisor import kotlinx.coroutines.CoroutineScope @@ -59,7 +60,7 @@ class IpScramblingStateUseCase( localStateRepository.setIpScramblingSetting(enabled = hideIp) } - suspend fun getAppsWithUseTor(): Flow>> { + suspend fun getAppsUsingTor(): Flow> { return combine( appListsRepository.displayableApp.map { apps -> apps.filter { app -> @@ -69,7 +70,7 @@ class IpScramblingStateUseCase( whitelistedPackages ) { apps, pNames -> apps.map { app -> - app to !app.isWhitelisted(pNames) + ToggleableApp(app = app, isOn = !app.isWhitelisted(pNames)) } } } @@ -97,7 +98,7 @@ class IpScramblingStateUseCase( } } - private fun DisplayableApp.isWhitelisted(whitelistedPNames: Set = whitelistedPackages.value): Boolean = apps.any { - it.packageName in whitelistedPNames + private fun DisplayableApp.isWhitelisted(whitelistedPackageNames: Set = whitelistedPackages.value): Boolean = apps.any { + it.packageName in whitelistedPackageNames } } diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt index 36ea4dde..b2959f7d 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackerDetailsUseCase.kt @@ -19,6 +19,7 @@ package foundation.e.advancedprivacy.domain.usecases import foundation.e.advancedprivacy.data.repositories.AppListsRepository import foundation.e.advancedprivacy.domain.entities.DisplayableApp +import foundation.e.advancedprivacy.domain.entities.ToggleableApp import foundation.e.advancedprivacy.trackers.data.StatsDatabase import foundation.e.advancedprivacy.trackers.data.WhitelistRepository import foundation.e.advancedprivacy.trackers.domain.entities.Tracker @@ -41,7 +42,7 @@ class TrackerDetailsUseCase( trackersStateUseCase.updateAllTrackersBlockedState() } - suspend fun getAppsWithBlockedState(tracker: Tracker): List> { + suspend fun getAppsWithBlockedState(tracker: Tracker): List { return enrichWithBlockedState( statsDatabase.getApIds(tracker.id).mapNotNull { appListsRepository.getInternetAppByApId(it) @@ -50,9 +51,12 @@ class TrackerDetailsUseCase( ) } - suspend fun enrichWithBlockedState(apps: List, tracker: Tracker): List> { + suspend fun enrichWithBlockedState(apps: List, tracker: Tracker): List { return apps.map { app -> - app to app.apps.any { !filterHostnameUseCase.isWhitelisted(it.uid, tracker.id) } + ToggleableApp( + app = app, + isOn = app.apps.any { !filterHostnameUseCase.isWhitelisted(it.uid, tracker.id) } + ) } } 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 0d98cd3f..e71dc7a5 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 @@ -141,7 +141,7 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac // TODO: this should not be mandatory. binding.apps.post { (binding.apps.adapter as ToggleAppsAdapter?)?.setData( - list = state.appsWithUseTor, + list = state.appsUsingTor, isEnabled = state.mode == FeatureState.ON ) } @@ -156,7 +156,7 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac when { state.mode.isLoading || - state.appsWithUseTor.isEmpty() -> { + state.appsUsingTor.isEmpty() -> { binding.loader.visibility = View.VISIBLE viewIdsToHide.forEach { it.visibility = View.GONE } } 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 da0f32fd..e786383b 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,12 +17,12 @@ package foundation.e.advancedprivacy.features.internetprivacy -import foundation.e.advancedprivacy.domain.entities.DisplayableApp import foundation.e.advancedprivacy.domain.entities.FeatureState +import foundation.e.advancedprivacy.domain.entities.ToggleableApp data class InternetPrivacyState( val mode: FeatureState = FeatureState.OFF, - val appsWithUseTor: List> = emptyList(), + val appsUsingTor: List = emptyList(), val selectedLocation: String = "", val availableLocationIds: List = emptyList(), val forceRedraw: Boolean = false 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 698eff83..dfe9ae73 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 @@ -71,8 +71,8 @@ class InternetPrivacyViewModel( suspend fun doOnStartedState() = withContext(Dispatchers.IO) { launch { merge( - ipScramblingStateUseCase.getAppsWithUseTor().map { apps -> - _state.update { it.copy(appsWithUseTor = apps) } + ipScramblingStateUseCase.getAppsUsingTor().map { apps -> + _state.update { it.copy(appsUsingTor = apps) } }, ipScramblingStateUseCase.internetPrivacyMode.map { _state.update { s -> s.copy(mode = it) } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt index b0abf33f..1c49a3e8 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/internetprivacy/ToggleAppsAdapter.kt @@ -26,6 +26,7 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.domain.entities.DisplayableApp +import foundation.e.advancedprivacy.domain.entities.ToggleableApp class ToggleAppsAdapter( private val itemsLayout: Int, @@ -38,17 +39,17 @@ class ToggleAppsAdapter( val togglePermission: CheckBox = view.findViewById(R.id.toggle) - fun bind(item: Pair, isEnabled: Boolean) { - appName.text = item.first.label - togglePermission.isChecked = item.second + fun bind(item: ToggleableApp, isEnabled: Boolean) { + appName.text = item.app.label + togglePermission.isChecked = item.isOn togglePermission.isEnabled = isEnabled - itemView.findViewById(R.id.icon).setImageDrawable(item.first.icon) - togglePermission.setOnClickListener { listener(item.first) } + itemView.findViewById(R.id.icon).setImageDrawable(item.app.icon) + togglePermission.setOnClickListener { listener(item.app) } } } - var dataSet: List> = emptyList() + var dataSet: List = emptyList() set(value) { field = value notifyDataSetChanged() @@ -56,7 +57,7 @@ class ToggleAppsAdapter( var isEnabled: Boolean = true - fun setData(list: List>, isEnabled: Boolean = true) { + fun setData(list: List, isEnabled: Boolean = true) { this.isEnabled = isEnabled dataSet = list } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt index 6fd1dc74..46cf7d88 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerAppsAdapter.kt @@ -22,7 +22,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import foundation.e.advancedprivacy.databinding.ApptrackersItemTrackerToggleBinding -import foundation.e.advancedprivacy.domain.entities.DisplayableApp +import foundation.e.advancedprivacy.domain.entities.ToggleableApp class TrackerAppsAdapter( private val viewModel: TrackerDetailsViewModel @@ -33,21 +33,20 @@ class TrackerAppsAdapter( private val viewModel: TrackerDetailsViewModel ) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: Pair) { - val (app, isWhiteListed) = item - binding.title.text = app.label + fun bind(item: ToggleableApp) { + binding.title.text = item.app.label binding.toggle.apply { - this.isChecked = isWhiteListed + this.isChecked = item.isOn setOnClickListener { - viewModel.onToggleUnblockApp(app, isChecked) + viewModel.onToggleUnblockApp(item.app, isChecked) } } } } - private var dataSet: List> = emptyList() + private var dataSet: List = emptyList() - fun updateDataSet(new: List>) { + fun updateDataSet(new: List) { dataSet = new notifyDataSetChanged() } @@ -60,8 +59,7 @@ class TrackerAppsAdapter( } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val permission = dataSet[position] - holder.bind(permission) + holder.bind(dataSet[position]) } override fun getItemCount(): Int = dataSet.size diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt index 52e750b3..9c00200a 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsState.kt @@ -18,7 +18,7 @@ package foundation.e.advancedprivacy.features.trackers.trackerdetails -import foundation.e.advancedprivacy.domain.entities.DisplayableApp +import foundation.e.advancedprivacy.domain.entities.ToggleableApp import foundation.e.advancedprivacy.trackers.domain.entities.Tracker data class TrackerDetailsState( @@ -27,6 +27,6 @@ data class TrackerDetailsState( val detectedCount: Int = 0, val blockedCount: Int = 0, val leakedCount: Int = 0, - val appList: List> = emptyList(), + val appList: List = emptyList(), val isTrackersBlockingEnabled: Boolean = false ) diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt index eda495ab..a5b2bbdf 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/trackerdetails/TrackerDetailsViewModel.kt @@ -81,7 +81,7 @@ class TrackerDetailsViewModel( } trackerDetailsUseCase.toggleTrackerWhitelist( tracker, - state.value.appList.map { it.first }, + state.value.appList.map { it.app }, isBlocked ) _state.update { @@ -119,7 +119,7 @@ class TrackerDetailsViewModel( s.copy( isBlockAllActivated = !trackersStateUseCase.isWhitelisted(tracker), appList = trackerDetailsUseCase.enrichWithBlockedState( - s.appList.map { it.first }, + s.appList.map { it.app }, tracker ) ) diff --git a/core/src/main/java/foundation/e/advancedprivacy/domain/entities/ToggleableApp.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/ToggleableApp.kt new file mode 100644 index 00000000..bff4383c --- /dev/null +++ b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/ToggleableApp.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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.domain.entities + +data class ToggleableApp( + val isOn: Boolean, + val app: DisplayableApp +) -- GitLab From 993e115ec9a8f24c0e2a2624c684fdbbba870d72 Mon Sep 17 00:00:00 2001 From: jacquarg Date: Wed, 24 Jul 2024 09:28:47 +0200 Subject: [PATCH 3/7] tech:148: reduce block depth in fetchAppDescription algorythm. --- .../data/repositories/AppListsRepository.kt | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) 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 index 3c41fc3a..cd661efa 100644 --- a/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt +++ b/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt @@ -112,6 +112,9 @@ class AppListsRepository( ).mapNotNull { it.activityInfo?.packageName } permissionsModule.getApplications().forEach { (appDesc: ApplicationDescription, pkgInfo: PackageInfo) -> + val hasInternetPermission = hasInternetPermission(pkgInfo) + val hasLocationPermission = hasLocationPermission(pkgInfo) + when { pkgInfo.packageName in compatibilityPNames -> { if (pkgInfo.packageName in compatibilityInternetPNames) { @@ -125,45 +128,27 @@ class AppListsRepository( } !isNotHiddenSystemApp(pkgInfo.applicationInfo, launcherPackageNames) -> { - if (hasInternetPermission(pkgInfo)) { + if (hasInternetPermission) { systemInternetApps.add(appDesc) } - if (hasLocationPermission(pkgInfo)) { + if (hasLocationPermission) { systemLocationApps.add(appDesc) } appDescs.add(appDesc) } - else -> { - val hasInternetPermission = hasInternetPermission(pkgInfo) - val hasLocationPermission = hasLocationPermission(pkgInfo) - - if (hasInternetPermission || hasLocationPermission) { - appDescs.add(appDesc) - - val id = appDesc.apId - if (appResources.contains(id)) { - buildDisplayableApp(appDesc, hasInternetPermission, hasLocationPermission)?.let { - tempDisplayableApp.add(it) - } - } else { - fetchResourcesJobs.add( - backgroundScope.launch { - val label = permissionsModule.getApplicationLabel(pkgInfo.applicationInfo) - val icon = permissionsModule.getApplicationIcon(appDesc) - - if (icon != null) { - appResources[id] = label to icon - buildDisplayableApp(appDesc, hasInternetPermission, hasLocationPermission)?.let { - tempDisplayableApp.add(it) - } - } - } - ) + hasInternetPermission || hasLocationPermission -> { + appDescs.add(appDesc) + val fetchJob = getOrFetchResourcesAndBuildApp(appDesc, pkgInfo) { + buildDisplayableApp(appDesc, hasInternetPermission, hasLocationPermission)?.let { + tempDisplayableApp.add(it) } } + fetchJob?.let { fetchResourcesJobs.add(it) } } + + else -> {} // Skip other apps. } } @@ -185,6 +170,24 @@ class AppListsRepository( } } + private fun getOrFetchResourcesAndBuildApp(appDesc: ApplicationDescription, pkgInfo: PackageInfo, onResourcesReady: () -> Unit): Job? { + val id = appDesc.apId + return if (appResources.contains(id)) { + onResourcesReady() + null + } else { + backgroundScope.launch { + val label = permissionsModule.getApplicationLabel(pkgInfo.applicationInfo) + val icon = permissionsModule.getApplicationIcon(appDesc) + + if (icon != null) { + appResources[id] = label to icon + onResourcesReady() + } + } + } + } + private fun buildDisplayableApp( appDesc: ApplicationDescription, hasInternetPermission: Boolean, -- GitLab From bb9e474314dd87e681ef41d9b354a59e47d49afd Mon Sep 17 00:00:00 2001 From: jacquarg Date: Fri, 2 Aug 2024 08:53:19 +0200 Subject: [PATCH 4/7] tech:148: MR review, improve naming. --- .../domain/usecases/IpScramblingStateUseCase.kt | 2 +- .../features/internetprivacy/InternetPrivacyFragment.kt | 4 ++-- .../features/internetprivacy/InternetPrivacyState.kt | 2 +- .../features/internetprivacy/InternetPrivacyViewModel.kt | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) 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 335d1ab3..e69d8ffd 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 @@ -60,7 +60,7 @@ class IpScramblingStateUseCase( localStateRepository.setIpScramblingSetting(enabled = hideIp) } - suspend fun getAppsUsingTor(): Flow> { + suspend fun getTorToggleableApp(): Flow> { return combine( appListsRepository.displayableApp.map { apps -> apps.filter { app -> 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 e71dc7a5..ec4fe5ee 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 @@ -141,7 +141,7 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac // TODO: this should not be mandatory. binding.apps.post { (binding.apps.adapter as ToggleAppsAdapter?)?.setData( - list = state.appsUsingTor, + list = state.torToggleableApp, isEnabled = state.mode == FeatureState.ON ) } @@ -156,7 +156,7 @@ class InternetPrivacyFragment : NavToolbarFragment(R.layout.fragment_internet_ac when { state.mode.isLoading || - state.appsUsingTor.isEmpty() -> { + state.torToggleableApp.isEmpty() -> { binding.loader.visibility = View.VISIBLE viewIdsToHide.forEach { it.visibility = View.GONE } } 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 e786383b..b02ce49d 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 @@ -22,7 +22,7 @@ import foundation.e.advancedprivacy.domain.entities.ToggleableApp data class InternetPrivacyState( val mode: FeatureState = FeatureState.OFF, - val appsUsingTor: List = emptyList(), + val torToggleableApp: List = emptyList(), val selectedLocation: String = "", val availableLocationIds: List = emptyList(), val forceRedraw: Boolean = false 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 dfe9ae73..d9989730 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 @@ -71,8 +71,8 @@ class InternetPrivacyViewModel( suspend fun doOnStartedState() = withContext(Dispatchers.IO) { launch { merge( - ipScramblingStateUseCase.getAppsUsingTor().map { apps -> - _state.update { it.copy(appsUsingTor = apps) } + ipScramblingStateUseCase.getTorToggleableApp().map { apps -> + _state.update { it.copy(torToggleableApp = apps) } }, ipScramblingStateUseCase.internetPrivacyMode.map { _state.update { s -> s.copy(mode = it) } -- GitLab From f399cdfabfb0cc7a4267c6bc434485b47e7f22ec Mon Sep 17 00:00:00 2001 From: jacquarg Date: Fri, 2 Aug 2024 11:48:14 +0200 Subject: [PATCH 5/7] tech:148: MR review, improve code style. --- .../domain/usecases/IpScramblingStateUseCase.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 e69d8ffd..2b2ee5ee 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 @@ -77,14 +77,12 @@ class IpScramblingStateUseCase( fun toggleBypassTor(app: DisplayableApp) { whitelistedPackages.update { whitelist -> - val mutable = whitelist.toMutableSet() - val packageNames = app.apps.map { it.packageName } + val packageNames = app.apps.map { it.packageName }.toSet() if (app.isWhitelisted()) { - mutable.removeAll(packageNames) + whitelist.minus(packageNames) } else { - mutable.addAll(packageNames) + whitelist.union(packageNames) } - mutable } } -- GitLab From 0e9770b34f163aee8788e43e1a403055330e2632 Mon Sep 17 00:00:00 2001 From: jacquarg Date: Thu, 8 Aug 2024 23:04:50 +0200 Subject: [PATCH 6/7] tech:148: MR review, improve naming and code style. --- .../data/repositories/AppListsRepository.kt | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) 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 index cd661efa..83b337b2 100644 --- a/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt +++ b/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt @@ -140,12 +140,25 @@ class AppListsRepository( hasInternetPermission || hasLocationPermission -> { appDescs.add(appDesc) - val fetchJob = getOrFetchResourcesAndBuildApp(appDesc, pkgInfo) { - buildDisplayableApp(appDesc, hasInternetPermission, hasLocationPermission)?.let { - tempDisplayableApp.add(it) - } + val onResourcesReady: (CharSequence, Drawable) -> Unit = { label, icon -> + tempDisplayableApp.add( + buildDisplayableApp( + appDesc, + label, + icon, + hasInternetPermission, + hasLocationPermission + ) + ) + } + + val resources = appResources[appDesc.apId] + if (resources != null) { + val (label, icon) = resources + onResourcesReady(label, icon) + } else { + fetchResourcesJobs.add(fetchResources(appDesc, pkgInfo, onResourcesReady)) } - fetchJob?.let { fetchResourcesJobs.add(it) } } else -> {} // Skip other apps. @@ -170,41 +183,39 @@ class AppListsRepository( } } - private fun getOrFetchResourcesAndBuildApp(appDesc: ApplicationDescription, pkgInfo: PackageInfo, onResourcesReady: () -> Unit): Job? { + private fun fetchResources( + appDesc: ApplicationDescription, + pkgInfo: PackageInfo, + onResourcesReady: (CharSequence, Drawable) -> Unit + ): Job { val id = appDesc.apId - return if (appResources.contains(id)) { - onResourcesReady() - null - } else { - backgroundScope.launch { - val label = permissionsModule.getApplicationLabel(pkgInfo.applicationInfo) - val icon = permissionsModule.getApplicationIcon(appDesc) - - if (icon != null) { - appResources[id] = label to icon - onResourcesReady() - } + return backgroundScope.launch { + val label = permissionsModule.getApplicationLabel(pkgInfo.applicationInfo) + val icon = permissionsModule.getApplicationIcon(appDesc) + + if (icon != null) { + appResources[id] = label to icon + onResourcesReady(label, icon) } } } private fun buildDisplayableApp( appDesc: ApplicationDescription, + label: CharSequence, + icon: Drawable, hasInternetPermission: Boolean, hasLocationPermission: Boolean - ): DisplayableApp? { - val id = appDesc.apId - return appResources[id]?.let { (label, icon) -> - DisplayableApp( - id = id, - label = label, - icon = icon, - apps = setOf(appDesc), - hasLocationPermission = hasLocationPermission, - hasInternetPermission = hasInternetPermission, - profileType = appDesc.profileType - ) - } + ): DisplayableApp { + return DisplayableApp( + id = appDesc.apId, + label = label, + icon = icon, + apps = setOf(appDesc), + hasLocationPermission = hasLocationPermission, + hasInternetPermission = hasInternetPermission, + profileType = appDesc.profileType + ) } private fun updateMaps(apps: List) { -- GitLab From 002a4bcb910a78fb6ead25d9f54119d625e9c2a4 Mon Sep 17 00:00:00 2001 From: jacquarg Date: Fri, 9 Aug 2024 17:17:26 +0200 Subject: [PATCH 7/7] tech:148: MR fixes, improve naming. --- .../domain/usecases/IpScramblingStateUseCase.kt | 2 +- .../domain/usecases/TrackersAndAppsListsUseCase.kt | 2 +- .../data/repositories/AppListsRepository.kt | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) 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 2b2ee5ee..c8e21040 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 @@ -62,7 +62,7 @@ class IpScramblingStateUseCase( suspend fun getTorToggleableApp(): Flow> { return combine( - appListsRepository.displayableApp.map { apps -> + appListsRepository.displayableApps.map { apps -> apps.filter { app -> app.hasInternetPermission && app.profileType == ProfileType.MAIN } diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt index d697b4da..408247c2 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersAndAppsListsUseCase.kt @@ -87,7 +87,7 @@ class TrackersAndAppsListsUseCase( } private suspend fun buildAllAppList(countByApp: Map): List { - return appListsRepository.displayableApp.first() + return appListsRepository.displayableApps.first() .filter { it.hasInternetPermission } .map { app: DisplayableApp -> AppWithCount(app = app, count = countByApp[app] ?: 0) 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 index 83b337b2..448a10af 100644 --- a/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt +++ b/core/src/main/java/foundation/e/advancedprivacy/data/repositories/AppListsRepository.kt @@ -66,13 +66,13 @@ class AppListsRepository( private val compatibilityPNames = compatibilityInternetPNames + compatibilityLocationPNames } - private val _displayableApp = MutableStateFlow>(emptySet()) - val displayableApp: StateFlow> = _displayableApp + private val _displayableApps = MutableStateFlow>(emptySet()) + val displayableApps: StateFlow> = _displayableApps - fun getAppById(id: String): DisplayableApp? = _displayableApp.value.find { it.id == id } + fun getAppById(id: String): DisplayableApp? = _displayableApps.value.find { it.id == id } fun getInternetAppByApId(apId: String): DisplayableApp? { - return _displayableApp.value.find { app -> app.hasInternetPermission && app.apps.any { it.apId == apId } } + return _displayableApps.value.find { app -> app.hasInternetPermission && app.apps.any { it.apId == apId } } } private var appsByUid = mapOf() @@ -288,7 +288,7 @@ class AppListsRepository( ) ) - _displayableApp.update { + _displayableApps.update { tempDisplayableApp } } -- GitLab