diff --git a/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt b/app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt index 938e5a15fea4b3327a65fd68e0dabe4aae7e7191..7503646fccc3f1e9a6496141caeb10efe0d15caa 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 e844473263f0dcb971eb1a35abc9a46804939038..ebd6e07befa69c0f50124058132875f7c4a37667 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 dfd32b6c48b47ba62d461ccd6320bedba7e9a427..0000000000000000000000000000000000000000 --- 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 b156246d57dc709763b9dc3cc1367a1c603e5ebc..56ab3f637955e1172551a3aa73952e729ac66170 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 00613dd49002f9b25a34f96545389c4123e4060e..c8e210404b3202443274598911185adfbc07fe66 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,71 @@ 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.entities.ToggleableApp 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 getTorToggleableApp(): Flow> { + return combine( + appListsRepository.displayableApps.map { apps -> + apps.filter { app -> + app.hasInternetPermission && app.profileType == ProfileType.MAIN + } + }, + whitelistedPackages + ) { apps, pNames -> + apps.map { app -> + ToggleableApp(app = app, isOn = !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 packageNames = app.apps.map { it.packageName }.toSet() + if (app.isWhitelisted()) { + whitelist.minus(packageNames) } else { - rawList.add(packageName) + whitelist.union(packageNames) } } - orbotSupervisor.appList = rawList } val availablesLocations: List = orbotSupervisor.getAvailablesLocations().sorted() @@ -101,4 +95,8 @@ class IpScramblingStateUseCase( orbotSupervisor.setExitCountryCode(locationId) } } + + 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 eb247d7b124146c430f706736170daac6e662380..b2959f7d903e61c28af89af7e246a71486493cd8 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,8 @@ 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.entities.ToggleableApp import foundation.e.advancedprivacy.trackers.data.StatsDatabase import foundation.e.advancedprivacy.trackers.data.WhitelistRepository import foundation.e.advancedprivacy.trackers.domain.entities.Tracker @@ -30,30 +32,31 @@ 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) - } + ToggleableApp( + app = app, + isOn = 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 b02f43b6c7833a96f4e6ea80f3bedf703b373469..408247c2c94f65f2c226af3cd1c437fbb2f5e1cb 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.displayableApps.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 3ae3f446653ef96ff3ef9f3b972f9d3e7fbf71b5..c2dddfc71a8f65593f4af96d3ccf6a617496b5f6 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 0b50c8e141d0b5ce23bd33f14a34b100cba5050a..14a77ea1020531b29493134aa244295343b9d666 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 badf0ab9c3175cab525547f59560e2c0915b78ee..e9835e879d9905904bd3d0a67335bfce29b73b38 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 4ff0095876ca473389e107a0769b960f13322829..e1fc7e430fa3263afe71304fdef605efcd9b6b22 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 9fec91e5a3b979417536fa385021b31dcc15ab32..ec4fe5eea00363939701631322989b473433520a 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.torToggleableApp, 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.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 4966431f70e3fd5a43e244ee94670644e2289876..b02ce49d9dbc3260001c7453cf2c89910484fe49 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.FeatureState +import foundation.e.advancedprivacy.domain.entities.ToggleableApp data class InternetPrivacyState( val mode: FeatureState = FeatureState.OFF, - val availableApps: List = emptyList(), - val bypassTorApps: Collection = emptyList(), + val torToggleableApp: 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 748ddd3afd8776ceda2eae749b0ed51b0ebc72ae..d99897305ac8cfc96f3426ce05dd69cdac6812b2 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.getTorToggleableApp().map { apps -> + _state.update { it.copy(torToggleableApp = 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 205abce4de9172477743751ed16ca8b7a5257079..1c49a3e8a80e8acd375eff50d81df46122a54862 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,31 @@ 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 +import foundation.e.advancedprivacy.domain.entities.ToggleableApp 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) { - 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.packageName) } + 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/ListsTabPagerAdapter.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/ListsTabPagerAdapter.kt index 66d8e71edaa28038293bb08a8b1970580cac7447..3897c63779255e7b19f5ca280ea41ebf7f29e7a6 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 587ef32ea3c1ad119f93f753a6c5d8296872309d..b416f2d820ce47cf479907725cf9a55a8ea0addf 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 adb20ceae6734f9bcf5ee7bc34645aa760590145..99280ddd873d4b8882aeb751740ace68c7c196ff 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 ede918f34affc7a4a82f5ebf190c10bae7042d1d..4bb754bd8017b7227165e95d52d9d2fe5adecb1d 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 4d1401f618ec862d3fe90b77cf002aa246c308db..726be53c45fc12a5ca27d4e149d4c035c2bbf164 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 da7142740c4e9c854882783e6c496907c4f63773..46cf7d883e07cc72417a00ccc69b3983c82c052d 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.ToggleableApp class TrackerAppsAdapter( private val viewModel: TrackerDetailsViewModel @@ -32,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() } @@ -59,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 9cbc39939b647a99f1c94403d2af5d5749255028..9c00200a4413fcd04e7d1f2f703a389292b373df 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.ToggleableApp 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 1a87f94bbbab763cf5a9ccf40aeee2a38d35f50c..a5b2bbdf2a46bef48c627a64bd87840514fe7434 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) @@ -80,7 +81,7 @@ class TrackerDetailsViewModel( } trackerDetailsUseCase.toggleTrackerWhitelist( tracker, - state.value.appList.map { it.first }, + state.value.appList.map { it.app }, isBlocked ) _state.update { @@ -118,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/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index ea5bb2501c78fd04ceacbb1c0e2632570fdb7aff..39abf099e04c71f96dc3a4d41eb477e189671f46 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 bb209d89b80e6087a9e7102b25d779a8621f572b..448a10af844d8c2636c26a108ae8d72c9413efca 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,61 +52,172 @@ 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 _displayableApps = MutableStateFlow>(emptySet()) + val displayableApps: StateFlow> = _displayableApps + + fun getAppById(id: String): DisplayableApp? = _displayableApps.value.find { it.id == id } + + fun getInternetAppByApId(apId: String): DisplayableApp? { + return _displayableApps.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) -> + val hasInternetPermission = hasInternetPermission(pkgInfo) + val hasLocationPermission = hasLocationPermission(pkgInfo) - val hiddenAppsFilter = { packageInfo: PackageInfo -> - hasInternetPermission(packageInfo) && - isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames) - } + when { + pkgInfo.packageName in compatibilityPNames -> { + if (pkgInfo.packageName in compatibilityInternetPNames) { + compatibilyInternetApps.add(appDesc) + } - val compatibilityAppsFilter = { packageInfo: PackageInfo -> - packageInfo.packageName in compatibiltyPNames - } + if (pkgInfo.packageName in compatibilityLocationPNames) { + compatibilyLocationApps.add(appDesc) + } + appDescs.add(appDesc) + } - val visibleApps = recycleIcons( - newApps = permissionsModule.getApplications(visibleAppsFilter), - fetchMissingIcons = fetchMissingIcons - ) - val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter) - val compatibilityApps = permissionsModule.getApplications(compatibilityAppsFilter) + !isNotHiddenSystemApp(pkgInfo.applicationInfo, launcherPackageNames) -> { + if (hasInternetPermission) { + systemInternetApps.add(appDesc) + } - updateMaps(visibleApps + hiddenApps + compatibilityApps) + if (hasLocationPermission) { + systemLocationApps.add(appDesc) + } + appDescs.add(appDesc) + } - allProfilesAppDescriptions.emit( - Triple( - visibleApps + dummySystemApp + dummyCompatibilityApp, - hiddenApps, - compatibilityApps + hasInternetPermission || hasLocationPermission -> { + appDescs.add(appDesc) + 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)) + } + } + + else -> {} // Skip other apps. + } + } + + updateMaps(appDescs.toList()) + + 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 fetchResources( + appDesc: ApplicationDescription, + pkgInfo: PackageInfo, + onResourcesReady: (CharSequence, Drawable) -> Unit + ): Job { + val id = appDesc.apId + 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 { + return DisplayableApp( + id = appDesc.apId, + label = label, + icon = icon, + apps = setOf(appDesc), + hasLocationPermission = hasLocationPermission, + hasInternetPermission = hasInternetPermission, + profileType = appDesc.profileType + ) + } + private fun updateMaps(apps: List) { val byUid = mutableMapOf() val byApId = mutableMapOf() @@ -118,12 +233,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 + ) + ) + + _displayableApps.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 +314,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 +339,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 0000000000000000000000000000000000000000..e9441d4ade54df637919eaf81d019b4d8ad37653 --- /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/domain/entities/ToggleableApp.kt b/core/src/main/java/foundation/e/advancedprivacy/domain/entities/ToggleableApp.kt new file mode 100644 index 0000000000000000000000000000000000000000..bff4383c2f9a2670a576114a60ecfcf44d2d8bf0 --- /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 +) 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 2b6ffcee6ce6f32ed78b693d2a353b803f9ce024..599cb376ab6746ca0dbfce9bec0f20e86e278262 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 d52a4671975d843ffe9221ece28b4a9ab3cd0256..0a19519ca6e5df4ce6ae33e0d541940135bcbf96 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 c61912d43b7dd51965c878c719b14e311365cbee..43e33520813025fa1266a749479b62b8e67ad203 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() }