aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/foundation/e/privacycentralapp/data
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp/data')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt197
1 files changed, 125 insertions, 72 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
index a97888f..a4f7487 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 E FOUNDATION, 2022 MURENA SAS
+ * Copyright (C) 2022 E FOUNDATION, 2022 - 2023 MURENA SAS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,6 +25,7 @@ import android.content.pm.PackageInfo
import foundation.e.privacycentralapp.R
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.ApplicationDescription
+import foundation.e.privacymodules.permissions.data.ProfileType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -32,6 +33,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
class AppListsRepository(
private val permissionsModule: PermissionsPrivacyModule,
@@ -44,7 +46,7 @@ class AppListsRepository(
private const val PNAME_INTENT_VERIFICATION = "com.android.statementservice"
private const val PNAME_MICROG_SERVICES_CORE = "com.google.android.gms"
- val appsCompatibiltyPNames = setOf(
+ val compatibiltyPNames = setOf(
PNAME_PWAPLAYER, PNAME_INTENT_VERIFICATION, PNAME_MICROG_SERVICES_CORE
)
}
@@ -53,18 +55,22 @@ class AppListsRepository(
packageName = "foundation.e.dummysystemapp",
uid = -1,
label = context.getString(R.string.dummy_system_app_label),
- icon = context.getDrawable(R.drawable.ic_e_app_logo)
+ icon = context.getDrawable(R.drawable.ic_e_app_logo),
+ profileId = -1,
+ profileType = ProfileType.MAIN
)
- val dummyAppsCompatibilityApp = ApplicationDescription(
+ val dummyCompatibilityApp = ApplicationDescription(
packageName = "foundation.e.dummyappscompatibilityapp",
uid = -2,
label = context.getString(R.string.dummy_apps_compatibility_app_label),
- icon = context.getDrawable(R.drawable.ic_apps_compatibility_components)
+ icon = context.getDrawable(R.drawable.ic_apps_compatibility_components),
+ profileId = -1,
+ profileType = ProfileType.MAIN
)
- private suspend fun fetchAppDescriptions() {
- val launcherPackageNames = pm.queryIntentActivities(
+ private suspend fun fetchAppDescriptions(fetchMissingIcons: Boolean = false) {
+ val launcherPackageNames = context.packageManager.queryIntentActivities(
Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) },
0
).mapNotNull { it.activityInfo?.packageName }
@@ -79,104 +85,151 @@ class AppListsRepository(
isHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames)
}
- val aCFilter = { packageInfo: PackageInfo ->
- packageInfo.packageName in appsCompatibiltyPNames
+ val compatibilityAppsFilter = { packageInfo: PackageInfo ->
+ packageInfo.packageName in compatibiltyPNames
}
- val visibleApps = permissionsModule.getApplications(visibleAppsFilter, true)
- val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter, false)
- val aCApps = permissionsModule.getApplications(aCFilter, false)
+ val visibleApps = recycleIcons(
+ newApps = permissionsModule.getApplications(visibleAppsFilter),
+ fetchMissingIcons = fetchMissingIcons
+ )
+ val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter)
+ val compatibilityApps = permissionsModule.getApplications(compatibilityAppsFilter)
+
+ updateMaps(visibleApps + hiddenApps + compatibilityApps)
+
+ allProfilesAppDescriptions.emit(
+ Triple(
+ visibleApps + dummySystemApp + dummyCompatibilityApp,
+ hiddenApps,
+ compatibilityApps
+ )
+ )
+ }
+
+ private fun recycleIcons(
+ newApps: List<ApplicationDescription>,
+ fetchMissingIcons: Boolean
+ ): List<ApplicationDescription> {
+ val oldVisibleApps = allProfilesAppDescriptions.value.first
+ return newApps.map { app ->
+ app.copy(
+ icon = oldVisibleApps.find { app.apId == it.apId }?.icon
+ ?: if (fetchMissingIcons) permissionsModule.getApplicationIcon(app) else null
+ )
+ }
+ }
- val workProfileVisibleApps = permissionsModule.getWorkProfileApplications(visibleAppsFilter, true)
- val workProfileHiddenApps = permissionsModule.getWorkProfileApplications(hiddenAppsFilter, false)
- val workProfileACApps = permissionsModule.getApplications(aCFilter, false)
+ private fun updateMaps(apps: List<ApplicationDescription>) {
+ val byUid = mutableMapOf<Int, ApplicationDescription>()
+ val byApId = mutableMapOf<String, ApplicationDescription>()
+ apps.forEach { app ->
+ byUid[app.uid]?.run { packageName > app.packageName } == true
+ if (byUid[app.uid].let { it == null || it.packageName > app.packageName }) {
+ byUid[app.uid] = app
+ }
- appDescriptions.emit((visibleApps + dummySystemApp + dummyAppsCompatibilityApp) to hiddenApps)
- allProfilesAppDescriptions.emit(Triple(
- (visibleApps + workProfileVisibleApps + dummySystemApp + dummyAppsCompatibilityApp),
- (hiddenApps + workProfileHiddenApps),
- (aCApps + workProfileACApps)
- ))
+ byApId[app.apId] = app
+ }
+ appsByUid = byUid
+ appsByAPId = byApId
}
+ private var lastFetchApps = 0
private var refreshAppJob: Job? = null
- private fun refreshAppDescriptions() {
- if (refreshAppJob != null) {
- return
- } else {
+ private fun refreshAppDescriptions(fetchMissingIcons: Boolean = true, force: Boolean = false): Job? {
+ if (refreshAppJob == null) {
refreshAppJob = coroutineScope.launch(Dispatchers.IO) {
- fetchAppDescriptions()
- refreshAppJob = null
+ if (force || context.packageManager.getChangedPackages(lastFetchApps) != null) {
+ fetchAppDescriptions(fetchMissingIcons = fetchMissingIcons)
+ if (fetchMissingIcons) {
+ lastFetchApps = context.packageManager.getChangedPackages(lastFetchApps)
+ ?.sequenceNumber ?: lastFetchApps
+ }
+
+ refreshAppJob = null
+ }
}
}
+
+ return refreshAppJob
}
- fun getVisibleApps(): Flow<List<ApplicationDescription>> {
+ fun mainProfileApps(): Flow<List<ApplicationDescription>> {
refreshAppDescriptions()
- return appDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } }
+ return allProfilesAppDescriptions.map {
+ it.first.filter { app -> app.profileType == ProfileType.MAIN }
+ .sortedBy { app -> app.label.toString().lowercase() }
+ }
}
- fun getHiddenSystemApps(): List<ApplicationDescription> {
- return appDescriptions.value.second
+ fun getMainProfileHiddenSystemApps(): List<ApplicationDescription> {
+ return allProfilesAppDescriptions.value.second.filter { it.profileType == ProfileType.MAIN }
}
- fun getAllProfilesVisibleApps(): Flow<List<ApplicationDescription>> {
+ fun apps(): Flow<List<ApplicationDescription>> {
refreshAppDescriptions()
- return allProfilesAppDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } }
+ return allProfilesAppDescriptions.map {
+ it.first.sortedBy { app -> app.label.toString().lowercase() }
+ }
+ }
+
+ fun allApps(): Flow<List<ApplicationDescription>> {
+ return allProfilesAppDescriptions.map {
+ it.first + it.second + it.third
+ }
}
- fun getAllProfilesHiddenSystemApps(): List<ApplicationDescription> {
+ private fun getHiddenSystemApps(): List<ApplicationDescription> {
return allProfilesAppDescriptions.value.second
}
- fun getAllProfilesACApps(): List<ApplicationDescription> {
+ private fun getCompatibilityApps(): List<ApplicationDescription> {
return allProfilesAppDescriptions.value.third
}
- fun getAllApps(): Flow<List<ApplicationDescription>> = getAllProfilesVisibleApps()
- .map { it + getAllProfilesHiddenSystemApps() + getAllProfilesACApps()}
-
- fun getApplicationDescription(appUid: Int): ApplicationDescription? {
- return allProfilesAppDescriptions.value.first.find { it.uid == appUid }
+ fun anyForHiddenApps(app: ApplicationDescription, test: (ApplicationDescription) -> Boolean): Boolean {
+ return if (app == dummySystemApp) {
+ getHiddenSystemApps().any { test(it) }
+ } else if (app == dummyCompatibilityApp) {
+ getCompatibilityApps().any { test(it) }
+ } else test(app)
}
- fun foldForHiddenApp(appUid: Int, appValueGetter: (Int) -> Int): Int {
- return if (appUid == dummySystemApp.uid) {
- getAllProfilesHiddenSystemApps().fold(0) { acc, app ->
- acc + appValueGetter(app.uid)
- }
- } else if (appUid == dummyAppsCompatibilityApp.uid) {
- getAllProfilesACApps().fold(0) { acc, app ->
- acc + appValueGetter(app.uid)
- }
- } else appValueGetter(appUid)
+ fun applyForHiddenApps(app: ApplicationDescription, action: (ApplicationDescription) -> Unit) {
+ mapReduceForHiddenApps(app = app, map = action, reduce = {})
}
- fun anyForHiddenApps(appUid: Int, test: (Int) -> Boolean): Boolean {
- return if (appUid == dummySystemApp.uid) {
- getAllProfilesHiddenSystemApps().any { test(it.uid) }
- } else if (appUid == dummyAppsCompatibilityApp.uid) {
- getAllProfilesACApps().any { test(it.uid) }
- } else test(appUid)
+ fun <T, R> mapReduceForHiddenApps(
+ app: ApplicationDescription,
+ map: (ApplicationDescription) -> T,
+ reduce: (List<T>) -> R
+ ): R {
+ return if (app == dummySystemApp) {
+ reduce(getHiddenSystemApps().map(map))
+ } else if (app == dummyCompatibilityApp) {
+ reduce(getCompatibilityApps().map(map))
+ } else reduce(listOf(map(app)))
}
- fun applyForHiddenApps(appUid: Int, action: (Int) -> Unit) {
- if (appUid == dummySystemApp.uid) {
- getAllProfilesHiddenSystemApps().forEach { action(it.uid) }
- } else if (appUid == dummyAppsCompatibilityApp.uid) {
- getAllProfilesACApps().forEach { action(it.uid) }
- } else action(appUid)
- }
+ private var appsByUid = mapOf<Int, ApplicationDescription>()
+ private var appsByAPId = mapOf<String, ApplicationDescription>()
+ fun getApp(appUid: Int): ApplicationDescription? {
+ return appsByUid[appUid] ?: run {
+ runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() }
+ appsByUid[appUid]
+ }
+ }
- private val pm get() = context.packageManager
+ fun getApp(apId: String): ApplicationDescription? {
+ if (apId.isBlank()) return null
- private val appDescriptions = MutableStateFlow(
- Pair(
- emptyList<ApplicationDescription>(),
- emptyList<ApplicationDescription>()
- )
- )
+ return appsByAPId[apId] ?: run {
+ runBlocking { refreshAppDescriptions(fetchMissingIcons = false, force = true)?.join() }
+ appsByAPId[apId]
+ }
+ }
private val allProfilesAppDescriptions = MutableStateFlow(
Triple(
@@ -209,7 +262,7 @@ class AppListsRepository(
private fun isStandardApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
return when {
app.packageName == PNAME_SETTINGS -> false
- app.packageName in appsCompatibiltyPNames -> false
+ app.packageName in compatibiltyPNames -> false
app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) -> true
!app.hasFlag(ApplicationInfo.FLAG_SYSTEM) -> true
launcherApps.contains(app.packageName) -> true
@@ -219,7 +272,7 @@ class AppListsRepository(
private fun isHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean {
return when {
- app.packageName in appsCompatibiltyPNames -> false
+ app.packageName in compatibiltyPNames -> false
else -> !isNotHiddenSystemApp(app, launcherApps)
}
}