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 65ae4781e0ab0d94f004685bfa3c83e09e35a526..2d32f24f7576cd72f69d24e7926664e8e6b8cd1d 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 @@ -128,6 +128,28 @@ class AppListsRepository( } else appValueGetter(appUid) } + private var packageNameByAppUid = mapOf() + + fun getPackageName(appUid: Int): String { + return packageNameByAppUid.get(appUid) ?: run { + buildAppDescsByAppUid( + permissionsModule.getApplications(::hasInternetPermission, false) + + permissionsModule.getWorkProfileApplications(::hasInternetPermission, false) + ) + packageNameByAppUid.get(appUid) ?: throw AppUidNotFoundException(appUid) + } + } + + private fun buildAppDescsByAppUid(appDescs: List) { + val map = mutableMapOf() + appDescs.forEach { appDesc -> + if (map.get(appDesc.uid).let { it == null || it > appDesc.packageName }) { + map[appDesc.uid] = appDesc.packageName + } + } + packageNameByAppUid = map + } + private val pm get() = context.packageManager private val appDescriptions = MutableStateFlow( @@ -166,3 +188,5 @@ class AppListsRepository( private fun ApplicationInfo.hasFlag(flag: Int) = (flags and flag) == 1 } + +class AppUidNotFoundException(appUid: Int) : Exception("App UID: $appUid not found.") 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 11f0466df75263a818084eca644dbf476409dd18..cfb3e494422e0dc4b1fd55875756750ac66dc1c7 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 @@ -36,7 +36,11 @@ class TrackersStateUseCase( private val coroutineScope: CoroutineScope ) { init { - trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false) + trackersPrivacyModule.start( + trackers = trackersRepository.trackers, + packageNameGetter = appListsRepository::getPackageName, + enableNotification = false + ) coroutineScope.launch { localStateRepository.blockTrackers.collect { enabled -> if (enabled) { @@ -97,7 +101,11 @@ class TrackersStateUseCase( fun updateTrackers() = coroutineScope.launch { trackersRepository.update() - trackersPrivacyModule.start(trackersRepository.trackers, enableNotification = false) + trackersPrivacyModule.start( + trackers = trackersRepository.trackers, + packageNameGetter = appListsRepository::getPackageName, + enableNotification = false + ) } } 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 404032bfd87b7620aa3a2b7dca48f58871f06d7b..bce6a11bbea327cdc7f7f87e950386a8e66e0221 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 @@ -75,9 +75,9 @@ class TrackersStatisticsUseCase( } fun getMostLeakedApp(): ApplicationDescription? { - return appListsRepository.getApplicationDescription( - trackTrackersPrivacyModule.getPastDayMostLeakedApp() - ) + return trackTrackersPrivacyModule.getPastDayMostLeakedApp()?.let { + appListsRepository.getApplicationDescription(it) + } } fun getDayTrackersCalls() = trackTrackersPrivacyModule.getPastDayTrackersCalls() diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt deleted file mode 100644 index f70065cbdb1b0bd0fa84c08436687a45377ae517..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2022 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.privacycentralapp.domain.usecases - -import foundation.e.privacycentralapp.data.repositories.LocalStateRepository -import foundation.e.privacymodules.trackers.api.ITrackTrackersPrivacyModule - -class UpdateWidgetUseCase( - private val localStateRepository: LocalStateRepository, - private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, -) { - init { - trackTrackersPrivacyModule.addListener(object : ITrackTrackersPrivacyModule.Listener { - override fun onNewData() { - } - }) - } -} diff --git a/dependencies.gradle b/dependencies.gradle index 352958effcba72753faf5191f87cf79e8b4ee08e..880d08543e4a7b9eeb2bc6f9d4af73b0dc73109b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -25,7 +25,7 @@ ext.Versions = versions libs.androidGradlePlugin = "com.android.tools.build:gradle:7.2.1" -libs.timber = "com.jakewharton.timber:timber:4.7.1" +libs.timber = "com.jakewharton.timber:timber:5.0.1" libs.junit = "junit:junit:4.13.1" diff --git a/trackers/build.gradle b/trackers/build.gradle index f888acfc347bbe7ef7aae00ae5abc3f1a9b09966..ecf95bee733e297e9d4b2f859e89e17351d64788 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/TrackersLogger.kt b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt index 99e21482cd76ffe9eb5492821802d6e43e610a54..0c343c8aea0809ed749b409768ce3c6fd34104d9 100644 --- a/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt +++ b/trackers/src/main/java/foundation/e/privacymodules/trackers/TrackersLogger.kt @@ -56,7 +56,7 @@ class TrackersLogger(context: Context) { Thread(writeLogRunner).start() } - fun logAccess(detectedTracker: DetectedTracker) { + private fun logAccess(detectedTracker: DetectedTracker) { statsRepository.logAccess( detectedTracker.trackerId, detectedTracker.appUid, 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 264f247f1fe1a066ea884839921139caa0c8aa20..0407141f4fdfdeab8d7bc2c6d5e225198495c109 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 @@ -22,7 +22,11 @@ package foundation.e.privacymodules.trackers.api */ interface ITrackTrackersPrivacyModule { - fun start(trackers: List, enableNotification: Boolean = true) + fun start( + trackers: List, + packageNameGetter: (Int) -> String, + enableNotification: Boolean = true + ) /** * List all the trackers encountered for a specific app. @@ -83,7 +87,7 @@ interface ITrackTrackersPrivacyModule { fun getPastDayTrackersCallsForApp(appUid: Int): Pair - fun getPastDayMostLeakedApp(): Int + fun getPastDayMostLeakedApp(): Int? 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 18c56c9dce7f2fa262c7858f00ef3ae7d3f3a51a..9d46ce3d152bbc2651bff4101320e607d143983a 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 @@ -42,8 +42,13 @@ class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersP } } - override fun start(trackers: List, enableNotification: Boolean) { + override fun start( + trackers: List, + packageNameGetter: (Int) -> String, + enableNotification: Boolean + ) { TrackersRepository.getInstance().setTrackersList(trackers) + StatsRepository.getInstance(context).setPackageNameGetter(packageNameGetter) val intent = Intent(context, DNSBlockerService::class.java) intent.action = DNSBlockerService.ACTION_START intent.putExtra(DNSBlockerService.EXTRA_ENABLE_NOTIFICATION, enableNotification) @@ -90,7 +95,7 @@ class TrackTrackersPrivacyModule(private val context: Context) : ITrackTrackersP return statsRepository.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS) } - override fun getPastDayMostLeakedApp(): Int { + override fun getPastDayMostLeakedApp(): Int? { return statsRepository.getMostLeakedApp(24, ChronoUnit.HOURS) } 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 21edb56f9cb286267535de7881a3244ae6b9dadf..69e5cfaedc7f4a0ea409d65e6e77f3313951b053 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 @@ -24,12 +24,13 @@ import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.provider.BaseColumns 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 +41,45 @@ 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", + "UPDATE $TABLE_NAME SET $COLUMN_NAME_APPID = app_uid || '_'", + // "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 +90,11 @@ 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(Exception("Unexpected database versions: oldVersion: $oldVersion ; newVersion: $newVersion")) + } } private fun getCallsByPeriod( @@ -182,13 +194,13 @@ class StatsDatabase(context: Context) : } } - fun getContactedTrackersCount(appUids: List?): Int { + fun getContactedTrackersCount(appIds: List?): 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(", ")})" + appIds?.let { appIds -> + query += " WHERE $COLUMN_NAME_APPID IN (${appIds.joinToString(", ") { "'$it'" }})" } val cursor = db.rawQuery(query, arrayOf()) @@ -204,19 +216,19 @@ class StatsDatabase(context: Context) : } } - fun getContactedTrackersCountByApp(): Map { + fun getContactedTrackersCountByAppId(): Map { 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() + val countByApp = mutableMapOf() 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 +237,26 @@ class StatsDatabase(context: Context) : } } - fun getCallsByApps(periodCount: Int, periodUnit: TemporalUnit): Map> { + fun getCallsByAppIds(periodCount: Int, periodUnit: TemporalUnit): Map> { 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>() + val callsByApp = HashMap>() 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 +264,13 @@ class StatsDatabase(context: Context) : } } - fun getCalls(appUid: Int, periodCount: Int, periodUnit: TemporalUnit): Pair { + fun getCalls(appId: String, periodCount: Int, periodUnit: TemporalUnit): Pair { 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 +290,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 +329,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 +367,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?): List { + fun getTrackers(appIds: List?): List { 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? = 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 +413,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 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 16d8ec674f47857334b5fae15666b4ef032b6992..e79c7fc3e0a0c5c1f76e0ec0cb8461a8f3e1a678 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 @@ -19,11 +19,13 @@ package foundation.e.privacymodules.trackers.data import android.content.Context import foundation.e.privacymodules.trackers.api.Tracker +import timber.log.Timber import java.time.temporal.TemporalUnit class StatsRepository private constructor(context: Context) { private val database: StatsDatabase private var newDataCallback: (() -> Unit)? = null + private var packageNameGetter: ((Int) -> String)? = null companion object { private var instance: StatsRepository? = null @@ -40,8 +42,12 @@ class StatsRepository private constructor(context: Context) { newDataCallback = callback } + fun setPackageNameGetter(getter: (Int) -> String) { + packageNameGetter = getter + } + fun logAccess(trackerId: String?, appUid: Int, blocked: Boolean) { - database.logAccess(trackerId, appUid, blocked) + database.logAccess(trackerId, buildAppId(appUid), blocked) newDataCallback?.invoke() } @@ -57,26 +63,56 @@ class StatsRepository private constructor(context: Context) { } fun getContactedTrackersCountByApp(): Map { - return database.getContactedTrackersCountByApp() + return mapByAppIdToAppUid(database.getContactedTrackersCountByAppId()) } fun getContactedTrackersCount(appUids: List?): Int { - return database.getContactedTrackersCount(appUids) + return database.getContactedTrackersCount(appUids?.mapNotNull { buildAppId(it) }) } fun getTrackers(appUids: List?): List { - return database.getTrackers(appUids) + return database.getTrackers(appUids?.mapNotNull { buildAppId(it) }) } fun getCallsByApps(periodCount: Int, periodUnit: TemporalUnit): Map> { - return database.getCallsByApps(periodCount, periodUnit) + return mapByAppIdToAppUid(database.getCallsByAppIds(periodCount, periodUnit)) } fun getCalls(appUid: Int, periodCount: Int, periodUnit: TemporalUnit): Pair { - return database.getCalls(appUid, periodCount, periodUnit) + return database.getCalls(buildAppId(appUid), periodCount, periodUnit) + } + + fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): Int? { + return extractAppUid(database.getMostLeakedAppId(periodCount, periodUnit)) + } + + private fun getPackageName(appUid: Int): String? { + return try { + packageNameGetter?.invoke(appUid) + } catch (e: Exception) { + Timber.w(e) + null + } + } + private fun buildAppId(appUid: Int): String { + return "${appUid}_${getPackageName(appUid)}" + } + + private fun extractAppUid(appId: String): Int? { + return try { + val elements = appId.split("_", limit = 2) + val appUid = elements[0].toInt() + val packageName = elements[1] + + if (getPackageName(appUid = appUid) == packageName) appUid else null + } catch (e: Exception) { + null + } } - fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): Int { - return database.getMostLeakedApp(periodCount, periodUnit) + private fun mapByAppIdToAppUid(mapByAppId: Map): Map { + return mapByAppId.entries.mapNotNull { (appId, value) -> + extractAppUid(appId)?.let { it to value } + }.toMap() } }