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

Commit ef669659 authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

Merge branch '6556-use_pname_for_trackers_2' into 'main'

6556: add AdvancedPrivacy App Id in trackers stats to avoid appUid aliasing

See merge request !122
parents 2df577ca 6068cebe
Loading
Loading
Loading
Loading
Loading
+9 −5
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 MURENA SAS
 * Copyright (C) 2021 E FOUNDATION
 *
 * This program is free software: you can redistribute it and/or modify
@@ -47,6 +48,7 @@ import foundation.e.privacymodules.ipscrambler.IpScramblerModule
import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.permissions.data.ProfileType
import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule
import foundation.e.privacymodules.trackers.api.TrackTrackersPrivacyModule
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -70,7 +72,9 @@ class DependencyContainer(val app: Application) {
            packageName = context.packageName,
            uid = Process.myUid(),
            label = context.resources.getString(R.string.app_name),
            icon = null
            icon = null,
            profileId = -1,
            profileType = ProfileType.MAIN
        )
    }

@@ -168,12 +172,12 @@ class ViewModelsFactory(
    override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
        return when (modelClass) {
            AppTrackersViewModel::class.java -> {
                val fallbackUid = android.os.Process.myPid()
                val appUid = extras[DEFAULT_ARGS_KEY]
                    ?.getInt(AppTrackersFragment.PARAM_APP_UID, fallbackUid) ?: fallbackUid
                val app = extras[DEFAULT_ARGS_KEY]?.getInt(AppTrackersFragment.PARAM_APP_UID)?.let {
                    appListUseCase.getApp(it)
                } ?: appListUseCase.dummySystemApp

                AppTrackersViewModel(
                    appUid = appUid,
                    app = app,
                    trackersStateUseCase = trackersStateUseCase,
                    trackersStatisticsUseCase = trackersStatisticsUseCase,
                    getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase
+3 −3
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS
 * Copyright (C) 2021 E FOUNDATION, 2022 - 2023 MURENA SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
@@ -38,12 +38,12 @@ class AppsAdapter(
        val icon: ImageView = view.findViewById(R.id.icon)
        fun bind(item: AppWithCounts) {
            appName.text = item.label
            counts.text = itemView.context.getString(
            counts.text = if (item.trackersCount > 0) itemView.context.getString(
                R.string.trackers_app_trackers_counts,
                item.blockedTrackersCount,
                item.trackersCount,
                item.leaks
            )
            ) else ""
            icon.setImageDrawable(item.icon)

            itemView.setOnClickListener { listener(item.uid) }
+125 −72
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 E FOUNDATION, 2022 MURENA SAS
 * Copyright (C) 2022 E FOUNDATION, 2022 - 2023 MURENA SAS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
@@ -25,6 +25,7 @@ import android.content.pm.PackageInfo
import foundation.e.privacycentralapp.R
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.permissions.data.ProfileType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -32,6 +33,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

class AppListsRepository(
    private val permissionsModule: PermissionsPrivacyModule,
@@ -44,7 +46,7 @@ class AppListsRepository(
        private const val PNAME_INTENT_VERIFICATION = "com.android.statementservice"
        private const val PNAME_MICROG_SERVICES_CORE = "com.google.android.gms"

        val appsCompatibiltyPNames = setOf(
        val compatibiltyPNames = setOf(
            PNAME_PWAPLAYER, PNAME_INTENT_VERIFICATION, PNAME_MICROG_SERVICES_CORE
        )
    }
@@ -53,18 +55,22 @@ class AppListsRepository(
        packageName = "foundation.e.dummysystemapp",
        uid = -1,
        label = context.getString(R.string.dummy_system_app_label),
        icon = context.getDrawable(R.drawable.ic_e_app_logo)
        icon = context.getDrawable(R.drawable.ic_e_app_logo),
        profileId = -1,
        profileType = ProfileType.MAIN
    )

    val dummyAppsCompatibilityApp = ApplicationDescription(
    val dummyCompatibilityApp = ApplicationDescription(
        packageName = "foundation.e.dummyappscompatibilityapp",
        uid = -2,
        label = context.getString(R.string.dummy_apps_compatibility_app_label),
        icon = context.getDrawable(R.drawable.ic_apps_compatibility_components)
        icon = context.getDrawable(R.drawable.ic_apps_compatibility_components),
        profileId = -1,
        profileType = ProfileType.MAIN
    )

    private suspend fun fetchAppDescriptions() {
        val launcherPackageNames = pm.queryIntentActivities(
    private suspend fun fetchAppDescriptions(fetchMissingIcons: Boolean = false) {
        val launcherPackageNames = context.packageManager.queryIntentActivities(
            Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) },
            0
        ).mapNotNull { it.activityInfo?.packageName }
@@ -79,104 +85,151 @@ class AppListsRepository(
                isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames)
        }

        val aCFilter = { packageInfo: PackageInfo ->
            packageInfo.packageName in appsCompatibiltyPNames
        val compatibilityAppsFilter = { packageInfo: PackageInfo ->
            packageInfo.packageName in compatibiltyPNames
        }

        val visibleApps = permissionsModule.getApplications(visibleAppsFilter, true)
        val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter, false)
        val aCApps = permissionsModule.getApplications(aCFilter, false)
        val visibleApps = recycleIcons(
            newApps = permissionsModule.getApplications(visibleAppsFilter),
            fetchMissingIcons = fetchMissingIcons
        )
        val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter)
        val compatibilityApps = permissionsModule.getApplications(compatibilityAppsFilter)

        val workProfileVisibleApps = permissionsModule.getWorkProfileApplications(visibleAppsFilter, true)
        val workProfileHiddenApps = permissionsModule.getWorkProfileApplications(hiddenAppsFilter, false)
        val workProfileACApps = permissionsModule.getApplications(aCFilter, false)
        updateMaps(visibleApps + hiddenApps + compatibilityApps)

        appDescriptions.emit((visibleApps + dummySystemApp + dummyAppsCompatibilityApp) to hiddenApps)
        allProfilesAppDescriptions.emit(Triple(
            (visibleApps + workProfileVisibleApps + dummySystemApp + dummyAppsCompatibilityApp),
            (hiddenApps + workProfileHiddenApps),
            (aCApps + workProfileACApps)
        ))
        allProfilesAppDescriptions.emit(
            Triple(
                visibleApps + dummySystemApp + dummyCompatibilityApp,
                hiddenApps,
                compatibilityApps
            )
        )
    }

    private fun recycleIcons(
        newApps: List<ApplicationDescription>,
        fetchMissingIcons: Boolean
    ): List<ApplicationDescription> {
        val oldVisibleApps = allProfilesAppDescriptions.value.first
        return newApps.map { app ->
            app.copy(
                icon = oldVisibleApps.find { app.apId == it.apId }?.icon
                    ?: if (fetchMissingIcons) permissionsModule.getApplicationIcon(app) else null
            )
        }
    }

    private fun updateMaps(apps: List<ApplicationDescription>) {
        val byUid = mutableMapOf<Int, ApplicationDescription>()
        val byApId = mutableMapOf<String, ApplicationDescription>()
        apps.forEach { app ->
            byUid[app.uid]?.run { packageName > app.packageName } == true
            if (byUid[app.uid].let { it == null || it.packageName > app.packageName }) {
                byUid[app.uid] = app
            }

            byApId[app.apId] = app
        }
        appsByUid = byUid
        appsByAPId = byApId
    }

    private var lastFetchApps = 0
    private var refreshAppJob: Job? = null
    private fun refreshAppDescriptions() {
        if (refreshAppJob != null) {
            return
        } else {
    private fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? {
        if (refreshAppJob == null) {
            refreshAppJob = coroutineScope.launch(Dispatchers.IO) {
                fetchAppDescriptions()
                if (force || context.packageManager.getChangedPackages(lastFetchApps) != null) {
                    fetchAppDescriptions(fetchMissingIcons = fetchMissingIcons)
                    if (fetchMissingIcons) {
                        lastFetchApps = context.packageManager.getChangedPackages(lastFetchApps)
                            ?.sequenceNumber ?: lastFetchApps
                    }

                    refreshAppJob = null
                }
            }
        }

    fun getVisibleApps(): Flow<List<ApplicationDescription>> {
        return refreshAppJob
    }

    fun mainProfileApps(): Flow<List<ApplicationDescription>> {
        refreshAppDescriptions()
        return appDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } }
        return allProfilesAppDescriptions.map {
            it.first.filter { app -> app.profileType == ProfileType.MAIN }
                .sortedBy { app -> app.label.toString().lowercase() }
        }
    }

    fun getHiddenSystemApps(): List<ApplicationDescription> {
        return appDescriptions.value.second
    fun getMainProfileHiddenSystemApps(): List<ApplicationDescription> {
        return allProfilesAppDescriptions.value.second.filter { it.profileType == ProfileType.MAIN }
    }

    fun getAllProfilesVisibleApps(): Flow<List<ApplicationDescription>> {
    fun apps(): Flow<List<ApplicationDescription>> {
        refreshAppDescriptions()
        return allProfilesAppDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } }
        return allProfilesAppDescriptions.map {
            it.first.sortedBy { app -> app.label.toString().lowercase() }
        }
    }

    fun allApps(): Flow<List<ApplicationDescription>> {
        return allProfilesAppDescriptions.map {
            it.first + it.second + it.third
        }
    }

    fun getAllProfilesHiddenSystemApps(): List<ApplicationDescription> {
    private fun getHiddenSystemApps(): List<ApplicationDescription> {
        return allProfilesAppDescriptions.value.second
    }

    fun getAllProfilesACApps(): List<ApplicationDescription> {
    private fun getCompatibilityApps(): List<ApplicationDescription> {
        return allProfilesAppDescriptions.value.third
    }

    fun getAllApps(): Flow<List<ApplicationDescription>> = getAllProfilesVisibleApps()
        .map { it + getAllProfilesHiddenSystemApps() + getAllProfilesACApps()}

    fun getApplicationDescription(appUid: Int): ApplicationDescription? {
        return allProfilesAppDescriptions.value.first.find { it.uid == appUid }
    fun anyForHiddenApps(app: ApplicationDescription, test: (ApplicationDescription) -> Boolean): Boolean {
        return if (app == dummySystemApp) {
            getHiddenSystemApps().any { test(it) }
        } else if (app == dummyCompatibilityApp) {
            getCompatibilityApps().any { test(it) }
        } else test(app)
    }

    fun foldForHiddenApp(appUid: Int, appValueGetter: (Int) -> Int): Int {
        return if (appUid == dummySystemApp.uid) {
            getAllProfilesHiddenSystemApps().fold(0) { acc, app ->
                acc + appValueGetter(app.uid)
            }
        } else if (appUid == dummyAppsCompatibilityApp.uid) {
            getAllProfilesACApps().fold(0) { acc, app ->
                acc + appValueGetter(app.uid)
            }
        } else appValueGetter(appUid)
    fun applyForHiddenApps(app: ApplicationDescription, action: (ApplicationDescription) -> Unit) {
        mapReduceForHiddenApps(app = app, map = action, reduce = {})
    }

    fun anyForHiddenApps(appUid: Int, test: (Int) -> Boolean): Boolean {
        return if (appUid == dummySystemApp.uid) {
            getAllProfilesHiddenSystemApps().any { test(it.uid) }
        } else if (appUid == dummyAppsCompatibilityApp.uid) {
            getAllProfilesACApps().any { test(it.uid) }
        } else test(appUid)
    fun <T, R> mapReduceForHiddenApps(
        app: ApplicationDescription,
        map: (ApplicationDescription) -> T,
        reduce: (List<T>) -> R
    ): R {
        return if (app == dummySystemApp) {
            reduce(getHiddenSystemApps().map(map))
        } else if (app == dummyCompatibilityApp) {
            reduce(getCompatibilityApps().map(map))
        } else reduce(listOf(map(app)))
    }

    fun applyForHiddenApps(appUid: Int, action: (Int) -> Unit) {
        if (appUid == dummySystemApp.uid) {
            getAllProfilesHiddenSystemApps().forEach { action(it.uid) }
        } else if (appUid == dummyAppsCompatibilityApp.uid) {
            getAllProfilesACApps().forEach { action(it.uid) }
        } else action(appUid)
    }
    private var appsByUid = mapOf<Int, ApplicationDescription>()
    private var appsByAPId = mapOf<String, ApplicationDescription>()

    fun getApp(appUid: Int): ApplicationDescription? {
        return appsByUid[appUid] ?: run {
            runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() }
            appsByUid[appUid]
        }
    }

        private val pm get() = context.packageManager
    fun getApp(apId: String): ApplicationDescription? {
        if (apId.isBlank()) return null

    private val appDescriptions = MutableStateFlow(
        Pair(
            emptyList<ApplicationDescription>(),
            emptyList<ApplicationDescription>()
        )
    )
        return appsByAPId[apId] ?: run {
            runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() }
            appsByAPId[apId]
        }
    }

    private val allProfilesAppDescriptions = MutableStateFlow(
        Triple(
@@ -209,7 +262,7 @@ class AppListsRepository(
    private fun isStandardApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
        return when {
            app.packageName == PNAME_SETTINGS -> false
            app.packageName in appsCompatibiltyPNames -> false
            app.packageName in compatibiltyPNames -> false
            app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) -> true
            !app.hasFlag(ApplicationInfo.FLAG_SYSTEM) -> true
            launcherApps.contains(app.packageName) -> true
@@ -219,7 +272,7 @@ class AppListsRepository(

    private fun isHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
        return when {
            app.packageName in appsCompatibiltyPNames -> false
            app.packageName in compatibiltyPNames -> false
            else -> !isNotHiddenSystemApp(app, launcherApps)
        }
    }
+4 −1
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 MURENA SAS
 * Copyright (C) 2022 E FOUNDATION
 *
 * This program is free software: you can redistribute it and/or modify
@@ -21,6 +22,7 @@ import android.graphics.drawable.Drawable
import foundation.e.privacymodules.permissions.data.ApplicationDescription

data class AppWithCounts(
    val appDesc: ApplicationDescription,
    val packageName: String,
    val uid: Int,
    var label: CharSequence?,
@@ -40,6 +42,7 @@ data class AppWithCounts(
        leaks: Int,
    ) :
        this(
            appDesc = app,
            packageName = app.packageName,
            uid = app.uid,
            label = app.label,
@@ -52,5 +55,5 @@ data class AppWithCounts(
        )

    val blockedTrackersCount get() = if (isWhitelisted) 0
    else trackersCount - whiteListedTrackersCount
    else Math.max(trackersCount - whiteListedTrackersCount, 0)
}
+10 −2
Original line number Diff line number Diff line
@@ -24,8 +24,16 @@ import kotlinx.coroutines.flow.Flow
class AppListUseCase(
    private val appListsRepository: AppListsRepository
) {

    val dummySystemApp = appListsRepository.dummySystemApp
    fun getApp(uid: Int): ApplicationDescription {
        return when (uid) {
            dummySystemApp.uid -> dummySystemApp
            appListsRepository.dummyCompatibilityApp.uid ->
                appListsRepository.dummyCompatibilityApp
            else -> appListsRepository.getApp(uid) ?: dummySystemApp
        }
    }
    fun getAppsUsingInternet(): Flow<List<ApplicationDescription>> {
        return appListsRepository.getVisibleApps()
        return appListsRepository.mainProfileApps()
    }
}
Loading