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

Commit 028f177e authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

feat:2995: debug generate reports since weeks ago

parent c28df703
Loading
Loading
Loading
Loading
+21 −9
Original line number Diff line number Diff line
@@ -17,15 +17,27 @@

package foundation.e.advancedprivacy.domain.entities

import foundation.e.advancedprivacy.trackers.domain.entities.Tracker

data class WeeklyReport(
    val appsWithNewTrackers: List<AppWithTrackers>,
    val trackersWithCallsChanges: List<TrackerWithCount>,
    val newTrackers: List<Tracker>
    val statType: StatType,
    val labelId: LabelId,
    val value: List<String>
) {
    data class AppWithTrackers(
        val app: DisplayableApp,
        val trackers: List<Tracker>
    )
    enum class StatType {
        CALLS_PER_APP,
        NEW_TRACKER,
        CALLS_AND_LEAKS,
        TRACKER_WITH_MOST_APPS
    }

    enum class LabelId {
        CALLS_PER_APP_1,
        CALLS_PER_APP_2,
        CALLS_PER_APP_3,
        NEW_TRACKER_1,
        NEW_TRACKER_2,
        NEW_TRACKER_3,
        CALLS_AND_LEAKS_1,
        CALLS_AND_LEAKS_2,
        TRACKER_WITH_MOST_APPS_1
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ class TrackersAndAppsListsUseCase(
    }

    private suspend fun get5MostTrackedAppsLastMonth(): List<AppWithCount> {
        val countByApIds = statsDatabase.getCallsByAppIds(since = Period.MONTH.getPeriodStart().epochSecond)
        val countByApIds = statsDatabase.getCallsByAppIds(start = Period.MONTH.getPeriodStart(), end = Instant.now())

        val countByApps = mutableMapOf<DisplayableApp, Int>()
        countByApIds.forEach { (apId, count) ->
+332 −70
Original line number Diff line number Diff line
@@ -23,13 +23,16 @@ import foundation.e.advancedprivacy.data.repositories.AppListRepository
import foundation.e.advancedprivacy.data.repositories.ResourcesRepository
import foundation.e.advancedprivacy.data.repositories.WeeklyReportLocalRepository
import foundation.e.advancedprivacy.domain.entities.DisplayableApp
import foundation.e.advancedprivacy.domain.entities.TrackerWithCount
import foundation.e.advancedprivacy.domain.entities.WeeklyReport
import foundation.e.advancedprivacy.trackers.data.StatsDatabase
import foundation.e.advancedprivacy.trackers.data.TrackersRepository
import foundation.e.advancedprivacy.trackers.domain.entities.Tracker
import java.time.Instant
import java.time.temporal.ChronoUnit
import kotlin.Pair
import kotlin.String
import kotlin.collections.List
import kotlin.random.Random
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
@@ -47,51 +50,82 @@ class WeeklyReportUseCase(
    val showWeeklyReportNotification: SharedFlow<String> = _showWeeklyReportNotification.asSharedFlow()

    suspend fun updateWeeklyReport() = withContext(Dispatchers.IO) {
        val weeklyReport = computeWeeklyReport(Instant.now())
        weeklyReportRepository.setLastWeeklyReport(weeklyReport)
        buildLabel(weeklyReport)?.let {
            _showWeeklyReportNotification.emit(it)
        }
//        val weeklyReport = computeWeeklyReport(Instant.now())
//        weeklyReportRepository.setLastWeeklyReport(weeklyReport)
//        buildLabel(weeklyReport)?.let {
//            _showWeeklyReportNotification.emit(it)
//        }
    }

    suspend fun getLast(): WeeklyReport {
        return weeklyReportRepository.getLastWeeklyReport()
            ?: WeeklyReport(emptyList(), emptyList(), emptyList())
    suspend fun getLast(): WeeklyReport? {
        return history.lastOrNull()
        // return weeklyReportRepository.getLastWeeklyReport()
    }

    suspend fun getLastLabel(): String? {
        return buildLabel(getLast())
    }

    private fun WeeklyReport.LabelId.toStringId(): Int = when (this) {
        WeeklyReport.LabelId.CALLS_PER_APP_1 -> R.string.weeklyreport_label_calls_per_app_1
        WeeklyReport.LabelId.CALLS_PER_APP_2 -> R.string.weeklyreport_label_calls_per_app_2
        WeeklyReport.LabelId.CALLS_PER_APP_3 -> R.string.weeklyreport_label_calls_per_app_3
        WeeklyReport.LabelId.NEW_TRACKER_1 -> R.string.weeklyreport_label_new_tracker_1
        WeeklyReport.LabelId.NEW_TRACKER_2 -> R.string.weeklyreport_label_new_tracker_2
        WeeklyReport.LabelId.NEW_TRACKER_3 -> R.string.weeklyreport_label_new_tracker_3
        WeeklyReport.LabelId.CALLS_AND_LEAKS_1 -> R.string.weeklyreport_label_call_and_leaks_1
        WeeklyReport.LabelId.CALLS_AND_LEAKS_2 -> R.string.weeklyreport_label_call_and_leaks_2
        WeeklyReport.LabelId.TRACKER_WITH_MOST_APPS_1 -> R.string.weeklyreport_label_tracker_with_most_apps_1
    }

    private fun buildLabel(weeklyReport: WeeklyReport?): String? {
        if (weeklyReport == null) return null

        val (appLabel, appTracker) =
            weeklyReport.appsWithNewTrackers.firstOrNull()?.let {
                it.app.label to it.trackers.firstOrNull()?.label
            } ?: return null

        return resourcesRepository.getString(
            R.string.weeklyreport_notification_description,
            appLabel,
            appTracker,
            weeklyReport.newTrackers.firstOrNull()?.label
            weeklyReport.labelId.toStringId(),
            *weeklyReport.value.toTypedArray()
        )
    }

    suspend fun debugGenerateReportWeeksAgo(weeksAgo: Int) = withContext(Dispatchers.IO) {
        val endOfWeek = Instant.now().minus(weeksAgo.toLong() * 7, ChronoUnit.DAYS)
        val weeklyReport = computeWeeklyReport(endOfWeek)
        weeklyReportRepository.setLastWeeklyReport(weeklyReport)
        buildLabel(weeklyReport)?.let {
            _showWeeklyReportNotification.emit(it)
//        val endOfWeek = Instant.now().minus(weeksAgo.toLong() * 7, ChronoUnit.DAYS)
//        val weeklyReport = computeWeeklyReport(endOfWeek)
//        weeklyReportRepository.setLastWeeklyReport(weeklyReport)
//        buildLabel(weeklyReport)?.let {
//            _showWeeklyReportNotification.emit(it)
//        }
    }

    var history = mutableListOf<WeeklyReport>()
    suspend fun debugGenerateReporsSinceWeeksAgo(weeksAgo: Int) = withContext(Dispatchers.IO) {
        history = mutableListOf<WeeklyReport>()
        val start = System.currentTimeMillis()

        (weeksAgo downTo 0).map {
            val endOfWeek = Instant.now().minus(it.toLong() * 7, ChronoUnit.DAYS)
            val weeklyReport = chooseBestStatAndLabelToDisplay(computeStats(endOfWeek))
            history.add(weeklyReport)
        }

    private suspend fun computeWeeklyReport(endOfWeek: Instant): WeeklyReport = withContext(Dispatchers.IO) {
        Log.e("DEBUG-weekly", "buildWeeklyReport")
        Log.d("Debug-weekly", history.mapIndexed { index, report -> "week $index : ${buildLabel(report)}" }.joinToString("\n"))

        Log.d("Debug-weekly", "computed reports in : ${System.currentTimeMillis() - start}")
    }

    private data class Stats(
        val newTrackersDetected: List<Pair<String, String>>,
        val callsPerApp: Map<String, Int>,
        val callsAndLeaks: Pair<Int, Int>,
        val trackerWithMostApp: Pair<String, Int>
    )

    private suspend fun computeStats(endOfWeek: Instant): Stats {
        Log.e("DEBUG-weekly", "computeStats")

        val startOfWeek = endOfWeek.minus(7, ChronoUnit.DAYS)

        // * New trackers detected
        // **Stat**: Trackers never seen, and the apps that bring them.
        val startOfYear = endOfWeek.minus(365, ChronoUnit.DAYS)
        val trackerAppsHistoric = // mapIdsToEntities(
            statsDatabase.getDistinctTrackerAndApp(
@@ -107,66 +141,294 @@ class WeeklyReportUseCase(
            )
        // )

        val newTrackerIds = trackerAppsOfWeek.map { it.first }.toSet() - trackerAppsHistoric.map { it.first }.toSet()
        Log.d("DebugReport", "$endOfWeek : newTrackers: $newTrackerIds")
        val historicTrackers = trackerAppsHistoric.map { it.first }.toSet()
        val newTrackersDetected = trackerAppsOfWeek.filter {
            it.first !in historicTrackers
        }

        // * Leaking rate (average on the week of the number of leak attempts per hour for an app)
        // **Stat** : most calls for an app last week, then divide by 7 or 7*24
        // We don't handle correctly system app and compatibility app here.
        val callsPerApp = statsDatabase.getCallsByAppIds(startOfWeek, endOfWeek)

        // * Advanced Privacy efficiency:
        // **Stat**: call and leaks this week
        val callsAndLeaks = statsDatabase.getCallsAndLeaks(startOfWeek, endOfWeek)

        // **Stat**: tracker with most apps.
        // We don't handle correctly system app and compatibility app here.
        val trackerWithMostApp = (trackerAppsOfWeek + trackerAppsHistoric).groupBy {
            it.first
        }.maxBy { it.value.size }.let { it.key to it.value.size }

        val newTrackers = newTrackerIds.mapNotNull {
            trackersRepository.getTracker(it)
        return Stats(
            newTrackersDetected = newTrackersDetected,
            callsPerApp = callsPerApp,
            callsAndLeaks = callsAndLeaks,
            trackerWithMostApp = trackerWithMostApp
        )
    }

        val newTrackerApps = (trackerAppsOfWeek.toSet() - trackerAppsHistoric.toSet())
    data class WeeklyReportScore(
        val weeklyReport: WeeklyReport,
        val score: Long
    )

        val appsWithNewTrackers = newTrackerApps.groupBy { it.second }
            .mapValues {
                it.value.map { it.first }
            }.let {
                Log.d("DebugReport", "$endOfWeek : appsWithNewTrackers: $it")
                it
    fun computeScore(weeklyReport: WeeklyReport, history: List<WeeklyReport>): WeeklyReportScore {
        // 52 weeks, 2 years --> 100
        val baseShift = 100L
        val statShift = baseShift // 10^2
        val statLabelShift = baseShift * statShift // 10^4
        val statValueShift = baseShift * statLabelShift // 10^6
        val statLabelValueShift = baseShift * statValueShift // 10^8
        val statLabelAllValueShift = baseShift * statLabelValueShift // 10^10

        var score = Random.nextLong(99)

        val statLabelAllValueRank = history.indexOfLast {
            it.statType == weeklyReport.statType &&
                it.labelId == weeklyReport.labelId &&
                it.value == weeklyReport.value
        }
            .mapNotNull {
                appListRepository.getInternetAppByApId(it.key)?.let { app ->
                    WeeklyReport.AppWithTrackers(
                        app = app,
                        trackers = it.value.mapNotNull { trackerId -> trackersRepository.getTracker(trackerId) }
        if (statLabelAllValueRank != -1) {
            score += (statLabelAllValueRank + 1) * statLabelAllValueShift
        }

        val statLabelValueRank = history.indexOfLast {
            it.statType == weeklyReport.statType &&
                it.labelId == weeklyReport.labelId &&
                it.value.first() == weeklyReport.value.first()
        }
        if (statLabelValueRank != -1) {
            score += (statLabelValueRank + 1) * statLabelValueShift
        }

        val statValueRank = history.indexOfLast {
            it.statType == weeklyReport.statType && it.value.first() == weeklyReport.value.first()
        }
        if (statValueRank != -1) {
            score += (statValueRank + 1) * statValueShift
        }

        val statLabelRank = history.indexOfLast {
            it.statType == weeklyReport.statType && it.labelId == weeklyReport.labelId
        }
        if (statLabelRank != -1) {
            score += (statLabelRank + 1) * statLabelShift
        }

        val statRank = history.indexOfLast {
            it.statType == weeklyReport.statType
        }
        if (statRank != -1) {
            score += (statRank + 1) * statShift
        }

        return WeeklyReportScore(
            weeklyReport = weeklyReport,
            score = score
        )
    }

    private suspend fun chooseBestStatAndLabelToDisplay(weekStats: Stats): WeeklyReport {
        // prepare state, label, value  triples

        val candidates = mutableListOf<WeeklyReport>()

        // TODO, could move to "APP_WITH_MOST_CALLS"
        val (appWithMostCalls, calls) = weekStats.callsPerApp.maxBy { it.value }.let { (key, value) ->
            appListRepository.getAppById(key)?.label?.toString() to value
        }
        val hoursInWeek = 7 * 24

//        val newTrackers = newTrackerApps.map { it.first }.distinct()
//        Log.d("DebugReport", "$endOfWeek : newTrackers: $newTrackers")
        Log.d("Debug-weekly", "callsPerApp: appWithMostCalls $appWithMostCalls calls: $calls ; ${weekStats.callsPerApp}")

        val maxCallsByTrackersHistory = statsDatabase.getMaxCallsByTrackerByWeek(startOfYear, startOfWeek)
        val maxCallsByTrackerOfWeek = statsDatabase.getCallsByTrackers(startOfWeek, endOfWeek)
        if (appWithMostCalls != null && calls > hoursInWeek) {
            candidates.add(
                WeeklyReport(
                    WeeklyReport.StatType.CALLS_PER_APP,
                    WeeklyReport.LabelId.CALLS_PER_APP_1,
                    listOf(appWithMostCalls, (calls / hoursInWeek).toString())
                )
            )

        val trackersWithCallsChanges = maxCallsByTrackerOfWeek.mapNotNull { kv ->
            val trackerId: String = kv.key
            val calls = kv.value.first
            candidates.add(
                WeeklyReport(
                    WeeklyReport.StatType.CALLS_PER_APP,
                    WeeklyReport.LabelId.CALLS_PER_APP_2,
                    listOf(appWithMostCalls, (calls / hoursInWeek).toString())
                )
            )
        }

            val historyCalls = maxCallsByTrackersHistory.get(trackerId)?.first
        if (appWithMostCalls != null && calls > 7) {
            candidates.add(
                WeeklyReport(
                    WeeklyReport.StatType.CALLS_PER_APP,
                    WeeklyReport.LabelId.CALLS_PER_APP_3,
                    listOf(appWithMostCalls, (calls / 7).toString())
                )
            )
        }

            if (historyCalls != null && historyCalls > 0 && calls > (2 * historyCalls)) {
                trackersRepository.getTracker(trackerId)?.let {
                    TrackerWithCount(
                        it,
                        (100 * (calls - historyCalls)) / historyCalls
        weekStats.newTrackersDetected.map { it.second }.toSet().forEach { appId ->
            // val tracker = trackersRepository.getTracker(trackerId)
            val displayableApp = appListRepository.getAppById(appId)
            if (displayableApp != null // TODO dummy apps ?&& displayableApp != appListRepository.
            ) {
                candidates.add(
                    WeeklyReport(
                        WeeklyReport.StatType.NEW_TRACKER,
                        WeeklyReport.LabelId.NEW_TRACKER_1,
                        listOf(displayableApp.label.toString(), "")
                    )
                )
            }
            } else {
                null
        }
        }.let {
            Log.d("DebugReport", "$endOfWeek : trackersWithCallsChanges: ${it.map { it.tracker.id }}")
            it
        weekStats.newTrackersDetected.groupBy { it.first }.keys.size.takeIf { it > 0 }?.let { trackersCount ->
            candidates.add(
                WeeklyReport(
                    WeeklyReport.StatType.NEW_TRACKER,
                    WeeklyReport.LabelId.NEW_TRACKER_2,
                    listOf("", trackersCount.toString())
                )
            )
        }

        weekStats.newTrackersDetected.groupBy { it.second }.keys.size.takeIf { it > 0 }?.let { appsCount ->
            candidates.add(
                WeeklyReport(
                    WeeklyReport.StatType.NEW_TRACKER,
                    WeeklyReport.LabelId.NEW_TRACKER_3,
                    listOf("", appsCount.toString())
                )
            )
        }
        // Log.e("DEBUG-weekly", "buildWeeklyReport: appsWithNewTrackers: $appsWithNewTrackers")

        //

        if (weekStats.callsAndLeaks.first > 0) {
            val blockedRate = (
                ((weekStats.callsAndLeaks.first - weekStats.callsAndLeaks.second) * 100) / weekStats.callsAndLeaks.first
                ).toString()
            // val calls = weekStats.callsAndLeaks.first.toString()
            val leaks = weekStats.callsAndLeaks.second.toString()
            candidates.add(
                WeeklyReport(
                    WeeklyReport.StatType.CALLS_AND_LEAKS,
                    WeeklyReport.LabelId.CALLS_AND_LEAKS_1,
                    listOf(blockedRate, leaks)
                )
            )
            candidates.add(
                WeeklyReport(
                    WeeklyReport.StatType.CALLS_AND_LEAKS,
                    WeeklyReport.LabelId.CALLS_AND_LEAKS_2,
                    listOf(blockedRate, leaks)
                )
            )
        }

        val tracker = trackersRepository.getTracker(weekStats.trackerWithMostApp.first)
        val appCount = weekStats.trackerWithMostApp.second
        if (tracker != null && appCount > 1) {
            candidates.add(
                WeeklyReport(
            appsWithNewTrackers = appsWithNewTrackers,
            trackersWithCallsChanges = trackersWithCallsChanges,
            newTrackers = newTrackers
                    WeeklyReport.StatType.TRACKER_WITH_MOST_APPS,
                    WeeklyReport.LabelId.TRACKER_WITH_MOST_APPS_1,
                    listOf(tracker.label, appCount.toString())
                )
            )
        }

        // Sort candidates, regarding history.
        return candidates.map { computeScore(it, history) }.sortedBy { it.score }.let {
            val scores = it.map { "${it.score} for: ${buildLabel(it.weeklyReport) }" }
            Log.d("Debug-weekly", "Computed scores : \n${scores.joinToString("\n")}")
            it
        }
            .first().weeklyReport
    }

//    private suspend fun computeWeeklyReport(endOfWeek: Instant): WeeklyReport = withContext(Dispatchers.IO) {
//        Log.e("DEBUG-weekly", "buildWeeklyReport")
//        val startOfWeek = endOfWeek.minus(7, ChronoUnit.DAYS)
//
//        val startOfYear = endOfWeek.minus(365, ChronoUnit.DAYS)
//        val trackerAppsHistoric = // mapIdsToEntities(
//            statsDatabase.getDistinctTrackerAndApp(
//                periodStart = startOfYear,
//                periodEnd = startOfWeek
//            )
//        // )
//
//        val trackerAppsOfWeek = // mapIdsToEntities(
//            statsDatabase.getDistinctTrackerAndApp(
//                periodStart = startOfWeek,
//                periodEnd = endOfWeek
//            )
//        // )
//
//        val newTrackerIds = trackerAppsOfWeek.map { it.first }.toSet() - trackerAppsHistoric.map { it.first }.toSet()
//        Log.d("DebugReport", "$endOfWeek : newTrackers: $newTrackerIds")
//
//        val newTrackers = newTrackerIds.mapNotNull {
//            trackersRepository.getTracker(it)
//        }
//
//        val newTrackerApps = (trackerAppsOfWeek.toSet() - trackerAppsHistoric.toSet())
//
//        val appsWithNewTrackers = newTrackerApps.groupBy { it.second }
//            .mapValues {
//                it.value.map { it.first }
//            }.let {
//                Log.d("DebugReport", "$endOfWeek : appsWithNewTrackers: $it")
//                it
//            }
//            .mapNotNull {
//                appListRepository.getInternetAppByApId(it.key)?.let { app ->
//                    WeeklyReport.AppWithTrackers(
//                        app = app,
//                        trackers = it.value.mapNotNull { trackerId -> trackersRepository.getTracker(trackerId) }
//                    )
//                }
//            }
//
// //        val newTrackers = newTrackerApps.map { it.first }.distinct()
// //        Log.d("DebugReport", "$endOfWeek : newTrackers: $newTrackers")
//
//        val maxCallsByTrackersHistory = statsDatabase.getMaxCallsByTrackerByWeek(startOfYear, startOfWeek)
//        val maxCallsByTrackerOfWeek = statsDatabase.getCallsByTrackers(startOfWeek, endOfWeek)
//
//        val trackersWithCallsChanges = maxCallsByTrackerOfWeek.mapNotNull { kv ->
//            val trackerId: String = kv.key
//            val calls = kv.value.first
//
//            val historyCalls = maxCallsByTrackersHistory.get(trackerId)?.first
//
//            if (historyCalls != null && historyCalls > 0 && calls > (2 * historyCalls)) {
//                trackersRepository.getTracker(trackerId)?.let {
//                    TrackerWithCount(
//                        it,
//                        (100 * (calls - historyCalls)) / historyCalls
//                    )
//                }
//            } else {
//                null
//            }
//        }.let {
//            Log.d("DebugReport", "$endOfWeek : trackersWithCallsChanges: ${it.map { it.tracker.id }}")
//            it
//        }
//        // Log.e("DEBUG-weekly", "buildWeeklyReport: appsWithNewTrackers: $appsWithNewTrackers")
//
//        WeeklyReport(
//            appsWithNewTrackers = appsWithNewTrackers,
//            trackersWithCallsChanges = trackersWithCallsChanges,
//            newTrackers = newTrackers
//        )
//    }

    // TODO: Z; duplicate from TrackersAndAppsListsUsecase
    private suspend fun mapIdsToEntities(trackersAndAppsIds: List<Pair<String, String>>): List<Pair<Tracker, DisplayableApp>> {
        return trackersAndAppsIds.mapNotNull { (trackerId, apId) ->
+77 −89

File changed.

Preview size limit exceeded, changes collapsed.

+5 −11
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package foundation.e.advancedprivacy.features.weeklyreport

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import foundation.e.advancedprivacy.domain.entities.WeeklyReport
import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,13 +31,7 @@ class WeeklyReportViewModel(
    private val weeklyReportUseCase: WeeklyReportUseCase
) : ViewModel() {
    private val _state = MutableStateFlow(
        WeeklyReportUiState(
            WeeklyReport(
                appsWithNewTrackers = emptyList(),
                trackersWithCallsChanges = emptyList(),
                newTrackers = emptyList()
            )
        )
        WeeklyReportUiState(weeklyReport = "")
    )

    val state = _state.asStateFlow()
@@ -51,7 +44,7 @@ class WeeklyReportViewModel(

    suspend fun doOnStartedState() = withContext(Dispatchers.IO) {
        _state.update {
            WeeklyReportUiState(weeklyReportUseCase.getLast())
            WeeklyReportUiState(weeklyReportUseCase.getLastLabel() ?: "NONE")
        }
    }

@@ -61,11 +54,12 @@ class WeeklyReportViewModel(
    }

    fun onClickDebugGenerateReport(weekAgo: Int) = viewModelScope.launch(Dispatchers.IO) {
        weeklyReportUseCase.debugGenerateReportWeeksAgo(weekAgo)
//        weeklyReportUseCase.debugGenerateReportWeeksAgo(weekAgo)
        weeklyReportUseCase.debugGenerateReporsSinceWeeksAgo(weekAgo)
        doOnStartedState()
    }
}

data class WeeklyReportUiState(
    val weeklyReport: WeeklyReport
    val weeklyReport: String
)
Loading