Loading app/src/main/AndroidManifest.xml +11 −1 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2022 ECORP, 2022 MURENA SAS Copyright (C) 2022 - 2025 MURENA SAS Copyright (C) 2022 ECORP 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 Loading Loading @@ -57,6 +58,15 @@ android:enabled="true" android:exported="true" /> <provider android:name=".externalinterfaces.contentproviders.ScreenshotsProvider" android:authorities="foundation.e.advancedprivacy.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider_paths" /> </provider> <receiver android:name="foundation.e.advancedprivacy.common.BootCompletedReceiver" Loading app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt +24 −14 Original line number Diff line number Diff line Loading @@ -44,28 +44,38 @@ class WeeklyReportUseCase( ) { private val _showWeeklyReportNotification = MutableSharedFlow<String>() 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) } } suspend fun getLast(): WeeklyReport { return weeklyReportRepository.getLastWeeklyReport() ?: WeeklyReport(emptyList(), emptyList(), emptyList()) } suspend fun getLastLabel(): String? { return buildLabel(getLast()) } 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 } ?: (null to null) } ?: return null _showWeeklyReportNotification.emit( resourcesRepository.getString( return resourcesRepository.getString( R.string.weeklyreport_notification_description, appLabel, appTracker, weeklyReport.newTrackers.firstOrNull()?.label ) ) } suspend fun getLastWeeklyReport(): WeeklyReport { return weeklyReportRepository.getLastWeeklyReport() ?: WeeklyReport(emptyList(), emptyList(), emptyList()) } suspend fun computeWeeklyReport(endOfWeek: Instant): WeeklyReport = withContext(Dispatchers.IO) { Loading app/src/main/java/foundation/e/advancedprivacy/externalinterfaces/contentproviders/ScreenshotsProvider.kt 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.advancedprivacy.externalinterfaces.contentproviders import android.content.Context import android.content.Intent import android.graphics.Bitmap import androidx.core.content.FileProvider import java.io.File // Subclass (empty) of FileProvider, as advicsed in documentation // https://developer.android.com/reference/androidx/core/content/FileProvider : // "It is possible to use FileProvider directly instead of extending it. However, this is // not reliable and will causes crashes on some devices." class ScreenshotsProvider : FileProvider() { companion object { const val AUTHORITY = "foundation.e.advancedprivacy.fileprovider" private const val PATH_WEEKLYREPORTS = "weeklyreports" fun getSendIntentForDrawable(context: Context, bmp: Bitmap, message: String): Intent { val baseDir = File(context.cacheDir, PATH_WEEKLYREPORTS) baseDir.mkdirs() val screenshotOutputFile = File(baseDir, "lastreport.png") screenshotOutputFile.delete() val screenshotOutputStream = screenshotOutputFile.outputStream() bmp.compress(Bitmap.CompressFormat.PNG, 100, screenshotOutputStream) screenshotOutputStream.close() val contentUri = getUriForFile(context, AUTHORITY, screenshotOutputFile) return Intent().apply { action = Intent.ACTION_SEND flags = Intent.FLAG_GRANT_READ_URI_PERMISSION setDataAndType(contentUri, "image/png") putExtra(Intent.EXTRA_TEXT, message) putExtra(Intent.EXTRA_STREAM, contentUri) } } } } app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +8 −1 Original line number Diff line number Diff line /* * Copyright (C) 2023-2024 MURENA SAS * Copyright (C) 2023 - 2025 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify Loading Loading @@ -193,6 +193,13 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } private fun render(state: DashboardState) { binding.weeklyreportLabel.text = state.weeklyReportLabel // (state.weeklyReportLabel != null).let { // binding.weeklyreportLabel.isVisible = it // binding.viewWeeklyreport.isVisible = it // } binding.dataBlockedTrackers.number.text = numberFormatter.format(state.blockedCallsCount) binding.dataApps.number.text = state.appsWithCallsCount.toString() Loading app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt +3 −2 Original line number Diff line number Diff line /* * Copyright (C) 2024 MURENA SAS * Copyright (C) 2024 - 2025 MURENA SAS * Copyright (C) 2022 E FOUNDATION * * This program is free software: you can redistribute it and/or modify Loading Loading @@ -30,5 +30,6 @@ data class DashboardState( val blockedCallsCount: Int = 0, val appsWithCallsCount: Int = 0, val shameApps: List<AppWithCount> = emptyList(), val shameTrackers: List<TrackerWithCount> = emptyList() val shameTrackers: List<TrackerWithCount> = emptyList(), val weeklyReportLabel: String? = null ) Loading
app/src/main/AndroidManifest.xml +11 −1 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2022 ECORP, 2022 MURENA SAS Copyright (C) 2022 - 2025 MURENA SAS Copyright (C) 2022 ECORP 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 Loading Loading @@ -57,6 +58,15 @@ android:enabled="true" android:exported="true" /> <provider android:name=".externalinterfaces.contentproviders.ScreenshotsProvider" android:authorities="foundation.e.advancedprivacy.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider_paths" /> </provider> <receiver android:name="foundation.e.advancedprivacy.common.BootCompletedReceiver" Loading
app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt +24 −14 Original line number Diff line number Diff line Loading @@ -44,28 +44,38 @@ class WeeklyReportUseCase( ) { private val _showWeeklyReportNotification = MutableSharedFlow<String>() 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) } } suspend fun getLast(): WeeklyReport { return weeklyReportRepository.getLastWeeklyReport() ?: WeeklyReport(emptyList(), emptyList(), emptyList()) } suspend fun getLastLabel(): String? { return buildLabel(getLast()) } 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 } ?: (null to null) } ?: return null _showWeeklyReportNotification.emit( resourcesRepository.getString( return resourcesRepository.getString( R.string.weeklyreport_notification_description, appLabel, appTracker, weeklyReport.newTrackers.firstOrNull()?.label ) ) } suspend fun getLastWeeklyReport(): WeeklyReport { return weeklyReportRepository.getLastWeeklyReport() ?: WeeklyReport(emptyList(), emptyList(), emptyList()) } suspend fun computeWeeklyReport(endOfWeek: Instant): WeeklyReport = withContext(Dispatchers.IO) { Loading
app/src/main/java/foundation/e/advancedprivacy/externalinterfaces/contentproviders/ScreenshotsProvider.kt 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 MURENA SAS * * 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 <https://www.gnu.org/licenses/>. */ package foundation.e.advancedprivacy.externalinterfaces.contentproviders import android.content.Context import android.content.Intent import android.graphics.Bitmap import androidx.core.content.FileProvider import java.io.File // Subclass (empty) of FileProvider, as advicsed in documentation // https://developer.android.com/reference/androidx/core/content/FileProvider : // "It is possible to use FileProvider directly instead of extending it. However, this is // not reliable and will causes crashes on some devices." class ScreenshotsProvider : FileProvider() { companion object { const val AUTHORITY = "foundation.e.advancedprivacy.fileprovider" private const val PATH_WEEKLYREPORTS = "weeklyreports" fun getSendIntentForDrawable(context: Context, bmp: Bitmap, message: String): Intent { val baseDir = File(context.cacheDir, PATH_WEEKLYREPORTS) baseDir.mkdirs() val screenshotOutputFile = File(baseDir, "lastreport.png") screenshotOutputFile.delete() val screenshotOutputStream = screenshotOutputFile.outputStream() bmp.compress(Bitmap.CompressFormat.PNG, 100, screenshotOutputStream) screenshotOutputStream.close() val contentUri = getUriForFile(context, AUTHORITY, screenshotOutputFile) return Intent().apply { action = Intent.ACTION_SEND flags = Intent.FLAG_GRANT_READ_URI_PERMISSION setDataAndType(contentUri, "image/png") putExtra(Intent.EXTRA_TEXT, message) putExtra(Intent.EXTRA_STREAM, contentUri) } } } }
app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt +8 −1 Original line number Diff line number Diff line /* * Copyright (C) 2023-2024 MURENA SAS * Copyright (C) 2023 - 2025 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify Loading Loading @@ -193,6 +193,13 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) { } private fun render(state: DashboardState) { binding.weeklyreportLabel.text = state.weeklyReportLabel // (state.weeklyReportLabel != null).let { // binding.weeklyreportLabel.isVisible = it // binding.viewWeeklyreport.isVisible = it // } binding.dataBlockedTrackers.number.text = numberFormatter.format(state.blockedCallsCount) binding.dataApps.number.text = state.appsWithCallsCount.toString() Loading
app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt +3 −2 Original line number Diff line number Diff line /* * Copyright (C) 2024 MURENA SAS * Copyright (C) 2024 - 2025 MURENA SAS * Copyright (C) 2022 E FOUNDATION * * This program is free software: you can redistribute it and/or modify Loading Loading @@ -30,5 +30,6 @@ data class DashboardState( val blockedCallsCount: Int = 0, val appsWithCallsCount: Int = 0, val shameApps: List<AppWithCount> = emptyList(), val shameTrackers: List<TrackerWithCount> = emptyList() val shameTrackers: List<TrackerWithCount> = emptyList(), val weeklyReportLabel: String? = null )