Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a6e419a authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

tech:148: Refac app list repository to handle apps with location permission.

parent cabbe784
Loading
Loading
Loading
Loading
Loading
+38 −34
Original line number Diff line number Diff line
@@ -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> {
        CoroutineScope(
            SupervisorJob() +
                Dispatchers.IO +
                CoroutineExceptionHandler { _, throwable ->
                    Timber.e("Uncaught error in backgroundScope", throwable)
                }
        )
    }

    factory<Resources> { androidContext().resources }
    single<LocalStateRepository> {
        LocalStateRepositoryImpl(context = androidContext())
@@ -81,42 +96,23 @@ val appModule = module {
        )
    }

    single<ApplicationDescription>(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<CharSequence>(named("SystemAppLabel")) {
        androidContext().getString(R.string.dummy_system_app_label)
    }

    single<ApplicationDescription>(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<Drawable>(named("SystemAppIcon")) {
        androidContext().getDrawable(R.drawable.ic_e_app_logo)!!
    }

    single<NotificationContent>(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<CharSequence>(named("CompatibilityAppLabel")) {
        androidContext().getString(R.string.dummy_apps_compatibility_app_label)
    }
    single<Drawable>(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,
+2 −1
Original line number Diff line number Diff line
/*
 * 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
)

+16 −25
Original line number Diff line number Diff line
/*
 * 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<Tracker>, isBlocked: Boolean) {
        val realApIds = appListsRepository.getRealApps(app).map { it.apId }
    suspend fun toggleAppWhitelist(app: DisplayableApp, trackers: List<Tracker>, 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<Int, Int> {
        return appListsRepository.mapReduceForHiddenApps(
            app = app,
            map = {
                statsDatabase.getCallsForApp(app.apId)
            },
            reduce = { zip ->
                zip.unzip().let { (blocked, leaked) ->
    suspend fun getCalls(app: DisplayableApp): Pair<Int, Int> {
        return app.apps.map {
            statsDatabase.getCallsForApp(it.apId)
        }.unzip().let { (blocked, leaked) ->
            blocked.sum() to leaked.sum()
        }
    }
        )
    }

    suspend fun getTrackersWithBlockedList(app: ApplicationDescription): List<Pair<Tracker, Boolean>> {
        val realApIds = appListsRepository.getRealApps(app).map { it.apId }
    suspend fun getTrackersWithBlockedList(app: DisplayableApp): List<Pair<Tracker, Boolean>> {
        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<Tracker>): List<Pair<Tracker, Boolean>> {
        val realAppUids = appListsRepository.getRealApps(app).map { it.uid }
    suspend fun enrichWithBlockedState(app: DisplayableApp, trackers: List<Tracker>): List<Pair<Tracker, Boolean>> {
        val realAppUids = app.apps.map { it.uid }
        return trackers.map { tracker ->
            tracker to !realAppUids.any { uid ->
                filterHostnameUseCase.isWhitelisted(uid, tracker.id)
+39 −40
Original line number Diff line number Diff line
/*
 * 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<FeatureState> = 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<String> {
        return appListsRepository.getMainProfileHiddenSystemApps().map { it.packageName }
    suspend fun getAppsWithUseTor(): Flow<List<Pair<DisplayableApp, Boolean>>> {
        return combine(
            appListsRepository.displayableApp.map { apps ->
                apps.filter { app ->
                    app.hasInternetPermission && app.profileType == ProfileType.MAIN
                }

    val bypassTorApps: Set<String> 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
            },
            whitelistedPackages
        ) { apps, pNames ->
            apps.map { app ->
                app to !app.isWhitelisted(pNames)
            }
        if (AppListsRepository.compatibiltyPNames.any { it in whitelist }) {
            val mutable = whitelist.toMutableSet()
            mutable.removeAll(AppListsRepository.compatibiltyPNames)
            mutable.add(appListsRepository.dummyCompatibilityApp.packageName)
            whitelist = mutable
        }
        return whitelist
    }

    fun toggleBypassTor(packageName: String) {
        val visibleList = bypassTorApps.toMutableSet()
        val rawList = 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<String> = orbotSupervisor.getAvailablesLocations().sorted()
@@ -101,4 +96,8 @@ class IpScramblingStateUseCase(
            orbotSupervisor.setExitCountryCode(locationId)
        }
    }

    private fun DisplayableApp.isWhitelisted(whitelistedPNames: Set<String> = whitelistedPackages.value): Boolean = apps.any {
        it.packageName in whitelistedPNames
    }
}
+9 −10
Original line number Diff line number Diff line
/*
 * 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<ApplicationDescription>, isBlocked: Boolean) {
    suspend fun toggleTrackerWhitelist(tracker: Tracker, apps: List<DisplayableApp>, 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<Pair<ApplicationDescription, Boolean>> {
    suspend fun getAppsWithBlockedState(tracker: Tracker): List<Pair<DisplayableApp, Boolean>> {
        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<ApplicationDescription>, tracker: Tracker): List<Pair<ApplicationDescription, Boolean>> {
    suspend fun enrichWithBlockedState(apps: List<DisplayableApp>, tracker: Tracker): List<Pair<DisplayableApp, Boolean>> {
        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) }
        }
    }

Loading