diff --git a/app/src/main/java/foundation/e/advancedprivacy/NotificationsPresenter.kt b/app/src/main/java/foundation/e/advancedprivacy/NotificationsPresenter.kt index 9246f5c16f871bda05cf4e59eef1d293102d5d96..449176ff706b6f5034d33c87d35fec893e70158d 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/NotificationsPresenter.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/NotificationsPresenter.kt @@ -215,7 +215,6 @@ class NotificationsPresenter( .setColorized(true) .setSmallIcon(R.drawable.ic_advanced_privacy_for_notification) - notificationBuilder.setSmallIcon(R.drawable.ic_shield_alert) notificationBuilder.setAutoCancel(true) val goToTrackersIntent = MainActivity.deepLinkBuilder(context) diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt index c61b7bad92090bd640ad5638e62dfa6ee1e59a64..26f16e57aac9b24b7c88fea269e0a3ba13ca9c04 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt @@ -238,17 +238,27 @@ class WeeklyReportUseCase( } } - private fun addCallAndLeaksCandidates(candidates: MutableList, endOfWeek: Instant) { - val blockedRate = Random.nextInt(100).toString() - val leaks = Random.nextInt(1000).toString() + private suspend fun addCallAndLeaksCandidates(candidates: MutableList, endOfWeek: Instant) { + val startOfWeek = endOfWeek.minus(7, ChronoUnit.DAYS) + val (calls, leaks) = statsDatabase.getCallsAndLeaks(startOfWeek, endOfWeek) + + if (calls == 0) { + return + } + + val blockedLeaks = calls - leaks + val blockedRate = blockedLeaks * 100 / calls + + val blockedRateStr = blockedRate.toString() + val blockedLeaksStr = blockedLeaks.toString() candidates.add( WeeklyReport( endOfWeek, WeeklyReport.StatType.CALLS_AND_LEAKS, WeeklyReport.LabelId.CALLS_AND_LEAKS_1, - blockedRate, - listOf(leaks) + blockedRateStr, + listOf(blockedLeaksStr) ) ) candidates.add( @@ -256,8 +266,8 @@ class WeeklyReportUseCase( endOfWeek, WeeklyReport.StatType.CALLS_AND_LEAKS, WeeklyReport.LabelId.CALLS_AND_LEAKS_2, - blockedRate, - listOf(leaks) + blockedRateStr, + listOf(blockedLeaksStr) ) ) } diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/weeklyreport/CallsAndLeaksViewFactory.kt b/app/src/main/java/foundation/e/advancedprivacy/features/weeklyreport/CallsAndLeaksViewFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..005b49ad46a58b3abe8594221722395073b45c67 --- /dev/null +++ b/app/src/main/java/foundation/e/advancedprivacy/features/weeklyreport/CallsAndLeaksViewFactory.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2025 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.features.weeklyreport + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import foundation.e.advancedprivacy.R +import foundation.e.advancedprivacy.databinding.WeeklyreportItemCallsAndLeaksBinding +import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableReport +import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReport + +class CallsAndLeaksViewFactory(private val context: Context) { + fun getShareTitle(): CharSequence { + return context.getString(R.string.weeklyreport_call_and_leaks_share_title) + } + + fun createView(report: DisplayableReport, inflater: LayoutInflater, viewGroup: ViewGroup): View { + val binding = WeeklyreportItemCallsAndLeaksBinding.inflate(inflater, viewGroup, false) + + val labelId = report.report.labelId + binding.title.text = getTitle(report, false) + binding.primaryValue.text = context.getString(R.string.weeklyreport_label_call_and_leaks_primaryvalue, report.report.primaryValue) + binding.description1.text = when (labelId) { + WeeklyReport.LabelId.CALLS_AND_LEAKS_1 -> + context.getString(R.string.weeklyreport_label_call_and_leaks_1_description_1) + WeeklyReport.LabelId.CALLS_AND_LEAKS_2 -> + context.getString(R.string.weeklyreport_label_call_and_leaks_2_description_1, report.report.primaryValue) + else -> "" + } + + binding.secondaryValue.text = context.getString( + R.string.weeklyreport_label_call_and_leaks_secondaryvalue, + report.report.secondaryValues.firstOrNull() + ) + + binding.description2.setText( + when (labelId) { + WeeklyReport.LabelId.CALLS_AND_LEAKS_1 -> R.string.weeklyreport_label_call_and_leaks_1_description_2 + WeeklyReport.LabelId.CALLS_AND_LEAKS_2 -> R.string.weeklyreport_label_call_and_leaks_2_description_2 + else -> R.string.empty + } + ) + + return binding.root + } + + fun createViewForSharing(report: DisplayableReport, inflater: LayoutInflater, viewGroup: ViewGroup): View { + val binding = WeeklyreportItemCallsAndLeaksBinding.inflate(inflater, viewGroup, false) + + val labelId = report.report.labelId + binding.title.text = getTitle(report, true) + binding.primaryValue.text = context.getString(R.string.weeklyreport_label_call_and_leaks_primaryvalue, report.report.primaryValue) + binding.description1.text = when (labelId) { + WeeklyReport.LabelId.CALLS_AND_LEAKS_1 -> + context.getString(R.string.weeklyreport_label_call_and_leaks_1_description_1_sharing) + WeeklyReport.LabelId.CALLS_AND_LEAKS_2 -> + context.getString(R.string.weeklyreport_label_call_and_leaks_2_description_sharing, report.report.primaryValue) + else -> "" + } + + binding.secondaryValue.text = context.getString( + R.string.weeklyreport_label_call_and_leaks_secondaryvalue_sharing, + report.report.secondaryValues.firstOrNull() + ) + + binding.description2.setText( + when (labelId) { + WeeklyReport.LabelId.CALLS_AND_LEAKS_1 -> R.string.weeklyreport_label_call_and_leaks_1_description_2 + WeeklyReport.LabelId.CALLS_AND_LEAKS_2 -> R.string.weeklyreport_label_call_and_leaks_2_description_2 + else -> R.string.empty + } + ) + + with(binding.secondaryValue) { + backgroundTintList = + ColorStateList.valueOf(ContextCompat.getColor(context, R.color.share_blue_highlight_number)) + setTextColor(ContextCompat.getColor(context, R.color.white)) + + if (labelId == WeeklyReport.LabelId.CALLS_AND_LEAKS_2) { + binding.description2.isVisible = false + } + } + + return binding.root + } + + fun getTitle(report: DisplayableReport, forSharing: Boolean = false): CharSequence { + val labelId = report.report.labelId + return context.getString( + when { + labelId == WeeklyReport.LabelId.CALLS_AND_LEAKS_1 -> R.string.weeklyreport_label_call_and_leaks_1_title + labelId == WeeklyReport.LabelId.CALLS_AND_LEAKS_2 && forSharing -> + R.string.weeklyreport_label_call_and_leaks_2_title_sharing + labelId == WeeklyReport.LabelId.CALLS_AND_LEAKS_2 -> + R.string.weeklyreport_label_call_and_leaks_2_title + else -> + R.string.empty + } + ) + } + + fun getDescriptionForNotification(report: DisplayableReport): String { + val descriptionId = when (report.report.labelId) { + WeeklyReport.LabelId.CALLS_AND_LEAKS_1 -> R.string.weeklyreport_label_call_and_leaks_1_description_notification + WeeklyReport.LabelId.CALLS_AND_LEAKS_2 -> R.string.weeklyreport_label_call_and_leaks_2_description_notification + else -> + R.string.empty + } + + return context.getString(descriptionId, report.report.primaryValue, *report.report.secondaryValues.toTypedArray()) + } + + fun getIconForNotification(): Drawable? = null +} diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/weeklyreport/WeeklyReportViewFactory.kt b/app/src/main/java/foundation/e/advancedprivacy/features/weeklyreport/WeeklyReportViewFactory.kt index 25fe0d850a77a8e5452ab2b962cbd929322e2d27..32f01a5dd3e2cc6c0b2b50ff2d3e9c95006849ea 100644 --- a/app/src/main/java/foundation/e/advancedprivacy/features/weeklyreport/WeeklyReportViewFactory.kt +++ b/app/src/main/java/foundation/e/advancedprivacy/features/weeklyreport/WeeklyReportViewFactory.kt @@ -36,6 +36,7 @@ import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableRepo import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReport class WeeklyReportViewFactory(private val context: Context) { + val callsAndLeaksViewFactory: CallsAndLeaksViewFactory by lazy { CallsAndLeaksViewFactory(context) } val newTrackersViewFactory: NewTrackersViewFactory by lazy { NewTrackersViewFactory(context) } val trackerWithMostAppsViewFactory: TrackerWithMostAppsViewFactory by lazy { TrackerWithMostAppsViewFactory(context) } val callsPerAppViewFactory: CallsPerAppViewFactory by lazy { CallsPerAppViewFactory(context) } @@ -48,6 +49,8 @@ class WeeklyReportViewFactory(private val context: Context) { trackerWithMostAppsViewFactory.getShareTitle() WeeklyReport.StatType.CALLS_PER_APP -> callsPerAppViewFactory.getShareTitle() + WeeklyReport.StatType.CALLS_AND_LEAKS -> + callsAndLeaksViewFactory.getShareTitle() else -> context.getString(R.string.weeklyreport_share_title_base) } } @@ -60,6 +63,9 @@ class WeeklyReportViewFactory(private val context: Context) { trackerWithMostAppsViewFactory.createView(report, inflater, viewGroup, onClick) WeeklyReport.StatType.CALLS_PER_APP -> callsPerAppViewFactory.createView(report, inflater, viewGroup) + WeeklyReport.StatType.CALLS_AND_LEAKS -> + callsAndLeaksViewFactory.createView(report, inflater, viewGroup) + else -> null } ?: createDefaultView(report, inflater, viewGroup) } @@ -116,7 +122,8 @@ class WeeklyReportViewFactory(private val context: Context) { trackerWithMostAppsViewFactory.createViewForSharing(report, inflater, viewGroup) WeeklyReport.StatType.CALLS_PER_APP -> callsPerAppViewFactory.createViewForSharing(report, inflater, viewGroup) - + WeeklyReport.StatType.CALLS_AND_LEAKS -> + callsAndLeaksViewFactory.createViewForSharing(report, inflater, viewGroup) else -> null } ?: createDefaultView(report, inflater, viewGroup) } @@ -139,6 +146,8 @@ class WeeklyReportViewFactory(private val context: Context) { WeeklyReport.StatType.CALLS_PER_APP -> callsPerAppViewFactory.getTitle(report) + WeeklyReport.StatType.CALLS_AND_LEAKS -> + callsAndLeaksViewFactory.getTitle(report) else -> report.report.labelId.name } } @@ -151,6 +160,8 @@ class WeeklyReportViewFactory(private val context: Context) { trackerWithMostAppsViewFactory.getDescriptionForNotification(report) WeeklyReport.StatType.CALLS_PER_APP -> callsPerAppViewFactory.getDescriptionForNotification(report) + WeeklyReport.StatType.CALLS_AND_LEAKS -> + callsAndLeaksViewFactory.getDescriptionForNotification(report) else -> getDefaultDescription(report).toString() } @@ -174,6 +185,8 @@ class WeeklyReportViewFactory(private val context: Context) { trackerWithMostAppsViewFactory.getIconForNotification() WeeklyReport.StatType.CALLS_PER_APP -> callsPerAppViewFactory.getIconForNotification(report) + WeeklyReport.StatType.CALLS_AND_LEAKS -> + callsAndLeaksViewFactory.getIconForNotification() else -> null } } diff --git a/app/src/main/res/layout/weeklyreport_item_calls_and_leaks.xml b/app/src/main/res/layout/weeklyreport_item_calls_and_leaks.xml new file mode 100644 index 0000000000000000000000000000000000000000..dafe3a63ae5cfd25b69fd43ee0d0bbe2cc3fe740 --- /dev/null +++ b/app/src/main/res/layout/weeklyreport_item_calls_and_leaks.xml @@ -0,0 +1,96 @@ + + + + > + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3ca581e1545c67a1bc24d2772ac8060766151d86..76c80c6a2c553e7e49640a5481f72ed600580f72 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -203,4 +203,23 @@ %s hat Daten gesendet mal pro Tag in dieser Woche. mal pro Tag in dieser Woche auf meinem Smartphone. + + Beeindruckend, oder? + %s%% + %s gesperrte Datenlecks + %s Datenlecks + + Blockierte Tracker: + %1$s%% der Tracking-Versuche neutralisiert, d.h. %2$s gesperrte Datenlecks nur diese Woche! + der Tracking-Versuche neutralisiert + aller Tracking-Versuche neutralisiert + nur diese Woche! + + Ihr Verteidigungswert: + /e/OS hat %1$s%% der Tracking-Versuche blockiert, d.h. %2$s gesperrte Datenlecks nur diese Woche! + /e/OS hat %s%% der Tracking-Versuche blockiert + nur diese Woche! + Datenschutz-Verteidigungswert: + /e/OS hat %s%% aller Tracking-Versuche auf meinem Smartphone diese Woche blockiert. + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d040f70f9072e64a9bfe7aceeccc138753146ddb..439ae571505d783b43ec101dc519ae599f6fdfc6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -160,6 +160,8 @@ Informe semanal: https://e.foundation/wp-content/uploads/2025/01/White_Paper_-_Privacy_-_ES.pdf + ¿Impresionado? + ¡Corre la voz! ¡Nuevo espía en la ciudad! Esta semana @@ -186,7 +188,6 @@ Informe Semanal Una notificación semanal que muestra un resumen de los rastreadores, destacando cualquier cambio y actualización en ellos. - ¿Impresionado? Principal infractor de privacidad: %s ha realizado intentos de seguimiento por hora esta semana. @@ -202,4 +203,23 @@ veces por día esta semana. veces por día esta semana en mi smartphone. + + Impresionante, ¿verdad? + %s%% + %s fugas bloqueadas + %s fugas + + Rastreadores bloqueados: + %1$s%% de los intentos de rastreo neutralizados, es decir, %2$s fugas bloqueadas solo esta semana! + de los intentos de rastreo neutralizados + de todos los intentos de rastreo neutralizados + ¡solo esta semana! + + Tu puntuación de defensa: + /e/OS bloqueó %1$s%% de los intentos de rastreo, es decir, %2$s fugas bloqueadas solo esta semana! + /e/OS bloqueó %s%% de los intentos de rastreo + ¡solo esta semana! + Puntuación de defensa de privacidad: + /e/OS bloqueó %s%% de todos los intentos de rastreo en mi smartphone esta semana. + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 817a2c5d900ab2cab9466ea7907a60f8e3a5568e..2972162f90c433b23a15b3ea4fc868be61231d54 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -202,4 +202,23 @@ fois par jour cette semaine. fois par jour cette semaine sur mon smartphone. + + Impressionnant, non ? + %s%% + %s fuites bloquées + %s fuites + + Pisteurs bloqués : + %1$s%% des tentatives de pistage neutralisées, c\'est-à-dire %2$s fuites bloquées rien que cette semaine ! + des tentatives de pistage neutralisées + de toutes les tentatives de pistage neutralisées + rien que cette semaine ! + + Votre score de défense : + /e/OS a bloqué %1$s%% des tentatives de pistage, c\'est-à-dire %2$s fuites bloquées rien que cette semaine ! + /e/OS a bloqué %s%% des tentatives de pistage + rien que cette semaine ! + Score de défense de la vie privée : + /e/OS a bloqué %s%% de toutes les tentatives de pistage sur mon smartphone cette semaine. + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 8bed1dfd74ccf1b6584ca2a33118f9d8403e8747..316eae7f7bbe44cbe028c4fca33341c8ce960888 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -205,4 +205,23 @@ volte al giorno questa settimana. volte al giorno questa settimana sul mio smartphone. + + Impressionante, vero? + %s%% + %s fughe di dati bloccate + %s fughe di dati + + Tracker bloccati: + %1$s%% dei tentativi di tracciamento neutralizzati, ovvero %2$s fughe di dati bloccate solo questa settimana! + dei tentativi di tracciamento neutralizzati + di tutti i tentativi di tracciamento neutralizzati + solo questa settimana! + + Il tuo punteggio di difesa: + /e/OS ha bloccato %1$s%% dei tentativi di tracciamento, ovvero %2$s fughe di dati bloccate solo questa settimana! + /e/OS ha bloccato %s%% dei tentativi di tracciamento + solo questa settimana! + Punteggio di difesa della privacy: + /e/OS ha bloccato %s%% di tutti i tentativi di tracciamento sul mio smartphone questa settimana. + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 989ca8fbc2d57beadaf18ab72647abb18ba2fce1..22e0152814f93e92c3e6dbb66c64a2adbfd9c086 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -233,6 +233,26 @@ times per day this week. times per day this week on my smartphone. + Awesome, no? + %s%% + %s blocked leaks + %s leaks + + + Trackers Blocked: + %1$s%% of tracking attempts neutralized, i.e %2$s blocked leaks just for this week! + of tracking attempts neutralized + of all tracking attempts neutralized + just this week! + + Your Defense Score: + /e/OS blocked %1$s%% of tracking attempts, i.e %2$s blocked leaks just this week! + /e/OS blocked %s%% of tracking attempts + just this week! + Privacy Defense Score: + /e/OS blocked %s%% of all tracking attempts on my smartphone this week. + + @string/app_name diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt index 7c6222c53e512a4fe2e96e8b7e98b1987aef05e3..7ee2cf7a9c887c0b45e1b0cd62ab4c47bee56863 100644 --- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt +++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt @@ -423,6 +423,30 @@ class StatsDatabase( } } + suspend fun getCallsAndLeaks(start: Instant, end: Instant): Pair = withContext(Dispatchers.IO) { + synchronized(lock) { + val db = readableDatabase + val selection = "$COLUMN_NAME_TIMESTAMP >= ? AND $COLUMN_NAME_TIMESTAMP <= ?" + val selectionArg = arrayOf("" + start.epochSecond, "" + end.epochSecond) + val projection = + "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", + selectionArg + ) + var calls: Pair = 0 to 0 + if (cursor.moveToNext()) { + val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM) + val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM) + calls = contacted to contacted - blocked + } + cursor.close() + db.close() + calls + } + } + suspend fun logAccess(trackerId: String?, appId: String, blocked: Boolean) = withContext( Dispatchers.IO ) {