summaryrefslogtreecommitdiff
path: root/trackers
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 /trackers
parent2df577ca97a674a4bd3875dc5137bb44df2c03ef (diff)
6556: add AdvancedPrivacy App Id in trackers stats to avoid appUid aliasing
Diffstat (limited to 'trackers')
-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
10 files changed, 288 insertions, 135 deletions
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()
}
}