summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-04-21 06:25:54 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-04-21 06:25:54 +0000
commit6068cebe972e000872e4780dd9f75680a3abf073 (patch)
tree4785f6b44d121f95c840020441687bce5777a44f
parent2df577ca97a674a4bd3875dc5137bb44df2c03ef (diff)
6556: add AdvancedPrivacy App Id in trackers stats to avoid appUid aliasing
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt14
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt6
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt197
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt5
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt12
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt17
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt47
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt182
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt1
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt16
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt49
-rw-r--r--app/src/main/res/layout/apptrackers_fragment.xml22
-rw-r--r--app/src/main/res/values/strings.xml2
-rw-r--r--fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt5
-rw-r--r--permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java27
-rw-r--r--permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt99
-rw-r--r--permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt13
-rw-r--r--privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt34
-rw-r--r--privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt30
-rw-r--r--privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt22
-rw-r--r--trackers/build.gradle3
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt4
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt5
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt30
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt19
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt24
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt34
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt106
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt51
-rw-r--r--trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt147
31 files changed, 764 insertions, 461 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
index 6ad84a7..aab81d5 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
@@ -1,4 +1,5 @@
/*
+ * 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
diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
index 7b09c51..2fbbc34 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt
@@ -1,5 +1,5 @@
/*
- * 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) }
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)
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt
index 0b76c7b..afdd2d5 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/entities/AppWithCounts.kt
@@ -1,4 +1,5 @@
/*
+ * 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)
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt
index 4821349..dd62839 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt
@@ -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()
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt
index caba132..dcb417b 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt
@@ -17,7 +17,6 @@
package foundation.e.privacycentralapp.domain.usecases
-import android.util.Log
import foundation.e.privacycentralapp.data.repositories.AppListsRepository
import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
@@ -86,7 +85,7 @@ class IpScramblingStateUseCase(
}
private fun getHiddenPackageNames(): List<String> {
- return appListsRepository.getHiddenSystemApps().map { it.packageName }
+ return appListsRepository.getMainProfileHiddenSystemApps().map { it.packageName }
}
val bypassTorApps: Set<String> get() {
@@ -97,10 +96,10 @@ class IpScramblingStateUseCase(
mutable.add(appListsRepository.dummySystemApp.packageName)
whitelist = mutable
}
- if (AppListsRepository.appsCompatibiltyPNames.any { it in whitelist }) {
+ if (AppListsRepository.compatibiltyPNames.any { it in whitelist }) {
val mutable = whitelist.toMutableSet()
- mutable.removeAll(AppListsRepository.appsCompatibiltyPNames)
- mutable.add(appListsRepository.dummyAppsCompatibilityApp.packageName)
+ mutable.removeAll(AppListsRepository.compatibiltyPNames)
+ mutable.add(appListsRepository.dummyCompatibilityApp.packageName)
whitelist = mutable
}
return whitelist
@@ -113,16 +112,16 @@ class IpScramblingStateUseCase(
if (visibleList.contains(packageName)) {
if (packageName == appListsRepository.dummySystemApp.packageName) {
rawList.removeAll(getHiddenPackageNames())
- } else if (packageName == appListsRepository.dummyAppsCompatibilityApp.packageName) {
- rawList.removeAll(AppListsRepository.appsCompatibiltyPNames)
+ } 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.dummyAppsCompatibilityApp.packageName) {
- rawList.addAll(AppListsRepository.appsCompatibiltyPNames)
+ } else if (packageName == appListsRepository.dummyCompatibilityApp.packageName) {
+ rawList.addAll(AppListsRepository.compatibiltyPNames)
} else {
rawList.add(packageName)
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
index 820073b..afb6d1e 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt
@@ -1,5 +1,5 @@
/*
- * 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
@@ -36,7 +36,12 @@ class TrackersStateUseCase(
private val coroutineScope: CoroutineScope
) {
init {
- trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false)
+ trackersPrivacyModule.start(
+ trackers = trackersRepository.trackers,
+ getAppByAPId = appListsRepository::getApp,
+ getAppByUid = appListsRepository::getApp,
+ enableNotification = false
+ )
coroutineScope.launch {
localStateRepository.blockTrackers.collect { enabled ->
if (enabled) {
@@ -54,39 +59,47 @@ class TrackersStateUseCase(
blockTrackersPrivacyModule.isWhiteListEmpty()
}
- fun getApplicationDescription(appUid: Int): ApplicationDescription? {
- return appListsRepository.getApplicationDescription(appUid)
+ fun isWhitelisted(app: ApplicationDescription): Boolean {
+ return isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule)
}
- fun isWhitelisted(appUid: Int): Boolean {
- return isWhitelisted(appUid, appListsRepository, blockTrackersPrivacyModule)
+ fun toggleAppWhitelist(app: ApplicationDescription, isWhitelisted: Boolean) {
+ appListsRepository.applyForHiddenApps(app) {
+ blockTrackersPrivacyModule.setWhiteListed(it, isWhitelisted)
+ }
+ updateAllTrackersBlockedState()
}
- fun toggleAppWhitelist(appUid: Int, isWhitelisted: Boolean) {
- appListsRepository.applyForHiddenApps(appUid) { uid ->
- blockTrackersPrivacyModule.setWhiteListed(uid, isWhitelisted)
+ fun blockTracker(app: ApplicationDescription, tracker: Tracker, isBlocked: Boolean) {
+ appListsRepository.applyForHiddenApps(app) {
+ blockTrackersPrivacyModule.setWhiteListed(tracker, it, !isBlocked)
}
-
updateAllTrackersBlockedState()
}
- fun blockTracker(appUid: Int, tracker: Tracker, isBlocked: Boolean) {
- appListsRepository.applyForHiddenApps(appUid) { uid ->
- blockTrackersPrivacyModule.setWhiteListed(tracker, uid, !isBlocked)
- }
+ fun clearWhitelist(app: ApplicationDescription) {
+ appListsRepository.applyForHiddenApps(
+ app,
+ blockTrackersPrivacyModule::clearWhiteList
+ )
updateAllTrackersBlockedState()
}
fun updateTrackers() = coroutineScope.launch {
trackersRepository.update()
- trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false)
+ trackersPrivacyModule.start(
+ trackers = trackersRepository.trackers,
+ getAppByAPId = appListsRepository::getApp,
+ getAppByUid = appListsRepository::getApp,
+ enableNotification = false
+ )
}
}
fun isWhitelisted(
- appUid: Int,
+ app: ApplicationDescription,
appListsRepository: AppListsRepository,
blockTrackersPrivacyModule: IBlockTrackersPrivacyModule
): Boolean {
- return appListsRepository.anyForHiddenApps(appUid, blockTrackersPrivacyModule::isWhitelisted)
+ return appListsRepository.anyForHiddenApps(app, blockTrackersPrivacyModule::isWhitelisted)
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
index cc6ec45..5ca7039 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt
@@ -1,5 +1,5 @@
/*
- * 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
@@ -47,7 +47,7 @@ class TrackersStatisticsUseCase(
private val resources: Resources
) {
fun initAppList() {
- appListsRepository.getAllProfilesVisibleApps()
+ appListsRepository.apps()
}
private fun rawUpdates(): Flow<Unit> = callbackFlow {
@@ -74,10 +74,25 @@ class TrackersStatisticsUseCase(
) to trackTrackersPrivacyModule.getTrackersCount()
}
+ fun getNonBlockedTrackersCount(): Flow<Int> {
+ return if (blockTrackersPrivacyModule.isBlockingEnabled())
+ appListsRepository.allApps().map { apps ->
+ val whiteListedTrackers = mutableSetOf<Tracker>()
+ val whiteListedApps = blockTrackersPrivacyModule.getWhiteListedApp()
+ apps.forEach { app ->
+ if (app in whiteListedApps) {
+ whiteListedTrackers.addAll(trackTrackersPrivacyModule.getTrackersForApp(app))
+ } else {
+ whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app))
+ }
+ }
+ whiteListedTrackers.size
+ }
+ else flowOf(trackTrackersPrivacyModule.getTrackersCount())
+ }
+
fun getMostLeakedApp(): ApplicationDescription? {
- return appListsRepository.getApplicationDescription(
- trackTrackersPrivacyModule.getPastDayMostLeakedApp()
- )
+ return trackTrackersPrivacyModule.getPastDayMostLeakedApp()
}
fun getDayTrackersCalls() = trackTrackersPrivacyModule.getPastDayTrackersCalls()
@@ -161,103 +176,93 @@ class TrackersStatisticsUseCase(
}
}
- fun getTrackers(appUid: Int): List<Tracker> {
- val trackers = if (appUid == appListsRepository.dummySystemApp.uid) {
- appListsRepository.getAllProfilesHiddenSystemApps().map {
- trackTrackersPrivacyModule.getTrackersForApp(it.uid)
- }.flatten().distinctBy { it.id }
- } else if (appUid == appListsRepository.dummyAppsCompatibilityApp.uid) {
- appListsRepository.getAllProfilesACApps().map {
- trackTrackersPrivacyModule.getTrackersForApp(it.uid)
- }.flatten().distinctBy { it.id }
- } else trackTrackersPrivacyModule.getTrackersForApp(appUid)
-
- return trackers.sortedBy { it.label.lowercase() }
- }
-
- fun getTrackersWithWhiteList(appUid: Int): List<Pair<Tracker, Boolean>> {
- val trackers: List<Tracker>
- val whiteListedTrackersIds: Set<String>
- if (appUid == appListsRepository.dummySystemApp.uid) {
- val hiddenApps = appListsRepository.getAllProfilesHiddenSystemApps()
- trackers = trackTrackersPrivacyModule.getTrackers(hiddenApps.map { it.uid })
-
- whiteListedTrackersIds = hiddenApps.fold(HashSet<String>()) { acc, app ->
- acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id })
- acc
- }
- } else if (appUid == appListsRepository.dummyAppsCompatibilityApp.uid) {
- val acApps = appListsRepository.getAllProfilesACApps()
- trackers = trackTrackersPrivacyModule.getTrackers(acApps.map { it.uid })
+ fun getTrackersWithWhiteList(app: ApplicationDescription): List<Pair<Tracker, Boolean>> {
+ return appListsRepository.mapReduceForHiddenApps(
+ app = app,
+ map = { appDesc: ApplicationDescription ->
+ (
+ trackTrackersPrivacyModule.getTrackersForApp(appDesc) to
+ blockTrackersPrivacyModule.getWhiteList(appDesc)
+ )
+ },
+ reduce = { lists ->
+ lists.unzip().let { (trackerLists, whiteListedIdLists) ->
+ val whiteListedIds = whiteListedIdLists.flatten().map { it.id }.toSet()
- whiteListedTrackersIds = acApps.fold(HashSet<String>()) { acc, app ->
- acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id })
- acc
+ trackerLists.flatten().distinctBy { it.id }.sortedBy { it.label.lowercase() }
+ .map { tracker -> tracker to (tracker.id in whiteListedIds) }
+ }
}
- } else {
- trackers = trackTrackersPrivacyModule.getTrackersForApp(appUid)
- whiteListedTrackersIds = blockTrackersPrivacyModule.getWhiteList(appUid)
- .map { it.id }.toSet()
- }
+ )
+ }
- return trackers.sortedBy { it.label.lowercase() }.map { tracker -> tracker to whiteListedTrackersIds.any { tracker.id == it } }
+ fun isWhiteListEmpty(app: ApplicationDescription): Boolean {
+ return appListsRepository.mapReduceForHiddenApps(
+ app = app,
+ map = { appDesc: ApplicationDescription ->
+ blockTrackersPrivacyModule.getWhiteList(appDesc).isEmpty()
+ },
+ reduce = { areEmpty -> areEmpty.all { it } }
+ )
}
- fun getCalls(appUid: Int): Pair<Int, Int> {
- return if (appUid == appListsRepository.dummySystemApp.uid) {
- appListsRepository.getAllProfilesHiddenSystemApps().map {
- trackTrackersPrivacyModule.getPastDayTrackersCallsForApp(it.uid)
- }.reduce { (accBlocked, accLeaked), (blocked, leaked) ->
- accBlocked + blocked to accLeaked + leaked
- }
- } else if (appUid == appListsRepository.dummyAppsCompatibilityApp.uid) {
- appListsRepository.getAllProfilesACApps().map {
- trackTrackersPrivacyModule.getPastDayTrackersCallsForApp(it.uid)
- }.reduce { (accBlocked, accLeaked), (blocked, leaked) ->
- accBlocked + blocked to accLeaked + leaked
+ fun getCalls(app: ApplicationDescription): Pair<Int, Int> {
+ return appListsRepository.mapReduceForHiddenApps(
+ app = app,
+ map = trackTrackersPrivacyModule::getPastDayTrackersCallsForApp,
+ reduce = { zip ->
+ zip.unzip().let { (blocked, leaked) ->
+ blocked.sum() to leaked.sum()
+ }
}
- } else trackTrackersPrivacyModule.getPastDayTrackersCallsForApp(appUid)
+ )
}
fun getAppsWithCounts(): Flow<List<AppWithCounts>> {
val trackersCounts = trackTrackersPrivacyModule.getTrackersCountByApp()
val hiddenAppsTrackersWithWhiteList =
- getTrackersWithWhiteList(appListsRepository.dummySystemApp.uid)
+ getTrackersWithWhiteList(appListsRepository.dummySystemApp)
val acAppsTrackersWithWhiteList =
- getTrackersWithWhiteList(appListsRepository.dummyAppsCompatibilityApp.uid)
+ getTrackersWithWhiteList(appListsRepository.dummyCompatibilityApp)
- return appListsRepository.getAllProfilesVisibleApps()
+ return appListsRepository.apps()
.map { apps ->
val callsByApp = trackTrackersPrivacyModule.getPastDayTrackersCallsByApps()
apps.map { app ->
+ val calls = appListsRepository.mapReduceForHiddenApps(
+ app = app,
+ map = { callsByApp.getOrDefault(app, 0 to 0) },
+ reduce = {
+ it.unzip().let { (blocked, leaked) ->
+ blocked.sum() to leaked.sum()
+ }
+ }
+ )
+
AppWithCounts(
app = app,
isWhitelisted = !blockTrackersPrivacyModule.isBlockingEnabled() ||
- isWhitelisted(app.uid, appListsRepository, blockTrackersPrivacyModule),
- trackersCount = if (app.uid == appListsRepository.dummySystemApp.uid) {
- hiddenAppsTrackersWithWhiteList.size
- } else if (app.uid == appListsRepository.dummyAppsCompatibilityApp.uid) {
- acAppsTrackersWithWhiteList.size
- } else {
- trackersCounts.getOrDefault(app.uid, 0)
- },
- whiteListedTrackersCount = if (app.uid == appListsRepository.dummySystemApp.uid) {
- hiddenAppsTrackersWithWhiteList.count { it.second }
- } else if (app.uid == appListsRepository.dummyAppsCompatibilityApp.uid) {
- acAppsTrackersWithWhiteList.count { it.second }
- } else {
- blockTrackersPrivacyModule.getWhiteList(app.uid).size
+ isWhitelisted(app, appListsRepository, blockTrackersPrivacyModule),
+ trackersCount = when (app) {
+ appListsRepository.dummySystemApp ->
+ hiddenAppsTrackersWithWhiteList.size
+ appListsRepository.dummyCompatibilityApp ->
+ acAppsTrackersWithWhiteList.size
+ else -> trackersCounts.getOrDefault(app, 0)
},
- blockedLeaks = appListsRepository.foldForHiddenApp(app.uid) {
- appUid ->
- callsByApp.getOrDefault(appUid, 0 to 0).first
+ whiteListedTrackersCount = when (app) {
+ appListsRepository.dummySystemApp ->
+ hiddenAppsTrackersWithWhiteList.count { it.second }
+ appListsRepository.dummyCompatibilityApp ->
+ acAppsTrackersWithWhiteList.count { it.second }
+ else ->
+ blockTrackersPrivacyModule.getWhiteList(app).size
},
- leaks = appListsRepository.foldForHiddenApp(app.uid) {
- appUid ->
- callsByApp.getOrDefault(appUid, 0 to 0).second
- }
+ blockedLeaks = calls.first,
+ leaks = calls.second
)
- }.sortedWith(mostLeakedAppsComparator)
+ }
+ .sortedWith(mostLeakedAppsComparator)
}
}
@@ -270,21 +275,4 @@ class TrackersStatisticsUseCase(
}
}
}
-
- fun getNonBlockedTrackersCount(): Flow<Int> {
- return if (blockTrackersPrivacyModule.isBlockingEnabled())
- appListsRepository.getAllApps().map { apps ->
- val whiteListedTrackers = mutableSetOf<Tracker>()
- val whiteListedAppUids = blockTrackersPrivacyModule.getWhiteListedApp()
- apps.forEach { app ->
- if (app.uid in whiteListedAppUids) {
- whiteListedTrackers.addAll(getTrackers(app.uid))
- } else {
- whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid))
- }
- }
- whiteListedTrackers.size
- }
- else flowOf(trackTrackersPrivacyModule.getTrackersCount())
- }
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
index ead01a5..f3a9774 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt
@@ -1,4 +1,5 @@
/*
+* Copyright (C) 2023 MURENA SAS
* Copyright (C) 2021 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt
index f15119e..888c140 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2021 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -85,6 +86,9 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) {
binding.blockAllToggle.setOnClickListener {
viewModel.submitAction(AppTrackersViewModel.Action.BlockAllToggleAction(binding.blockAllToggle.isChecked))
}
+ binding.btnReset.setOnClickListener {
+ viewModel.submitAction(AppTrackersViewModel.Action.ResetAllTrackers)
+ }
binding.trackers.apply {
layoutManager = LinearLayoutManager(requireContext())
@@ -94,7 +98,7 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) {
onToggleSwitch = { tracker, isBlocked ->
viewModel.submitAction(AppTrackersViewModel.Action.ToggleTrackerAction(tracker, isBlocked))
},
- onClickTitle = { viewModel.submitAction(AppTrackersViewModel.Action.ClickTracker(it)) }
+ onClickTitle = { viewModel.submitAction(AppTrackersViewModel.Action.ClickTracker(it)) },
)
}
@@ -162,15 +166,19 @@ class AppTrackersFragment : NavToolbarFragment(R.layout.apptrackers_fragment) {
)
}
binding.noTrackersYet.isVisible = false
+ binding.btnReset.isVisible = true
} else {
binding.trackersListTitle.isVisible = false
binding.trackers.isVisible = false
binding.noTrackersYet.isVisible = true
binding.noTrackersYet.text = getString(
- if (state.isBlockingActivated)
- R.string.apptrackers_no_trackers_yet_block_on
- else R.string.apptrackers_no_trackers_yet_block_off
+ when {
+ !state.isBlockingActivated -> R.string.apptrackers_no_trackers_yet_block_off
+ state.isWhitelistEmpty -> R.string.apptrackers_no_trackers_yet_block_on
+ else -> R.string.app_trackers_no_trackers_yet_remaining_whitelist
+ }
)
+ binding.btnReset.isVisible = state.isBlockingActivated && !state.isWhitelistEmpty
}
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt
index 8088443..a190a74 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersState.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -27,6 +28,7 @@ data class AppTrackersState(
val leaked: Int = 0,
val blocked: Int = 0,
val isTrackersBlockingEnabled: Boolean = false,
+ val isWhitelistEmpty: Boolean = true,
val showQuickPrivacyDisabledMessage: Boolean = false,
) {
fun getTrackersStatus(): List<Pair<Tracker, Boolean>>? {
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt
index 1a33844..e5a94f9 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2021 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -25,6 +26,7 @@ import foundation.e.privacycentralapp.domain.entities.TrackerMode
import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase
import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.trackers.api.Tracker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -38,7 +40,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AppTrackersViewModel(
- private val appUid: Int,
+ private val app: ApplicationDescription,
private val trackersStateUseCase: TrackersStateUseCase,
private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase
@@ -57,9 +59,12 @@ class AppTrackersViewModel(
viewModelScope.launch(Dispatchers.IO) {
_state.update {
it.copy(
- appDesc = trackersStateUseCase.getApplicationDescription(appUid),
- isBlockingActivated = !trackersStateUseCase.isWhitelisted(appUid),
- trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(appUid),
+ appDesc = app,
+ isBlockingActivated = !trackersStateUseCase.isWhitelisted(app),
+ trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(
+ app
+ ),
+ isWhitelistEmpty = trackersStatisticsUseCase.isWhiteListEmpty(app)
)
}
}
@@ -79,6 +84,7 @@ class AppTrackersViewModel(
is Action.BlockAllToggleAction -> blockAllToggleAction(action)
is Action.ToggleTrackerAction -> toggleTrackerAction(action)
is Action.ClickTracker -> actionClickTracker(action)
+ is Action.ResetAllTrackers -> resetAllTrackers()
}
}
@@ -87,10 +93,10 @@ class AppTrackersViewModel(
if (!state.value.isTrackersBlockingEnabled) {
_singleEvents.emit(SingleEvent.ToastTrackersControlDisabled)
}
- trackersStateUseCase.toggleAppWhitelist(appUid, !action.isBlocked)
+ trackersStateUseCase.toggleAppWhitelist(app, !action.isBlocked)
_state.update {
it.copy(
- isBlockingActivated = !trackersStateUseCase.isWhitelisted(appUid)
+ isBlockingActivated = !trackersStateUseCase.isWhitelisted(app)
)
}
}
@@ -103,14 +109,8 @@ class AppTrackersViewModel(
}
if (state.value.isBlockingActivated) {
- trackersStateUseCase.blockTracker(appUid, action.tracker, action.isBlocked)
- _state.update {
- it.copy(
- trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(
- appUid
- )
- )
- }
+ trackersStateUseCase.blockTracker(app, action.tracker, action.isBlocked)
+ updateWhitelist()
}
}
}
@@ -130,13 +130,29 @@ class AppTrackersViewModel(
}
}
+ private suspend fun resetAllTrackers() {
+ withContext(Dispatchers.IO) {
+ trackersStateUseCase.clearWhitelist(app)
+ updateWhitelist()
+ }
+ }
private fun fetchStatistics() {
- val (blocked, leaked) = trackersStatisticsUseCase.getCalls(appUid)
+ val (blocked, leaked) = trackersStatisticsUseCase.getCalls(app)
return _state.update { s ->
s.copy(
- trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(appUid),
+ trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(app),
leaked = leaked,
blocked = blocked,
+ isWhitelistEmpty = trackersStatisticsUseCase.isWhiteListEmpty(app)
+ )
+ }
+ }
+
+ private fun updateWhitelist() {
+ _state.update { s ->
+ s.copy(
+ trackersWithWhiteList = trackersStatisticsUseCase.getTrackersWithWhiteList(app),
+ isWhitelistEmpty = trackersStatisticsUseCase.isWhiteListEmpty(app)
)
}
}
@@ -151,5 +167,6 @@ class AppTrackersViewModel(
data class BlockAllToggleAction(val isBlocked: Boolean) : Action()
data class ToggleTrackerAction(val tracker: Tracker, val isBlocked: Boolean) : Action()
data class ClickTracker(val tracker: Tracker) : Action()
+ object ResetAllTrackers : Action()
}
}
diff --git a/app/src/main/res/layout/apptrackers_fragment.xml b/app/src/main/res/layout/apptrackers_fragment.xml
index e6e226f..d0a72d5 100644
--- a/app/src/main/res/layout/apptrackers_fragment.xml
+++ b/app/src/main/res/layout/apptrackers_fragment.xml
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 MURENA SAS
~ Copyright (C) 2021 E FOUNDATION
~
~ This program is free software: you can redistribute it and/or modify
@@ -82,20 +83,33 @@
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/trackers"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:layout_marginBottom="32dp"
+ android:layout_marginBottom="16dp"
tools:listitem="@layout/apptrackers_item_tracker_toggle"
android:visibility="gone"
/>
<TextView
android:id="@+id/no_trackers_yet"
- android:padding="32dp"
+ android:paddingHorizontal="32dp"
+ android:paddingTop="64dp"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:gravity="center"
tools:text="@string/apptrackers_no_trackers_yet_block_off"
/>
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_reset"
+ style="@style/Widget.MaterialComponents.Button.OutlinedButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/apptrackers_reset"
+ android:textSize="14sp"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ tools:visibility="visible"
+ android:layout_margin="16dp"
+ />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 38cd38b..49d9182 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -92,10 +92,12 @@
<string name="apptrackers_trackers_list_title">Opt for the trackers you want to activate/deactivate.</string>
<string name="apptrackers_no_trackers_yet_block_off">No trackers were detected yet. If new trackers are detected they will be updated here.</string>
<string name="apptrackers_no_trackers_yet_block_on">No trackers were detected yet. All future trackers will be blocked.</string>
+ <string name="app_trackers_no_trackers_yet_remaining_whitelist">No trackers were detected yet. Some trackers were unblocked previously.</string>
<string name="apptrackers_error_quickprivacy_disabled">Enable Quick Privacy to be able to activate/deactivate trackers.</string>
<string name="apptrackers_trackers_count_summary">%1$d blocked trackers out of %2$d detected trackers, %3$d blocked leaks and %4$d allowed leaks.</string>
<string name="apptrackers_error_no_app">App not installed.</string>
<string name="apptrackers_tracker_control_disabled_message">Changes will take effect when tracker blocker is on.</string>
+ <string name="apptrackers_reset">Reset trackers</string>
<!-- Features warning dialog -->
<string name="warningdialog_do_not_show_again">Do not show again</string>
diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt
index 42cf658..cf6fb2c 100644
--- a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt
+++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt
@@ -38,6 +38,7 @@ import foundation.e.privacymodules.fakelocationdemo.databinding.ActivityMainBind
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.AppOpModes
import foundation.e.privacymodules.permissions.data.ApplicationDescription
+import foundation.e.privacymodules.permissions.data.ProfileType
class MainActivity : AppCompatActivity() {
companion object {
@@ -54,7 +55,9 @@ class MainActivity : AppCompatActivity() {
packageName = packageName,
uid = myUid(),
label = getString(R.string.app_name),
- icon = null
+ icon = null,
+ profileId = 0,
+ profileType = ProfileType.MAIN
)
}
diff --git a/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java b/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java
index 3f232bf..c6232ce 100644
--- a/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java
+++ b/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java
@@ -26,11 +26,26 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;
import java.util.List;
+import android.util.AndroidException;
// Stub based on:
// https://gitlab.e.foundation/e/os/android_frameworks_base/-/blob/[SDK_VERSION]/core/java/android/content/pm/PackageManager.java
public abstract class PackageManager {
+ @TargetApi(29)
+ @DeprecatedSinceApi(
+ api = 34,
+ message = "Check availability in SDK34"
+ )
+ public static class NameNotFoundException extends AndroidException {
+ public NameNotFoundException() {
+ }
+
+ public NameNotFoundException(String name) {
+ super(name);
+ }
+ }
+
@TargetApi(29)
@DeprecatedSinceApi(
@@ -58,6 +73,18 @@ public abstract class PackageManager {
@TargetApi(29)
@DeprecatedSinceApi(
+ api = 33,
+ message = "@deprecated Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, int)} instead."
+ )
+ public abstract ApplicationInfo getApplicationInfoAsUser(
+ @NonNull String packageName,
+ int flags,
+ int userId
+ ) throws NameNotFoundException;
+
+
+ @TargetApi(29)
+ @DeprecatedSinceApi(
api = 34,
message = "Check disponibility in SDK34"
)
diff --git a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
index cde4afb..6d0a17c 100644
--- a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
+++ b/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
@@ -26,6 +26,7 @@ import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.UserInfo
+import android.graphics.drawable.Drawable
import android.net.IConnectivityManager
import android.net.VpnManager
import android.net.VpnManager.TYPE_VPN_SERVICE
@@ -36,6 +37,8 @@ import android.os.UserManager
import android.util.Log
import foundation.e.privacymodules.permissions.data.AppOpModes
import foundation.e.privacymodules.permissions.data.ApplicationDescription
+import foundation.e.privacymodules.permissions.data.ProfileType.MAIN
+import foundation.e.privacymodules.permissions.data.ProfileType.WORK
/**
* Implements [IPermissionsPrivacyModule] with all privileges of a system app.
@@ -88,6 +91,50 @@ class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(con
return true
}
+ override fun getApplications(
+ filter: ((PackageInfo) -> Boolean)?
+ ): List<ApplicationDescription> {
+ val pm = context.packageManager
+ val mainUserId = UserHandle.myUserId()
+ val workProfileId = getWorkProfile()?.id
+
+ val userIds = listOf(mainUserId, workProfileId).filterNotNull()
+ return userIds.map { profileId ->
+ pm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, profileId)
+ .filter { filter?.invoke(it) ?: true }
+ .map {
+ buildApplicationDescription(
+ appInfo = it.applicationInfo,
+ profileId = profileId,
+ profileType = if (profileId == mainUserId) MAIN else WORK
+ )
+ }
+ }.flatten()
+ }
+
+ override fun getApplicationIcon(app: ApplicationDescription): Drawable? {
+ return if (app.profileType == WORK) {
+ getWorkProfile()?.let { workProfile ->
+ val pm = context.packageManager
+ getApplicationIcon(
+ pm.getApplicationInfoAsUser(app.packageName, 0, workProfile.id)
+ )?.let {
+ pm.getUserBadgedIcon(it, workProfile.getUserHandle())
+ }
+ }
+ } else getApplicationIcon(app.packageName)
+ }
+
+ override fun setBlockable(notificationChannel: NotificationChannel) {
+ when (Build.VERSION.SDK_INT) {
+ 29 -> notificationChannel.setBlockableSystem(true)
+ 30, 31, 32, 33 -> notificationChannel.setBlockable(true)
+ else -> {
+ Log.e("Permissions-e", "Bad android sdk version")
+ }
+ }
+ }
+
override fun setVpnPackageAuthorization(packageName: String): Boolean {
return when (Build.VERSION.SDK_INT) {
29 -> setVpnPackageAuthorizationSDK29(packageName)
@@ -161,45 +208,6 @@ class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(con
return false
}
- private fun getWorkProfile(): UserInfo? {
- val userManager: UserManager = context.getSystemService(UserManager::class.java)
- val userId = UserHandle.myUserId()
- for (user in userManager.getProfiles(UserHandle.myUserId())) {
- if (user.id != userId && userManager.isManagedProfile(user.id)) {
- return user
- }
- }
- return null
- }
-
- override fun getApplications(
- filter: ((PackageInfo) -> Boolean)?,
- withIcon: Boolean
- ): List<ApplicationDescription> {
- return context.packageManager
- .getInstalledPackages(PackageManager.GET_PERMISSIONS)
- .filter { filter?.invoke(it) ?: true }
- .map { buildApplicationDescription(it.applicationInfo, withIcon = withIcon) }
- }
-
- override fun getWorkProfileApplications(
- filter: ((PackageInfo) -> Boolean)?,
- withIcon: Boolean
- ): List<ApplicationDescription> {
- val pm = context.packageManager
- return getWorkProfile()?.let { workProfile ->
- pm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, workProfile.id)
- .filter { filter?.invoke(it) ?: true }
- .map {
- val appDesc = buildApplicationDescription(it.applicationInfo, withIcon = withIcon)
- appDesc.icon = appDesc.icon?.let {
- pm.getUserBadgedIcon(it, workProfile.getUserHandle())
- }
- appDesc
- }
- } ?: emptyList()
- }
-
override fun getAlwaysOnVpnPackage(): String? {
return when (Build.VERSION.SDK_INT) {
29, 30 -> getAlwaysOnVpnPackageSDK29()
@@ -236,13 +244,14 @@ class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(con
}
}
- override fun setBlockable(notificationChannel: NotificationChannel) {
- when (Build.VERSION.SDK_INT) {
- 29 -> notificationChannel.setBlockableSystem(true)
- 30, 31, 32, 33 -> notificationChannel.setBlockable(true)
- else -> {
- Log.e("Permissions-e", "Bad android sdk version")
+ private fun getWorkProfile(): UserInfo? {
+ val userManager: UserManager = context.getSystemService(UserManager::class.java)
+ val userId = UserHandle.myUserId()
+ for (user in userManager.getProfiles(UserHandle.myUserId())) {
+ if (user.id != userId && userManager.isManagedProfile(user.id)) {
+ return user
}
}
+ return null
}
}
diff --git a/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
index da7c73e..283b417 100644
--- a/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
+++ b/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt
@@ -21,6 +21,7 @@ import android.app.NotificationChannel
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
import foundation.e.privacymodules.permissions.data.AppOpModes
import foundation.e.privacymodules.permissions.data.ApplicationDescription
@@ -29,19 +30,15 @@ import foundation.e.privacymodules.permissions.data.ApplicationDescription
*/
class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) {
override fun getApplications(
- filter: ((PackageInfo) -> Boolean)?,
- withIcon: Boolean
+ filter: ((PackageInfo) -> Boolean)?
): List<ApplicationDescription> {
return context.packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
.filter { filter?.invoke(it) == true }
- .map { buildApplicationDescription(it.applicationInfo, withIcon = withIcon) }
+ .map { buildApplicationDescription(it.applicationInfo) }
}
- override fun getWorkProfileApplications(
- filter: ((PackageInfo) -> Boolean)?,
- withIcon: Boolean
- ): List<ApplicationDescription> {
- return emptyList()
+ override fun getApplicationIcon(app: ApplicationDescription): Drawable? {
+ return getApplicationIcon(app.packageName)
}
/**
diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt
index d0e2e75..64b2292 100644
--- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt
+++ b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS
+ * Copyright (C) 2022 - 2023 MURENA SAS
+ * 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
@@ -29,6 +30,7 @@ import android.util.Log
import foundation.e.privacymodules.permissions.data.AppOpModes
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.permissions.data.PermissionDescription
+import foundation.e.privacymodules.permissions.data.ProfileType
/**
* Implementation of the commons functionality between privileged and standard
@@ -40,28 +42,12 @@ abstract class APermissionsPrivacyModule(protected val context: Context) : IPerm
companion object {
private const val TAG = "PermissionsModule"
}
- /**
- * @see IPermissionsPrivacyModule.getAllApplications
- */
- override fun getAllApplications(): List<ApplicationDescription> {
- val appInfos = context.packageManager.getInstalledApplications(0)
- return appInfos.map { buildApplicationDescription(it, false) }
- }
-
- /**
- * @see IPermissionsPrivacyModule.getInstalledApplications
- */
- override fun getInstalledApplications(): List<ApplicationDescription> {
- return context.packageManager.getInstalledApplications(0)
- .filter { it.flags and ApplicationInfo.FLAG_SYSTEM == 0 }
- .map { buildApplicationDescription(it, false) }
- }
/**
* @see IPermissionsPrivacyModule.getInstalledApplications
*/
override fun getApplicationDescription(packageName: String, withIcon: Boolean): ApplicationDescription {
- val appDesc = buildApplicationDescription(context.packageManager.getApplicationInfo(packageName, 0), false)
+ val appDesc = buildApplicationDescription(context.packageManager.getApplicationInfo(packageName, 0))
if (withIcon) {
appDesc.icon = getApplicationIcon(appDesc.packageName)
}
@@ -144,13 +130,19 @@ abstract class APermissionsPrivacyModule(protected val context: Context) : IPerm
}
}
- override fun buildApplicationDescription(appInfo: ApplicationInfo, withIcon: Boolean):
+ override fun buildApplicationDescription(
+ appInfo: ApplicationInfo,
+ profileId: Int,
+ profileType: ProfileType
+ ):
ApplicationDescription {
return ApplicationDescription(
packageName = appInfo.packageName,
uid = appInfo.uid,
label = getAppLabel(appInfo),
- icon = if (withIcon) getApplicationIcon(appInfo) else null
+ icon = null,
+ profileId = profileId,
+ profileType = profileType,
)
}
@@ -158,7 +150,7 @@ abstract class APermissionsPrivacyModule(protected val context: Context) : IPerm
return context.packageManager.getApplicationLabel(appInfo)
}
- private fun getApplicationIcon(appInfo: ApplicationInfo): Drawable? {
+ fun getApplicationIcon(appInfo: ApplicationInfo): Drawable? {
return context.packageManager.getApplicationIcon(appInfo)
}
diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt
index b64762f..39c726a 100644
--- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt
+++ b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS
+ * Copyright (C) 2022 - 2023 MURENA SAS
+ * 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
@@ -24,6 +25,7 @@ import android.graphics.drawable.Drawable
import foundation.e.privacymodules.permissions.data.AppOpModes
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.permissions.data.PermissionDescription
+import foundation.e.privacymodules.permissions.data.ProfileType
/**
* List applications and manage theirs permissions.
@@ -32,32 +34,15 @@ interface IPermissionsPrivacyModule {
fun buildApplicationDescription(
appInfo: ApplicationInfo,
- withIcon: Boolean = true
+ profileId: Int = -1,
+ profileType: ProfileType = ProfileType.MAIN
): ApplicationDescription
fun getApplications(
filter: ((PackageInfo) -> Boolean)?,
- withIcon: Boolean
- ): List<ApplicationDescription>
-
- fun getWorkProfileApplications(
- filter: ((PackageInfo) -> Boolean)?,
- withIcon: Boolean
): List<ApplicationDescription>
/**
- * List the installed application on the device which have not the FLAGS_SYSTEM.
- * @return list of filled up [ApplicationDescription]
- */
- fun getInstalledApplications(): List<ApplicationDescription>
-
- /**
- * List all the installed application on the device.
- * @return list of filled up [ApplicationDescription]
- */
- fun getAllApplications(): List<ApplicationDescription>
-
- /**
* List of permissions names used by an app, specified by its [packageName].
* @param packageName the appId of the app
* @return the list off permission, in the "android.permission.PERMISSION" format.
@@ -131,6 +116,11 @@ interface IPermissionsPrivacyModule {
fun getApplicationIcon(packageName: String): Drawable?
/**
+ * Get the application icon.
+ */
+ fun getApplicationIcon(app: ApplicationDescription): Drawable?
+
+ /**
* 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/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt
index cafe256..4fa1bb9 100644
--- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt
+++ b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/data/ApplicationDescription.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -25,6 +26,25 @@ import android.graphics.drawable.Drawable
data class ApplicationDescription(
val packageName: String,
val uid: Int,
+ val profileId: Int,
+ val profileType: ProfileType,
var label: CharSequence?,
var icon: Drawable?
-)
+) {
+ val profileFlag = when (profileType) {
+ ProfileType.MAIN -> PROFILE_FLAG_MAIN
+ ProfileType.WORK -> PROFILE_FLAG_WORK
+ else -> profileId
+ }
+
+ val apId: String get() = "${profileFlag}_$packageName"
+
+ companion object {
+ const val PROFILE_FLAG_MAIN = -1
+ const val PROFILE_FLAG_WORK = -2
+ }
+}
+
+enum class ProfileType {
+ MAIN, WORK, OTHER
+}
diff --git a/trackers/build.gradle b/trackers/build.gradle
index f888acf..ecf95be 100644
--- a/trackers/build.gradle
+++ b/trackers/build.gradle
@@ -46,6 +46,7 @@ dependencies {
implementation(
Libs.Kotlin.stdlib,
Libs.AndroidX.coreKtx,
- Libs.Coroutines.core
+ Libs.Coroutines.core,
+ Libs.timber
)
}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt
index 737aa4a..44793a4 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/DNSBlockerRunnable.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -135,7 +136,6 @@ class DNSBlockerRunnable(
private fun shouldBlock(appUid: Int, trackerId: String?): Boolean {
return whitelistRepository.isBlockingEnabled &&
- !whitelistRepository.isAppWhiteListed(appUid) &&
- !whitelistRepository.isTrackerWhiteListedForApp(trackerId, appUid)
+ !whitelistRepository.isWhiteListed(appUid, trackerId)
}
}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt
index 99e2148..f3c4745 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -39,8 +40,8 @@ class TrackersLogger(context: Context) {
stopped = true
}
- fun logAccess(trackerId: String?, appId: Int, wasBlocked: Boolean) {
- queue.offer(DetectedTracker(trackerId, appId, wasBlocked))
+ fun logAccess(trackerId: String?, appUid: Int, wasBlocked: Boolean) {
+ queue.offer(DetectedTracker(trackerId, appUid, wasBlocked))
}
private fun startWriteLogLoop() {
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt
index 25f0f2a..7463b22 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/BlockTrackersPrivacyModule.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -18,6 +19,7 @@
package foundation.e.privacymodules.trackers.api
import android.content.Context
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.trackers.data.TrackersRepository
import foundation.e.privacymodules.trackers.data.WhitelistRepository
@@ -52,14 +54,14 @@ class BlockTrackersPrivacyModule(context: Context) : IBlockTrackersPrivacyModule
mListeners.forEach { listener -> listener.onBlockingToggle(true) }
}
- override fun getWhiteList(appUid: Int): List<Tracker> {
- return whitelistRepository.getWhiteListForApp(appUid).mapNotNull {
+ override fun getWhiteList(app: ApplicationDescription): List<Tracker> {
+ return whitelistRepository.getWhiteListForApp(app).mapNotNull {
trackersRepository.getTracker(it)
}
}
- override fun getWhiteListedApp(): List<Int> {
- return whitelistRepository.whiteListedApp
+ override fun getWhiteListedApp(): List<ApplicationDescription> {
+ return whitelistRepository.getWhiteListedApp()
}
override fun isBlockingEnabled(): Boolean {
@@ -70,19 +72,27 @@ class BlockTrackersPrivacyModule(context: Context) : IBlockTrackersPrivacyModule
return whitelistRepository.areWhiteListEmpty()
}
- override fun isWhitelisted(appUid: Int): Boolean {
- return whitelistRepository.isAppWhiteListed(appUid)
+ override fun isWhitelisted(app: ApplicationDescription): Boolean {
+ return whitelistRepository.isAppWhiteListed(app)
}
override fun removeListener(listener: IBlockTrackersPrivacyModule.Listener) {
mListeners.remove(listener)
}
- override fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean) {
- whitelistRepository.setWhiteListed(tracker, appUid, isWhiteListed)
+ override fun setWhiteListed(
+ tracker: Tracker,
+ app: ApplicationDescription,
+ isWhiteListed: Boolean
+ ) {
+ whitelistRepository.setWhiteListed(tracker, app.apId, isWhiteListed)
}
- override fun setWhiteListed(appUid: Int, isWhiteListed: Boolean) {
- whitelistRepository.setWhiteListed(appUid, isWhiteListed)
+ override fun setWhiteListed(app: ApplicationDescription, isWhiteListed: Boolean) {
+ whitelistRepository.setWhiteListed(app.apId, isWhiteListed)
+ }
+
+ override fun clearWhiteList(app: ApplicationDescription) {
+ whitelistRepository.clearWhiteList(app.apId)
}
}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt
index 9e1a041..3547b8e 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/IBlockTrackersPrivacyModule.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -17,6 +18,8 @@
package foundation.e.privacymodules.trackers.api
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
/**
* Manage trackers blocking and whitelisting.
*/
@@ -40,18 +43,18 @@ interface IBlockTrackersPrivacyModule {
/**
* Set or unset in whitelist the App with the specified uid.
- * @param appUid the uid of the app
+ * @param app the ApplicationDescription of the app
* @param isWhiteListed true, the app will appears in whitelist, false, it won't
*/
- fun setWhiteListed(appUid: Int, isWhiteListed: Boolean)
+ fun setWhiteListed(app: ApplicationDescription, isWhiteListed: Boolean)
/**
* Set or unset in whitelist the specifid tracked, for the App specified by its uid.
* @param tracker the tracker
- * @param appUid the uid of the app
+ * @param app the ApplicationDescription of the app
* @param isWhiteListed true, the app will appears in whitelist, false, it won't
*/
- fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean)
+ fun setWhiteListed(tracker: Tracker, app: ApplicationDescription, isWhiteListed: Boolean)
/**
* Return true if nothing has been added to the whitelist : everything is blocked.
@@ -61,17 +64,19 @@ interface IBlockTrackersPrivacyModule {
/**
* Return the white listed App, by their UID
*/
- fun getWhiteListedApp(): List<Int>
+ fun getWhiteListedApp(): List<ApplicationDescription>
/**
* Return true if the App is whitelisted for trackers blocking.
*/
- fun isWhitelisted(appUid: Int): Boolean
+ fun isWhitelisted(app: ApplicationDescription): Boolean
/**
* List the white listed trackers for an App specified by it uid
*/
- fun getWhiteList(appUid: Int): List<Tracker>
+ fun getWhiteList(app: ApplicationDescription): List<Tracker>
+
+ fun clearWhiteList(app: ApplicationDescription)
/**
* Callback interface to get updates about the state of the Block trackers module.
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt
index 264f247..8aaed4a 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/ITrackTrackersPrivacyModule.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -17,34 +18,41 @@
package foundation.e.privacymodules.trackers.api
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
+
/**
* Get reporting about trackers calls.
*/
interface ITrackTrackersPrivacyModule {
- fun start(trackers: List<Tracker>, enableNotification: Boolean = true)
+ fun start(
+ trackers: List<Tracker>,
+ getAppByUid: (Int) -> ApplicationDescription?,
+ getAppByAPId: (String) -> ApplicationDescription?,
+ enableNotification: Boolean = true
+ )
/**
* List all the trackers encountered for a specific app.
*/
- fun getTrackersForApp(appUid: Int): List<Tracker>
+ fun getTrackersForApp(app: ApplicationDescription): List<Tracker>
/**
* List all the trackers encountere trackers since "ever", for the given [appUids],
* or all apps if [appUids] is null
*/
- fun getTrackers(appUids: List<Int>? = null): List<Tracker>
+ fun getTrackers(apps: List<ApplicationDescription>? = null): List<Tracker>
/**
* Return the number of encountered trackers since "ever", for the given [appUids],
* or all apps if [appUids] is null
*/
- fun getTrackersCount(appUids: List<Int>? = null): Int
+ fun getTrackersCount(): Int
/**
* Return the number of encountere trackers since "ever", for each app uid.
*/
- fun getTrackersCountByApp(): Map<Int, Int>
+ fun getTrackersCountByApp(): Map<ApplicationDescription, Int>
/**
* Return the number of encountered trackers for the last 24 hours
@@ -79,11 +87,11 @@ interface ITrackTrackersPrivacyModule {
*/
fun getPastYearTrackersCalls(): List<Pair<Int, Int>>
- fun getPastDayTrackersCallsByApps(): Map<Int, Pair<Int, Int>>
+ fun getPastDayTrackersCallsByApps(): Map<ApplicationDescription, Pair<Int, Int>>
- fun getPastDayTrackersCallsForApp(appUid: Int): Pair<Int, Int>
+ fun getPastDayTrackersCallsForApp(app: ApplicationDescription): Pair<Int, Int>
- fun getPastDayMostLeakedApp(): Int
+ fun getPastDayMostLeakedApp(): ApplicationDescription?
interface Listener {
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt
index 18c56c9..5fc5b6b 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/api/TrackTrackersPrivacyModule.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2021 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -19,9 +20,11 @@ package foundation.e.privacymodules.trackers.api
import android.content.Context
import android.content.Intent
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.trackers.DNSBlockerService
import foundation.e.privacymodules.trackers.data.StatsRepository
import foundation.e.privacymodules.trackers.data.TrackersRepository
+import foundation.e.privacymodules.trackers.data.WhitelistRepository
import java.time.temporal.ChronoUnit
class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersPrivacyModule {
@@ -42,8 +45,15 @@ class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersP
}
}
- override fun start(trackers: List<Tracker>, enableNotification: Boolean) {
+ override fun start(
+ trackers: List<Tracker>,
+ getAppByUid: (Int) -> ApplicationDescription?,
+ getAppByAPId: (String) -> ApplicationDescription?,
+ enableNotification: Boolean
+ ) {
TrackersRepository.getInstance().setTrackersList(trackers)
+ StatsRepository.getInstance(context).setAppGetters(getAppByUid, getAppByAPId)
+ WhitelistRepository.getInstance(context).setAppGetters(context, getAppByAPId, getAppByUid)
val intent = Intent(context, DNSBlockerService::class.java)
intent.action = DNSBlockerService.ACTION_START
intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, enableNotification)
@@ -62,20 +72,20 @@ class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersP
return statsRepository.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS)
}
- override fun getTrackersCount(appUids: List<Int>?): Int {
- return statsRepository.getContactedTrackersCount(appUids)
+ override fun getTrackersCount(): Int {
+ return statsRepository.getContactedTrackersCount()
}
- override fun getTrackersCountByApp(): Map<Int, Int> {
+ override fun getTrackersCountByApp(): Map<ApplicationDescription, Int> {
return statsRepository.getContactedTrackersCountByApp()
}
- override fun getTrackersForApp(appUid: Int): List<Tracker> {
- return statsRepository.getTrackers(listOf(appUid))
+ override fun getTrackersForApp(app: ApplicationDescription): List<Tracker> {
+ return statsRepository.getTrackers(listOf(app))
}
- override fun getTrackers(appUids: List<Int>?): List<Tracker> {
- return statsRepository.getTrackers(appUids)
+ override fun getTrackers(apps: List<ApplicationDescription>?): List<Tracker> {
+ return statsRepository.getTrackers(apps)
}
override fun getPastDayTrackersCount(): Int {
@@ -90,16 +100,16 @@ class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersP
return statsRepository.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS)
}
- override fun getPastDayMostLeakedApp(): Int {
+ override fun getPastDayMostLeakedApp(): ApplicationDescription? {
return statsRepository.getMostLeakedApp(24, ChronoUnit.HOURS)
}
- override fun getPastDayTrackersCallsByApps(): Map<Int, Pair<Int, Int>> {
+ override fun getPastDayTrackersCallsByApps(): Map<ApplicationDescription, Pair<Int, Int>> {
return statsRepository.getCallsByApps(24, ChronoUnit.HOURS)
}
- override fun getPastDayTrackersCallsForApp(appUid: Int): Pair<Int, Int> {
- return statsRepository.getCalls(appUid, 24, ChronoUnit.HOURS)
+ override fun getPastDayTrackersCallsForApp(app: ApplicationDescription): Pair<Int, Int> {
+ return statsRepository.getCalls(app, 24, ChronoUnit.HOURS)
}
override fun addListener(listener: ITrackTrackersPrivacyModule.Listener) {
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt
index 21edb56..4d287d4 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsDatabase.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -23,13 +24,15 @@ import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.provider.BaseColumns
+import androidx.core.database.getStringOrNull
import foundation.e.privacymodules.trackers.api.Tracker
-import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_APP_UID
+import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_APPID
import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_BLOCKED
import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_NUMBER_CONTACTED
import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP
import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER
import foundation.e.privacymodules.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME
+import timber.log.Timber
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
@@ -40,38 +43,44 @@ class StatsDatabase(context: Context) :
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
- const val DATABASE_VERSION = 1
+ const val DATABASE_VERSION = 2
const val DATABASE_NAME = "TrackerFilterStats.db"
private const val SQL_CREATE_TABLE = "CREATE TABLE $TABLE_NAME (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," +
"$COLUMN_NAME_TIMESTAMP INTEGER," +
- "$COLUMN_NAME_APP_UID INTEGER," +
"$COLUMN_NAME_TRACKER TEXT," +
"$COLUMN_NAME_NUMBER_CONTACTED INTEGER," +
- "$COLUMN_NAME_NUMBER_BLOCKED INTEGER)"
+ "$COLUMN_NAME_NUMBER_BLOCKED INTEGER," +
+ "$COLUMN_NAME_APPID TEXT)"
private const val PROJECTION_NAME_PERIOD = "period"
private const val PROJECTION_NAME_CONTACTED_SUM = "contactedsum"
private const val PROJECTION_NAME_BLOCKED_SUM = "blockedsum"
private const val PROJECTION_NAME_LEAKED_SUM = "leakedsum"
private const val PROJECTION_NAME_TRACKERS_COUNT = "trackerscount"
+
+ private val MIGRATE_1_2 = listOf(
+ "ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_NAME_APPID TEXT"
+ // "ALTER TABLE $TABLE_NAME DROP COLUMN app_uid"
+ // DROP COLUMN is available since sqlite 3.35.0, and sdk29 as 3.22.0, sdk32 as 3.32.2
+ )
}
object AppTrackerEntry : BaseColumns {
const val TABLE_NAME = "tracker_filter_stats"
const val COLUMN_NAME_TIMESTAMP = "timestamp"
const val COLUMN_NAME_TRACKER = "tracker"
- const val COLUMN_NAME_APP_UID = "app_uid"
const val COLUMN_NAME_NUMBER_CONTACTED = "sum_contacted"
const val COLUMN_NAME_NUMBER_BLOCKED = "sum_blocked"
+ const val COLUMN_NAME_APPID = "app_apid"
}
private var projection = arrayOf(
COLUMN_NAME_TIMESTAMP,
- COLUMN_NAME_APP_UID,
COLUMN_NAME_TRACKER,
COLUMN_NAME_NUMBER_CONTACTED,
- COLUMN_NAME_NUMBER_BLOCKED
+ COLUMN_NAME_NUMBER_BLOCKED,
+ COLUMN_NAME_APPID
)
private val lock = Any()
@@ -82,7 +91,13 @@ class StatsDatabase(context: Context) :
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
- onCreate(db)
+ if (oldVersion == 1 && newVersion == 2) {
+ MIGRATE_1_2.forEach(db::execSQL)
+ } else {
+ Timber.e(
+ "Unexpected database versions: oldVersion: $oldVersion ; newVersion: $newVersion"
+ )
+ }
}
private fun getCallsByPeriod(
@@ -128,11 +143,11 @@ class StatsDatabase(context: Context) :
javaPeriodFormat: String
): List<Pair<Int, Int>> {
var currentDate = ZonedDateTime.now().minus(periodsCount.toLong(), periodUnit)
- val formater = DateTimeFormatter.ofPattern(javaPeriodFormat)
+ val formatter = DateTimeFormatter.ofPattern(javaPeriodFormat)
val calls = mutableListOf<Pair<Int, Int>>()
for (i in 0 until periodsCount) {
currentDate = currentDate.plus(1, periodUnit)
- val currentPeriod = formater.format(currentDate)
+ val currentPeriod = formatter.format(currentDate)
calls.add(callsByPeriod.getOrDefault(currentPeriod, 0 to 0))
}
return calls
@@ -182,15 +197,11 @@ class StatsDatabase(context: Context) :
}
}
- fun getContactedTrackersCount(appUids: List<Int>?): Int {
+ fun getContactedTrackersCount(): Int {
synchronized(lock) {
val db = readableDatabase
var query = "SELECT DISTINCT $COLUMN_NAME_TRACKER FROM $TABLE_NAME"
- appUids?.let {
- query += " WHERE $COLUMN_NAME_APP_UID IN (${it.joinToString(", ")})"
- }
-
val cursor = db.rawQuery(query, arrayOf())
var count = 0
while (cursor.moveToNext()) {
@@ -204,19 +215,19 @@ class StatsDatabase(context: Context) :
}
}
- fun getContactedTrackersCountByApp(): Map<Int, Int> {
+ fun getContactedTrackersCountByAppId(): Map<String, Int> {
synchronized(lock) {
val db = readableDatabase
- val projection = "$COLUMN_NAME_APP_UID, $COLUMN_NAME_TRACKER"
+ val projection = "$COLUMN_NAME_APPID, $COLUMN_NAME_TRACKER"
val cursor = db.rawQuery(
"SELECT DISTINCT $projection FROM $TABLE_NAME", // +
arrayOf()
)
- val countByApp = mutableMapOf<Int, Int>()
+ val countByApp = mutableMapOf<String, Int>()
while (cursor.moveToNext()) {
trackersRepository.getTracker(cursor.getString(COLUMN_NAME_TRACKER))?.let {
- val appUid = cursor.getInt(COLUMN_NAME_APP_UID)
- countByApp[appUid] = countByApp.getOrDefault(appUid, 0) + 1
+ val appId = cursor.getString(COLUMN_NAME_APPID)
+ countByApp[appId] = countByApp.getOrDefault(appId, 0) + 1
}
}
cursor.close()
@@ -225,26 +236,26 @@ class StatsDatabase(context: Context) :
}
}
- fun getCallsByApps(periodCount: Int, periodUnit: TemporalUnit): Map<Int, Pair<Int, Int>> {
+ fun getCallsByAppIds(periodCount: Int, periodUnit: TemporalUnit): Map<String, Pair<Int, Int>> {
synchronized(lock) {
val minTimestamp = getPeriodStartTs(periodCount, periodUnit)
val db = readableDatabase
val selection = "$COLUMN_NAME_TIMESTAMP >= ?"
val selectionArg = arrayOf("" + minTimestamp)
- val projection = "$COLUMN_NAME_APP_UID, " +
+ val projection = "$COLUMN_NAME_APPID, " +
"SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," +
"SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM"
val cursor = db.rawQuery(
"SELECT $projection FROM $TABLE_NAME" +
" WHERE $selection" +
- " GROUP BY $COLUMN_NAME_APP_UID",
+ " GROUP BY $COLUMN_NAME_APPID",
selectionArg
)
- val callsByApp = HashMap<Int, Pair<Int, Int>>()
+ val callsByApp = HashMap<String, Pair<Int, Int>>()
while (cursor.moveToNext()) {
val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM)
val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM)
- callsByApp[cursor.getInt(COLUMN_NAME_APP_UID)] = blocked to contacted - blocked
+ callsByApp[cursor.getString(COLUMN_NAME_APPID)] = blocked to contacted - blocked
}
cursor.close()
db.close()
@@ -252,13 +263,13 @@ class StatsDatabase(context: Context) :
}
}
- fun getCalls(appUid: Int, periodCount: Int, periodUnit: TemporalUnit): Pair<Int, Int> {
+ fun getCalls(appId: String, periodCount: Int, periodUnit: TemporalUnit): Pair<Int, Int> {
synchronized(lock) {
val minTimestamp = getPeriodStartTs(periodCount, periodUnit)
val db = readableDatabase
- val selection = "$COLUMN_NAME_APP_UID = ? AND " +
+ val selection = "$COLUMN_NAME_APPID = ? AND " +
"$COLUMN_NAME_TIMESTAMP >= ?"
- val selectionArg = arrayOf("" + appUid, "" + minTimestamp)
+ val selectionArg = arrayOf("" + appId, "" + minTimestamp)
val projection =
"SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," +
"SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM"
@@ -278,37 +289,37 @@ class StatsDatabase(context: Context) :
}
}
- fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): Int {
+ fun getMostLeakedAppId(periodCount: Int, periodUnit: TemporalUnit): String {
synchronized(lock) {
val minTimestamp = getPeriodStartTs(periodCount, periodUnit)
val db = readableDatabase
val selection = "$COLUMN_NAME_TIMESTAMP >= ?"
val selectionArg = arrayOf("" + minTimestamp)
- val projection = "$COLUMN_NAME_APP_UID, " +
+ val projection = "$COLUMN_NAME_APPID, " +
"SUM($COLUMN_NAME_NUMBER_CONTACTED - $COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_LEAKED_SUM"
val cursor = db.rawQuery(
"SELECT $projection FROM $TABLE_NAME" +
" WHERE $selection" +
- " GROUP BY $COLUMN_NAME_APP_UID" +
+ " GROUP BY $COLUMN_NAME_APPID" +
" ORDER BY $PROJECTION_NAME_LEAKED_SUM DESC LIMIT 1",
selectionArg
)
- var appUid = 0
+ var appId = ""
if (cursor.moveToNext()) {
- appUid = cursor.getInt(COLUMN_NAME_APP_UID)
+ appId = cursor.getString(COLUMN_NAME_APPID)
}
cursor.close()
db.close()
- return appUid
+ return appId
}
}
- fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) {
+ fun logAccess(trackerId: String?, appId: String, blocked: Boolean) {
synchronized(lock) {
val currentHour = getCurrentHourTs()
val db = writableDatabase
val values = ContentValues()
- values.put(COLUMN_NAME_APP_UID, appUid)
+ values.put(COLUMN_NAME_APPID, appId)
values.put(COLUMN_NAME_TRACKER, trackerId)
values.put(COLUMN_NAME_TIMESTAMP, currentHour)
@@ -317,9 +328,9 @@ class StatsDatabase(context: Context) :
query+=COLUMN_NAME_NUMBER_BLOCKED+" = "+COLUMN_NAME_NUMBER_BLOCKED+" + 1 ";
*/
val selection = "$COLUMN_NAME_TIMESTAMP = ? AND " +
- "$COLUMN_NAME_APP_UID = ? AND " +
+ "$COLUMN_NAME_APPID = ? AND " +
"$COLUMN_NAME_TRACKER = ? "
- val selectionArg = arrayOf("" + currentHour, "" + appUid, trackerId)
+ val selectionArg = arrayOf("" + currentHour, "" + appId, trackerId)
val cursor = db.query(
TABLE_NAME,
projection,
@@ -355,23 +366,22 @@ class StatsDatabase(context: Context) :
private fun cursorToEntry(cursor: Cursor): StatEntry {
val entry = StatEntry()
- entry.timestamp =
- cursor.getLong(COLUMN_NAME_TIMESTAMP)
- entry.app_uid = cursor.getInt(COLUMN_NAME_APP_UID)
+ entry.timestamp = cursor.getLong(COLUMN_NAME_TIMESTAMP)
+ entry.appId = cursor.getString(COLUMN_NAME_APPID)
entry.sum_blocked = cursor.getInt(COLUMN_NAME_NUMBER_BLOCKED)
entry.sum_contacted = cursor.getInt(COLUMN_NAME_NUMBER_CONTACTED)
entry.tracker = cursor.getInt(COLUMN_NAME_TRACKER)
return entry
}
- fun getTrackers(appUids: List<Int>?): List<Tracker> {
+ fun getTrackers(appIds: List<String>?): List<Tracker> {
synchronized(lock) {
- val columns = arrayOf(COLUMN_NAME_TRACKER, COLUMN_NAME_APP_UID)
+ val columns = arrayOf(COLUMN_NAME_TRACKER, COLUMN_NAME_APPID)
var selection: String? = null
var selectionArg: Array<String>? = null
- appUids?.let {
- selection = "$COLUMN_NAME_APP_UID IN (${it.joinToString(", ")})"
+ appIds?.let { appIds ->
+ selection = "$COLUMN_NAME_APPID IN (${appIds.joinToString(", ") { "'$it'" }})"
selectionArg = arrayOf()
}
@@ -402,7 +412,7 @@ class StatsDatabase(context: Context) :
}
class StatEntry {
- var app_uid = 0
+ var appId = ""
var sum_contacted = 0
var sum_blocked = 0
var timestamp: Long = 0
@@ -442,6 +452,8 @@ class StatsDatabase(context: Context) :
private fun Cursor.getString(columnName: String): String {
val columnIndex = getColumnIndex(columnName)
- return if (columnIndex >= 0) getString(columnIndex) else ""
+ return if (columnIndex >= 0) {
+ getStringOrNull(columnIndex) ?: ""
+ } else ""
}
}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt
index 16d8ec6..8f02adb 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/StatsRepository.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -18,12 +19,15 @@
package foundation.e.privacymodules.trackers.data
import android.content.Context
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.trackers.api.Tracker
import java.time.temporal.TemporalUnit
class StatsRepository private constructor(context: Context) {
private val database: StatsDatabase
private var newDataCallback: (() -> Unit)? = null
+ private var getAppByUid: ((Int) -> ApplicationDescription?)? = null
+ private var getAppByAPId: ((String) -> ApplicationDescription?)? = null
companion object {
private var instance: StatsRepository? = null
@@ -32,6 +36,14 @@ class StatsRepository private constructor(context: Context) {
}
}
+ fun setAppGetters(
+ getAppByUid: (Int) -> ApplicationDescription?,
+ getAppByAPId: (String) -> ApplicationDescription?
+ ) {
+ this.getAppByUid = getAppByUid
+ this.getAppByAPId = getAppByAPId
+ }
+
init {
database = StatsDatabase(context)
}
@@ -41,8 +53,10 @@ class StatsRepository private constructor(context: Context) {
}
fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) {
- database.logAccess(trackerId, appUid, blocked)
- newDataCallback?.invoke()
+ getAppByUid?.invoke(appUid)?.let { app ->
+ database.logAccess(trackerId, app.apId, blocked)
+ newDataCallback?.invoke()
+ }
}
fun getTrackersCallsOnPeriod(
@@ -56,27 +70,36 @@ class StatsRepository private constructor(context: Context) {
return database.getActiveTrackersByPeriod(periodsCount, periodUnit)
}
- fun getContactedTrackersCountByApp(): Map<Int, Int> {
- return database.getContactedTrackersCountByApp()
+ fun getContactedTrackersCountByApp(): Map<ApplicationDescription, Int> {
+ return database.getContactedTrackersCountByAppId().mapByAppIdToApp()
}
- fun getContactedTrackersCount(appUids: List<Int>?): Int {
- return database.getContactedTrackersCount(appUids)
+ fun getContactedTrackersCount(): Int {
+ return database.getContactedTrackersCount()
}
- fun getTrackers(appUids: List<Int>?): List<Tracker> {
- return database.getTrackers(appUids)
+ fun getTrackers(apps: List<ApplicationDescription>?): List<Tracker> {
+ return database.getTrackers(apps?.map { it.apId })
+ }
+
+ fun getCallsByApps(
+ periodCount: Int,
+ periodUnit: TemporalUnit
+ ): Map<ApplicationDescription, Pair<Int, Int>> {
+ return database.getCallsByAppIds(periodCount, periodUnit).mapByAppIdToApp()
}
- fun getCallsByApps(periodCount: Int, periodUnit: TemporalUnit): Map<Int, Pair<Int, Int>> {
- return database.getCallsByApps(periodCount, periodUnit)
+ fun getCalls(app: ApplicationDescription, periodCount: Int, periodUnit: TemporalUnit): Pair<Int, Int> {
+ return database.getCalls(app.apId, periodCount, periodUnit)
}
- fun getCalls(appUid: Int, periodCount: Int, periodUnit: TemporalUnit): Pair<Int, Int> {
- return database.getCalls(appUid, periodCount, periodUnit)
+ fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): ApplicationDescription? {
+ return getAppByAPId?.invoke(database.getMostLeakedAppId(periodCount, periodUnit))
}
- fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): Int {
- return database.getMostLeakedApp(periodCount, periodUnit)
+ private fun <K> Map<String, K>.mapByAppIdToApp(): Map<ApplicationDescription, K> {
+ return entries.mapNotNull { (apId, value) ->
+ getAppByAPId?.invoke(apId)?.let { it to value }
+ }.toMap()
}
}
diff --git a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt
index e9f049d..2763d06 100644
--- a/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt
+++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/data/WhitelistRepository.kt
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2023 MURENA SAS
* Copyright (C) 2022 E FOUNDATION
*
* This program is free software: you can redistribute it and/or modify
@@ -19,18 +20,28 @@ package foundation.e.privacymodules.trackers.data
import android.content.Context
import android.content.SharedPreferences
+import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.trackers.api.Tracker
+import java.io.File
class WhitelistRepository private constructor(context: Context) {
- private lateinit var appsWhitelist: Set<Int>
- private val trackersWhitelistByApp: MutableMap<Int, MutableSet<String>> = HashMap()
+ private var appsWhitelist: Set<String> = HashSet()
+ private var appUidsWhitelist: Set<Int> = HashSet()
+
+ private var trackersWhitelistByApp: MutableMap<String, MutableSet<String>> = HashMap()
+ private var trackersWhitelistByUid: Map<Int, MutableSet<String>> = HashMap()
+
private val prefs: SharedPreferences
+ private var getAppByAPId: ((String) -> ApplicationDescription?)? = null
companion object {
- private const val SHARED_PREFS_FILE = "trackers_whitelist.prefs"
- private const val KEY_BLOKING_ENABLED = "blocking_enabled"
+ private const val SHARED_PREFS_FILE = "trackers_whitelist_v2"
+ private const val KEY_BLOCKING_ENABLED = "blocking_enabled"
private const val KEY_APPS_WHITELIST = "apps_whitelist"
private const val KEY_APP_TRACKERS_WHITELIST_PREFIX = "app_trackers_whitelist_"
+
+ private const val SHARED_PREFS_FILE_V1 = "trackers_whitelist.prefs"
+
private var instance: WhitelistRepository? = null
fun getInstance(context: Context): WhitelistRepository {
return instance ?: WhitelistRepository(context).apply { instance = this }
@@ -42,83 +53,155 @@ class WhitelistRepository private constructor(context: Context) {
reloadCache()
}
+ fun setAppGetters(
+ context: Context,
+ getAppByAPId: (String) -> ApplicationDescription?,
+ getAppByUid: (Int) -> ApplicationDescription?
+ ) {
+ this.getAppByAPId = getAppByAPId
+ migrate(context, getAppByUid)
+ }
+
+ private fun migrate(context: Context, getAppByUid: (Int) -> ApplicationDescription?) {
+ if (context.sharedPreferencesExists(SHARED_PREFS_FILE_V1)) {
+ migrate1To2(context, getAppByUid)
+ }
+ }
+
+ private fun Context.sharedPreferencesExists(fileName: String): Boolean {
+ return File(
+ "${applicationInfo.dataDir}/shared_prefs/$fileName.xml"
+ ).exists()
+ }
+
+ private fun migrate1To2(context: Context, getAppByUid: (Int) -> ApplicationDescription?) {
+ val prefsV1 = context.getSharedPreferences(SHARED_PREFS_FILE_V1, Context.MODE_PRIVATE)
+ val editorV2 = prefs.edit()
+
+ editorV2.putBoolean(KEY_BLOCKING_ENABLED, prefsV1.getBoolean(KEY_BLOCKING_ENABLED, false))
+
+ val apIds = prefsV1.getStringSet(KEY_APPS_WHITELIST, HashSet())?.mapNotNull {
+ try {
+ val uid = it.toInt()
+ getAppByUid(uid)?.apId
+ } catch (e: Exception) { null }
+ }?.toSet() ?: HashSet()
+
+ editorV2.putStringSet(KEY_APPS_WHITELIST, apIds)
+
+ prefsV1.all.keys.forEach { key ->
+ if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) {
+ try {
+ val uid = key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length).toInt()
+ val apId = getAppByUid(uid)?.apId
+ apId?.let {
+ val trackers = prefsV1.getStringSet(key, emptySet())
+ editorV2.putStringSet(buildAppTrackersKey(apId), trackers)
+ }
+ } catch (e: Exception) { }
+ }
+ }
+ editorV2.commit()
+
+ context.deleteSharedPreferences(SHARED_PREFS_FILE_V1)
+
+ reloadCache()
+ }
+
private fun reloadCache() {
- isBlockingEnabled = prefs.getBoolean(KEY_BLOKING_ENABLED, false)
+ isBlockingEnabled = prefs.getBoolean(KEY_BLOCKING_ENABLED, false)
reloadAppsWhiteList()
reloadAllAppTrackersWhiteList()
}
private fun reloadAppsWhiteList() {
- appsWhitelist = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet())?.mapNotNull {
- try { it.toInt() } catch (e: Exception) { null }
- }?.toHashSet() ?: HashSet()
+ appsWhitelist = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet()) ?: HashSet()
+ appUidsWhitelist = appsWhitelist
+ .mapNotNull { apId -> getAppByAPId?.invoke(apId)?.uid }
+ .toSet()
}
- private fun reloadAppTrackersWhiteList(appUid: Int) {
- val key = buildAppTrackersKey(appUid)
- trackersWhitelistByApp[appUid] = prefs.getStringSet(key, HashSet()) ?: HashSet()
+ private fun refreshAppUidTrackersWhiteList() {
+ trackersWhitelistByUid = trackersWhitelistByApp.mapNotNull { (apId, value) ->
+ getAppByAPId?.invoke(apId)?.uid?.let { uid ->
+ uid to value
+ }
+ }.toMap()
}
-
private fun reloadAllAppTrackersWhiteList() {
- trackersWhitelistByApp.clear()
+ val map: MutableMap<String, MutableSet<String>> = HashMap()
prefs.all.keys.forEach { key ->
if (key.startsWith(KEY_APP_TRACKERS_WHITELIST_PREFIX)) {
- val appUid = key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length).toInt()
- reloadAppTrackersWhiteList(appUid)
+ map[key.substring(KEY_APP_TRACKERS_WHITELIST_PREFIX.length)] = (
+ prefs.getStringSet(key, HashSet()) ?: HashSet()
+ )
}
}
+ trackersWhitelistByApp = map
}
var isBlockingEnabled: Boolean = false
get() = field
set(enabled) {
- prefs.edit().putBoolean(KEY_BLOKING_ENABLED, enabled).apply()
+ prefs.edit().putBoolean(KEY_BLOCKING_ENABLED, enabled).apply()
field = enabled
}
- fun setWhiteListed(appUid: Int, isWhiteListed: Boolean) {
+ fun setWhiteListed(apId: String, isWhiteListed: Boolean) {
val current = prefs.getStringSet(KEY_APPS_WHITELIST, HashSet())?.toHashSet() ?: HashSet()
if (isWhiteListed) {
- current.add("" + appUid)
+ current.add(apId)
} else {
- current.remove("" + appUid)
+ current.remove(apId)
}
prefs.edit().putStringSet(KEY_APPS_WHITELIST, current).commit()
reloadAppsWhiteList()
}
- private fun buildAppTrackersKey(appUid: Int): String {
- return KEY_APP_TRACKERS_WHITELIST_PREFIX + appUid
+ private fun buildAppTrackersKey(apId: String): String {
+ return KEY_APP_TRACKERS_WHITELIST_PREFIX + apId
}
- fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean) {
- val trackers = trackersWhitelistByApp.getOrDefault(appUid, HashSet())
- trackersWhitelistByApp[appUid] = trackers
+ fun setWhiteListed(tracker: Tracker, apId: String, isWhiteListed: Boolean) {
+ val trackers = trackersWhitelistByApp.getOrDefault(apId, HashSet())
+ trackersWhitelistByApp[apId] = trackers
if (isWhiteListed) {
trackers.add(tracker.id)
} else {
trackers.remove(tracker.id)
}
- prefs.edit().putStringSet(buildAppTrackersKey(appUid), trackers).commit()
+ refreshAppUidTrackersWhiteList()
+ prefs.edit().putStringSet(buildAppTrackersKey(apId), trackers).commit()
}
- fun isAppWhiteListed(appUid: Int): Boolean {
- return appsWhitelist.contains(appUid)
+ fun isAppWhiteListed(app: ApplicationDescription): Boolean {
+ return appsWhitelist.contains(app.apId)
}
- fun isTrackerWhiteListedForApp(trackerId: String?, appUid: Int): Boolean {
- return trackersWhitelistByApp.getOrDefault(appUid, HashSet()).contains(trackerId)
+ fun isWhiteListed(appUid: Int, trackerId: String?): Boolean {
+ return appUidsWhitelist.contains(appUid) ||
+ trackersWhitelistByUid.getOrDefault(appUid, HashSet()).contains(trackerId)
}
fun areWhiteListEmpty(): Boolean {
return appsWhitelist.isEmpty() && trackersWhitelistByApp.all { (_, trackers) -> trackers.isEmpty() }
}
- val whiteListedApp: List<Int> get() = appsWhitelist.toList()
+ fun getWhiteListedApp(): List<ApplicationDescription> {
+ return getAppByAPId?.let {
+ appsWhitelist.mapNotNull(it)
+ } ?: emptyList()
+ }
+
+ fun getWhiteListForApp(app: ApplicationDescription): List<String> {
+ return trackersWhitelistByApp[app.apId]?.toList() ?: emptyList()
+ }
- fun getWhiteListForApp(appUid: Int): List<String> {
- return trackersWhitelistByApp[appUid]?.toList() ?: emptyList()
+ fun clearWhiteList(apId: String) {
+ trackersWhitelistByApp.remove(apId)
+ refreshAppUidTrackersWhiteList()
+ prefs.edit().remove(buildAppTrackersKey(apId)).commit()
}
}