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

Commit 98ff99e0 authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

Merge branch '3399-fix_freezing' into 'main'

fix:3399: make trackerService a foregound service to avoid freezing.

See merge request !210
parents 739e384a d48f28d4
Loading
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -76,7 +76,6 @@ class AdvancedPrivacyApplication : Application() {
        get<IpScramblingStateUseCase>(IpScramblingStateUseCase::class.java)
        get<TrackersStateUseCase>(TrackersStateUseCase::class.java)
        get<FakeLocationStateUseCase>(FakeLocationStateUseCase::class.java)
        get<VpnSupervisorUseCase>(VpnSupervisorUseCase::class.java).listenSettings()
    }

    private suspend fun initBackgroundSingletons() = withContext(Dispatchers.IO) {
@@ -97,6 +96,10 @@ class AdvancedPrivacyApplication : Application() {
        )

        get<NotificationsPresenter>(NotificationsPresenter::class.java).startListening()
        // Notifications channels have to be created before calling
        // VpnSupervisorUseCase::listenSettings (which runs foreground notification)
        get<VpnSupervisorUseCase>(VpnSupervisorUseCase::class.java).listenSettings()

        get<WeeklyReportUseCase>(WeeklyReportUseCase::class.java).listen()
    }
}
+14 −0
Original line number Diff line number Diff line
@@ -27,7 +27,9 @@ import foundation.e.advancedprivacy.data.repositories.LocalStateRepositoryImpl
import foundation.e.advancedprivacy.data.repositories.ResourcesRepository
import foundation.e.advancedprivacy.data.repositories.WeeklyReportLocalRepository
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.domain.entities.CHANNEL_TRACKER_FLAG
import foundation.e.advancedprivacy.domain.entities.DisplayableApp
import foundation.e.advancedprivacy.domain.entities.NotificationContent
import foundation.e.advancedprivacy.domain.entities.ProfileType
import foundation.e.advancedprivacy.domain.repositories.LocalStateRepository
import foundation.e.advancedprivacy.domain.usecases.AppTrackersUseCase
@@ -237,10 +239,22 @@ val appModule = module {
        NotificationsPresenter(
            context = androidContext(),
            getQuickPrivacyStateUseCase = get(),
            trackersStatisticsUseCase = get(),
            permissionsPrivacyModule = get(),
            weeklyReportUseCase = get(),
            weeklyReportViewFactory = get(),
            notificationTrackerFlag = get(named("notificationTrackerFlag")),
            appScope = get()
        )
    }

    single<NotificationContent>(named("notificationTrackerFlag")) {
        NotificationContent(
            channelId = CHANNEL_TRACKER_FLAG,
            icon = R.drawable.ic_advanced_privacy_for_notification,
            title = R.string.notifications_tracker_service_running_title,
            description = R.string.empty,
            pendingIntent = null
        )
    }
}
+47 −3
Original line number Diff line number Diff line
@@ -25,25 +25,31 @@ import androidx.annotation.StringRes
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import foundation.e.advancedprivacy.core.utils.notificationBuilder
import foundation.e.advancedprivacy.domain.entities.CHANNEL_FAKE_LOCATION_FLAG
import foundation.e.advancedprivacy.domain.entities.CHANNEL_FIRST_BOOT
import foundation.e.advancedprivacy.domain.entities.CHANNEL_IPSCRAMBLING_FLAG
import foundation.e.advancedprivacy.domain.entities.CHANNEL_TRACKER_FLAG
import foundation.e.advancedprivacy.domain.entities.CHANNEL_WEEKLYREPORT
import foundation.e.advancedprivacy.domain.entities.DisplayableTrackerCall
import foundation.e.advancedprivacy.domain.entities.FeatureMode
import foundation.e.advancedprivacy.domain.entities.FeatureState
import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_FAKE_LOCATION_FLAG
import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_FIRST_BOOT
import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_IPSCRAMBLING_FLAG
import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_TRACKER_FLAG
import foundation.e.advancedprivacy.domain.entities.NOTIFICATION_WEEKLYREPORT
import foundation.e.advancedprivacy.domain.entities.NotificationContent
import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableReport
import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase
import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase
import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule
import foundation.e.advancedprivacy.features.weeklyreport.WeeklyReportViewFactory
import foundation.e.advancedprivacy.main.MainActivity
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
@@ -55,9 +61,11 @@ import timber.log.Timber
class NotificationsPresenter(
    private val context: Context,
    private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
    private val trackersStatisticsUseCase: TrackersStatisticsUseCase,
    private val permissionsPrivacyModule: IPermissionsPrivacyModule,
    private val weeklyReportUseCase: WeeklyReportUseCase,
    private val weeklyReportViewFactory: WeeklyReportViewFactory,
    private val notificationTrackerFlag: NotificationContent,
    private val appScope: CoroutineScope
) {

@@ -105,7 +113,8 @@ class NotificationsPresenter(
        createNotificationFlagChannel(
            channelId = CHANNEL_TRACKER_FLAG,
            channelName = R.string.notifications_tracker_channel_name,
            channelDescription = R.string.notifications_tracker_channel_description
            channelDescription = R.string.notifications_tracker_channel_description,
            importance = NotificationManager.IMPORTANCE_MIN
        )

        createWeeklyReportChannel()
@@ -137,6 +146,8 @@ class NotificationsPresenter(
                notificationManager.cancel(NOTIFICATION_WEEKLYREPORT)
            }
        }.launchIn(appScope)

        trackersStatisticsUseCase.lastCall.map(::showLastTrackerCall).launchIn(appScope)
    }

    private fun createNotificationFirstBootChannel() {
@@ -148,11 +159,16 @@ class NotificationsPresenter(
        NotificationManagerCompat.from(context).createNotificationChannel(channel)
    }

    private fun createNotificationFlagChannel(channelId: String, @StringRes channelName: Int, @StringRes channelDescription: Int) {
    private fun createNotificationFlagChannel(
        channelId: String,
        @StringRes channelName: Int,
        @StringRes channelDescription: Int,
        importance: Int = NotificationManager.IMPORTANCE_LOW
    ) {
        val channel = NotificationChannel(
            channelId,
            context.getString(channelName),
            NotificationManager.IMPORTANCE_LOW
            importance
        )
        channel.description = context.getString(channelDescription)
        permissionsPrivacyModule.setBlockable(channel)
@@ -227,6 +243,34 @@ class NotificationsPresenter(
        )
    }

    private fun showLastTrackerCall(lastCall: DisplayableTrackerCall?) {
        val builder = notificationBuilder(context = context, content = notificationTrackerFlag)
        if (lastCall != null) {
            builder.setContentText(
                context.getString(
                    if (lastCall.wasBlocked) {
                        R.string.notifications_tracker_blocked_leak
                    } else {
                        R.string.notifications_tracker_allowed_leak
                    },
                    lastCall.displayableApp.label,
                    lastCall.tracker.label,
                    DateTimeFormatter.ofPattern(
                        context.getString(R.string.notifications_tracker_timestamp_format)
                    ).format(lastCall.timestamp.atZone(ZoneId.systemDefault()))

                )
            )

            builder.setLargeIcon(lastCall.displayableApp.icon.toBitmap())
        }

        notificationManager.notify(
            NOTIFICATION_TRACKER_FLAG,
            builder.build()
        )
    }

    private fun hideFlagNotification(id: Int) {
        NotificationManagerCompat.from(context).cancel(id)
    }
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 E FOUNDATION
 * 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
@@ -15,31 +15,14 @@
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package foundation.e.advancedprivacy.trackers.services
package foundation.e.advancedprivacy.domain.entities

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.os.Build
import foundation.e.advancedprivacy.trackers.domain.entities.Tracker
import java.time.Instant

object ForegroundStarter {
    private const val NOTIFICATION_CHANNEL_ID = "blocker_service"
    fun startForeground(service: Service) {
        val mNotificationManager =
            service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (Build.VERSION.SDK_INT >= 26) {
            mNotificationManager.createNotificationChannel(
                NotificationChannel(
                    NOTIFICATION_CHANNEL_ID,
                    NOTIFICATION_CHANNEL_ID,
                    NotificationManager.IMPORTANCE_LOW
data class DisplayableTrackerCall(
    val displayableApp: DisplayableApp,
    val tracker: Tracker,
    val timestamp: Instant,
    val wasBlocked: Boolean
)
            )
            val notification = Notification.Builder(service, NOTIFICATION_CHANNEL_ID)
                .setContentTitle("Trackers filter").build()
            service.startForeground(1337, notification)
        }
    }
}
+36 −2
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 - 2025 E FOUNDATION
 * Copyright (C) 2022 - 2024 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
@@ -24,6 +24,8 @@ import foundation.e.advancedprivacy.data.repositories.AppListRepository
import foundation.e.advancedprivacy.data.repositories.ResourcesRepository
import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.domain.entities.DisplayableApp
import foundation.e.advancedprivacy.domain.entities.DisplayableTrackerCall
import foundation.e.advancedprivacy.domain.entities.TrackerCall
import foundation.e.advancedprivacy.domain.entities.TrackersPeriodicStatistics
import foundation.e.advancedprivacy.features.trackers.Period
import foundation.e.advancedprivacy.trackers.data.StatsDatabase
@@ -34,22 +36,54 @@ import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn

class TrackersStatisticsUseCase(
    private val whitelistRepository: WhitelistRepository,
    private val trackersRepository: TrackersRepository,
    private val appListRepository: AppListRepository,
    private val statsDatabase: StatsDatabase,
    private val resourcesRepository: ResourcesRepository
    private val resourcesRepository: ResourcesRepository,
    backgroundScope: CoroutineScope
) {
    companion object {
        private const val LASTCALL_DISPLAY_DURATION = 5 * 60 * 1000L
    }

    val lastCall: Flow<DisplayableTrackerCall?> = statsDatabase.newDataAvailable
        .mapNotNull(::prepareNotification)
        .shareIn(scope = backgroundScope, started = WhileSubscribed(), replay = 0)

    private fun prepareNotification(trackerCall: TrackerCall): DisplayableTrackerCall? {
        val displayableApp = appListRepository.getAppById(trackerCall.apId)
        val tracker = trackersRepository.getTracker(trackerCall.trackerId)

        return if (tracker != null && displayableApp != null) {
            DisplayableTrackerCall(
                displayableApp = displayableApp,
                tracker = tracker,
                timestamp = trackerCall.timestamp,
                wasBlocked = trackerCall.wasBlocked
            )
        } else {
            null
        }
    }

    fun initAppList() {
        appListRepository.refreshAppDescriptions()
    }

    @OptIn(FlowPreview::class)
    fun listenUpdates(debounce: Duration = 1.seconds) = statsDatabase.newDataAvailable
        .map { Unit }
        .throttleFirst(windowDuration = debounce)
        .onStart { emit(Unit) }

Loading