/* * 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 * 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.advancedprivacy.domain.usecases import android.content.res.Resources import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.common.throttleFirst import foundation.e.advancedprivacy.data.repositories.AppListsRepository import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import foundation.e.advancedprivacy.domain.entities.TrackersPeriodicStatistics import foundation.e.advancedprivacy.trackers.data.StatsDatabase import foundation.e.advancedprivacy.trackers.data.TrackersRepository import foundation.e.advancedprivacy.trackers.data.WhitelistRepository import foundation.e.advancedprivacy.trackers.domain.entities.Tracker import foundation.e.advancedprivacy.trackers.domain.usecases.StatisticsUseCase import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds class TrackersStatisticsUseCase( private val statisticsUseCase: StatisticsUseCase, private val whitelistRepository: WhitelistRepository, private val trackersRepository: TrackersRepository, private val appListsRepository: AppListsRepository, private val statsDatabase: StatsDatabase, private val resources: Resources ) { fun initAppList() { appListsRepository.apps() } @OptIn(FlowPreview::class) fun listenUpdates(debounce: Duration = 1.seconds) = statsDatabase.newDataAvailable .throttleFirst(windowDuration = debounce) .onStart { emit(Unit) } fun getDayStatistics(): Pair { return TrackersPeriodicStatistics( callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS), periods = buildDayLabels(), trackersCount = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS), graduations = buildDayGraduations(), ) to statisticsUseCase.getContactedTrackersCount() } fun getNonBlockedTrackersCount(): Flow { return if (whitelistRepository.isBlockingEnabled) appListsRepository.allApps().map { apps -> val whiteListedTrackers = mutableSetOf() val whiteListedApps = whitelistRepository.getWhiteListedApp() apps.forEach { app -> if (app in whiteListedApps) { whiteListedTrackers.addAll(statisticsUseCase.getTrackers(listOf(app))) } else { whiteListedTrackers.addAll(getWhiteList(app)) } } whiteListedTrackers.size } else flowOf(statisticsUseCase.getContactedTrackersCount()) } fun getMostLeakedApp(): ApplicationDescription? { return statisticsUseCase.getMostLeakedApp(24, ChronoUnit.HOURS) } fun getDayTrackersCalls() = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS) fun getDayTrackersCount() = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) private fun buildDayGraduations(): List { val formatter = DateTimeFormatter.ofPattern( resources.getString(R.string.trackers_graph_hours_period_format) ) val periods = mutableListOf() var end = ZonedDateTime.now() for (i in 1..24) { val start = end.truncatedTo(ChronoUnit.HOURS) periods.add(if (start.hour % 6 == 0) formatter.format(start) else null) end = start.minus(1, ChronoUnit.MINUTES) } return periods.reversed() } private fun buildDayLabels(): List { val formatter = DateTimeFormatter.ofPattern( resources.getString(R.string.trackers_graph_hours_period_format) ) val periods = mutableListOf() var end = ZonedDateTime.now() for (i in 1..24) { val start = end.truncatedTo(ChronoUnit.HOURS) periods.add("${formatter.format(start)} - ${formatter.format(end)}") end = start.minus(1, ChronoUnit.MINUTES) } return periods.reversed() } private fun buildMonthLabels(): List { val formater = DateTimeFormatter.ofPattern( resources.getString(R.string.trackers_graph_days_period_format) ) val periods = mutableListOf() var day = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS) for (i in 1..30) { periods.add(formater.format(day)) day = day.minus(1, ChronoUnit.DAYS) } return periods.reversed() } private fun buildYearLabels(): List { val formater = DateTimeFormatter.ofPattern( resources.getString(R.string.trackers_graph_months_period_format) ) val periods = mutableListOf() var month = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1) for (i in 1..12) { periods.add(formater.format(month)) month = month.minus(1, ChronoUnit.MONTHS) } return periods.reversed() } fun getDayMonthYearStatistics(): Triple { return Triple( TrackersPeriodicStatistics( callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS), periods = buildDayLabels(), trackersCount = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS) ), TrackersPeriodicStatistics( callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(30, ChronoUnit.DAYS), periods = buildMonthLabels(), trackersCount = statisticsUseCase.getActiveTrackersByPeriod(30, ChronoUnit.DAYS) ), TrackersPeriodicStatistics( callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(12, ChronoUnit.MONTHS), periods = buildYearLabels(), trackersCount = statisticsUseCase.getActiveTrackersByPeriod(12, ChronoUnit.MONTHS) ) ) } suspend fun isWhiteListEmpty(app: ApplicationDescription): Boolean { return appListsRepository.mapReduceForHiddenApps( app = app, map = { appDesc: ApplicationDescription -> getWhiteList(appDesc).isEmpty() }, reduce = { areEmpty -> areEmpty.all { it } } ) } suspend fun getCalls(app: ApplicationDescription): Pair { return appListsRepository.mapReduceForHiddenApps( app = app, map = { statisticsUseCase.getCalls(it, 24, ChronoUnit.HOURS) }, reduce = { zip -> zip.unzip().let { (blocked, leaked) -> blocked.sum() to leaked.sum() } } ) } private fun getWhiteList(app: ApplicationDescription): List { return whitelistRepository.getWhiteListForApp(app).mapNotNull { trackersRepository.getTracker(it) } } }