Loading app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt +3 −2 Original line number Diff line number Diff line Loading @@ -28,7 +28,6 @@ import foundation.e.advancedprivacy.domain.usecases.TrackersStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import foundation.e.advancedprivacy.domain.usecases.VpnSupervisorUseCase import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase import foundation.e.advancedprivacy.externalinterfaces.workers.WeeklyReportWorker import foundation.e.advancedprivacy.trackers.data.TrackersRepository import foundation.e.advancedprivacy.trackers.services.UpdateTrackersWorker import foundation.e.lib.telemetry.Telemetry Loading @@ -39,12 +38,15 @@ import kotlinx.coroutines.withContext import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import org.koin.java.KoinJavaComponent.get import timber.log.Timber class AdvancedPrivacyApplication : Application() { override fun onCreate() { super.onCreate() Telemetry.init(BuildConfig.SENTRY_DSN, this, true) Timber.plant(Timber.DebugTree()) startKoin { androidContext(this@AdvancedPrivacyApplication) modules(appModule) Loading @@ -59,7 +61,6 @@ class AdvancedPrivacyApplication : Application() { get<TrackersRepository>(TrackersRepository::class.java).initTrackersFile() UpdateTrackersWorker.periodicUpdate(this@AdvancedPrivacyApplication) WeeklyReportWorker.scheduleNext(this@AdvancedPrivacyApplication) WarningDialog.startListening( get(ShowFeaturesWarningUseCase::class.java), Loading app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt +3 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase import foundation.e.advancedprivacy.dummy.CityDataSource import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule import foundation.e.advancedprivacy.externalinterfaces.workers.WeeklyReportWorkerScheduler import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel import foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyViewModel import foundation.e.advancedprivacy.features.location.FakeLocationViewModel Loading Loading @@ -138,6 +139,8 @@ val appModule = module { single { CityDataSource } single { ResourcesRepository(androidContext()) } single { WeeklyReportWorkerScheduler(androidContext()) } singleOf(::FakeLocationStateUseCase) single { Loading app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt +11 −13 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import foundation.e.advancedprivacy.data.repositories.WeeklyReportLocalRepositor import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableReport import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReport import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReportScore import foundation.e.advancedprivacy.externalinterfaces.workers.WeeklyReportWorkerScheduler import foundation.e.advancedprivacy.trackers.data.StatsDatabase import foundation.e.advancedprivacy.trackers.data.TrackersRepository import java.time.Duration Loading @@ -32,8 +33,6 @@ import kotlin.collections.component2 import kotlin.random.Random import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch Loading @@ -43,44 +42,43 @@ class WeeklyReportUseCase( private val trackersRepository: TrackersRepository, private val appListRepository: AppListRepository, private val weeklyReportRepository: WeeklyReportLocalRepository, private val weeklyReportWorkerScheduler: WeeklyReportWorkerScheduler, private val statsDatabase: StatsDatabase, private val scope: CoroutineScope ) { companion object { val DISPLAY_DURATION: Duration = Duration.ofDays(1) } private val _currentReport = MutableStateFlow<DisplayableReport?>(null) val currentReport: StateFlow<DisplayableReport?> = _currentReport private val displayDuration: Duration = Duration.ofDays(1) private var stopDisplayJob: Job? = null fun listen() { scope.launch { updateCurrent() val currentReport = currentReport.value?.report weeklyReportWorkerScheduler.scheduleNext(currentReport) weeklyReportWorkerScheduler.scheduleDismiss(currentReport) } } suspend fun updateCurrent() { val weeklyReport = weeklyReportRepository.getLastWeeklyReport() ?: return val now = Instant.now() val endOfDisplay = weeklyReport.timestamp + displayDuration val endOfDisplay = weeklyReport.timestamp + DISPLAY_DURATION if (now < endOfDisplay) { _currentReport.value = weeklyReport.toDisplayableReport() stopDisplayJob = scope.launch { delay(endOfDisplay.toEpochMilli() - now.toEpochMilli()) _currentReport.value = null stopDisplayJob = null } } else { _currentReport.value = null } } suspend fun updateWeeklyReport() = withContext(Dispatchers.IO) { suspend fun updateWeeklyReport(): WeeklyReport = withContext(Dispatchers.IO) { val history = weeklyReportRepository.getLast99Reports() val weeklyReport = buildCandidates(Instant.now(), history).first().weeklyReport weeklyReportRepository.setLastWeeklyReport(weeklyReport) updateCurrent() weeklyReport } suspend fun debugGenerateReportsSinceWeeksAgo(weeksAgo: Int): List<Pair<DisplayableReport?, List<WeeklyReportScore>>> = Loading app/src/main/java/foundation/e/advancedprivacy/externalinterfaces/workers/WeeklyReportWorker.kt +57 −18 Original line number Diff line number Diff line Loading @@ -23,7 +23,10 @@ import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReport import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime import java.time.temporal.ChronoUnit import java.time.temporal.WeekFields Loading @@ -34,17 +37,35 @@ import timber.log.Timber class WeeklyReportWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { Timber.d("WeeklyReportWorker starts computing this week report") val weeklyReportWorkerScheduler: WeeklyReportWorkerScheduler = get(WeeklyReportWorkerScheduler::class.java) val weeklyReportUseCase: WeeklyReportUseCase = get(WeeklyReportUseCase::class.java) weeklyReportUseCase.updateWeeklyReport() scheduleNext(applicationContext) val newReport = weeklyReportUseCase.updateWeeklyReport() weeklyReportWorkerScheduler.scheduleNext(newReport) weeklyReportWorkerScheduler.scheduleDismiss(newReport) return Result.success() } } class WeeklyReportDismissWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { Timber.d("WeeklyReportDismissWorker will dismiss any displayed WeeklyReport") val weeklyReportUseCase: WeeklyReportUseCase = get(WeeklyReportUseCase::class.java) weeklyReportUseCase.updateCurrent() return Result.success() } } companion object { fun scheduleNext(appContext: Context) { var next = ZonedDateTime.now() class WeeklyReportWorkerScheduler(private val appContext: Context) { fun scheduleNext(lastReport: WeeklyReport?) { val now = ZonedDateTime.now() var next = now next = next.with(WeekFields.of(appContext.resources.configuration.locales[0]).dayOfWeek(), 7) next = next.truncatedTo(ChronoUnit.DAYS) if (next.toLocalDate() == lastReport?.timestamp?.atZone(ZoneId.systemDefault())?.toLocalDate() && now.hour >= 11) { next = next.plus(7, ChronoUnit.DAYS) } next = next.plus(11, ChronoUnit.HOURS) val delay = next.toEpochSecond() - ZonedDateTime.now().toEpochSecond() Loading @@ -60,5 +81,23 @@ class WeeklyReportWorker(appContext: Context, workerParams: WorkerParameters) : request ) } fun scheduleDismiss(currentReport: WeeklyReport?) { val next = currentReport?.timestamp?.plus(WeeklyReportUseCase.DISPLAY_DURATION) ?: return val delay = next.toEpochMilli() - Instant.now().toEpochMilli() if (delay < 0) { return } Timber.d("Schedule Dismiss of WeeklyReport of ${currentReport.timestamp} at $next, in $delay milliseconds") val request = OneTimeWorkRequestBuilder<WeeklyReportDismissWorker>() .setInitialDelay(delay, TimeUnit.MILLISECONDS) .build() WorkManager.getInstance(appContext).enqueueUniqueWork( WeeklyReportDismissWorker::class.qualifiedName ?: "", ExistingWorkPolicy.REPLACE, request ) } } Loading
app/src/main/java/foundation/e/advancedprivacy/AdvancedPrivacyApplication.kt +3 −2 Original line number Diff line number Diff line Loading @@ -28,7 +28,6 @@ import foundation.e.advancedprivacy.domain.usecases.TrackersStateUseCase import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import foundation.e.advancedprivacy.domain.usecases.VpnSupervisorUseCase import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase import foundation.e.advancedprivacy.externalinterfaces.workers.WeeklyReportWorker import foundation.e.advancedprivacy.trackers.data.TrackersRepository import foundation.e.advancedprivacy.trackers.services.UpdateTrackersWorker import foundation.e.lib.telemetry.Telemetry Loading @@ -39,12 +38,15 @@ import kotlinx.coroutines.withContext import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import org.koin.java.KoinJavaComponent.get import timber.log.Timber class AdvancedPrivacyApplication : Application() { override fun onCreate() { super.onCreate() Telemetry.init(BuildConfig.SENTRY_DSN, this, true) Timber.plant(Timber.DebugTree()) startKoin { androidContext(this@AdvancedPrivacyApplication) modules(appModule) Loading @@ -59,7 +61,6 @@ class AdvancedPrivacyApplication : Application() { get<TrackersRepository>(TrackersRepository::class.java).initTrackersFile() UpdateTrackersWorker.periodicUpdate(this@AdvancedPrivacyApplication) WeeklyReportWorker.scheduleNext(this@AdvancedPrivacyApplication) WarningDialog.startListening( get(ShowFeaturesWarningUseCase::class.java), Loading
app/src/main/java/foundation/e/advancedprivacy/KoinModule.kt +3 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase import foundation.e.advancedprivacy.dummy.CityDataSource import foundation.e.advancedprivacy.externalinterfaces.permissions.IPermissionsPrivacyModule import foundation.e.advancedprivacy.externalinterfaces.workers.WeeklyReportWorkerScheduler import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel import foundation.e.advancedprivacy.features.internetprivacy.InternetPrivacyViewModel import foundation.e.advancedprivacy.features.location.FakeLocationViewModel Loading Loading @@ -138,6 +139,8 @@ val appModule = module { single { CityDataSource } single { ResourcesRepository(androidContext()) } single { WeeklyReportWorkerScheduler(androidContext()) } singleOf(::FakeLocationStateUseCase) single { Loading
app/src/main/java/foundation/e/advancedprivacy/domain/usecases/WeeklyReportUseCase.kt +11 −13 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import foundation.e.advancedprivacy.data.repositories.WeeklyReportLocalRepositor import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableReport import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReport import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReportScore import foundation.e.advancedprivacy.externalinterfaces.workers.WeeklyReportWorkerScheduler import foundation.e.advancedprivacy.trackers.data.StatsDatabase import foundation.e.advancedprivacy.trackers.data.TrackersRepository import java.time.Duration Loading @@ -32,8 +33,6 @@ import kotlin.collections.component2 import kotlin.random.Random import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch Loading @@ -43,44 +42,43 @@ class WeeklyReportUseCase( private val trackersRepository: TrackersRepository, private val appListRepository: AppListRepository, private val weeklyReportRepository: WeeklyReportLocalRepository, private val weeklyReportWorkerScheduler: WeeklyReportWorkerScheduler, private val statsDatabase: StatsDatabase, private val scope: CoroutineScope ) { companion object { val DISPLAY_DURATION: Duration = Duration.ofDays(1) } private val _currentReport = MutableStateFlow<DisplayableReport?>(null) val currentReport: StateFlow<DisplayableReport?> = _currentReport private val displayDuration: Duration = Duration.ofDays(1) private var stopDisplayJob: Job? = null fun listen() { scope.launch { updateCurrent() val currentReport = currentReport.value?.report weeklyReportWorkerScheduler.scheduleNext(currentReport) weeklyReportWorkerScheduler.scheduleDismiss(currentReport) } } suspend fun updateCurrent() { val weeklyReport = weeklyReportRepository.getLastWeeklyReport() ?: return val now = Instant.now() val endOfDisplay = weeklyReport.timestamp + displayDuration val endOfDisplay = weeklyReport.timestamp + DISPLAY_DURATION if (now < endOfDisplay) { _currentReport.value = weeklyReport.toDisplayableReport() stopDisplayJob = scope.launch { delay(endOfDisplay.toEpochMilli() - now.toEpochMilli()) _currentReport.value = null stopDisplayJob = null } } else { _currentReport.value = null } } suspend fun updateWeeklyReport() = withContext(Dispatchers.IO) { suspend fun updateWeeklyReport(): WeeklyReport = withContext(Dispatchers.IO) { val history = weeklyReportRepository.getLast99Reports() val weeklyReport = buildCandidates(Instant.now(), history).first().weeklyReport weeklyReportRepository.setLastWeeklyReport(weeklyReport) updateCurrent() weeklyReport } suspend fun debugGenerateReportsSinceWeeksAgo(weeksAgo: Int): List<Pair<DisplayableReport?, List<WeeklyReportScore>>> = Loading
app/src/main/java/foundation/e/advancedprivacy/externalinterfaces/workers/WeeklyReportWorker.kt +57 −18 Original line number Diff line number Diff line Loading @@ -23,7 +23,10 @@ import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReport import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase import java.time.Instant import java.time.ZoneId import java.time.ZonedDateTime import java.time.temporal.ChronoUnit import java.time.temporal.WeekFields Loading @@ -34,17 +37,35 @@ import timber.log.Timber class WeeklyReportWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { Timber.d("WeeklyReportWorker starts computing this week report") val weeklyReportWorkerScheduler: WeeklyReportWorkerScheduler = get(WeeklyReportWorkerScheduler::class.java) val weeklyReportUseCase: WeeklyReportUseCase = get(WeeklyReportUseCase::class.java) weeklyReportUseCase.updateWeeklyReport() scheduleNext(applicationContext) val newReport = weeklyReportUseCase.updateWeeklyReport() weeklyReportWorkerScheduler.scheduleNext(newReport) weeklyReportWorkerScheduler.scheduleDismiss(newReport) return Result.success() } } class WeeklyReportDismissWorker(appContext: Context, workerParams: WorkerParameters) : CoroutineWorker(appContext, workerParams) { override suspend fun doWork(): Result { Timber.d("WeeklyReportDismissWorker will dismiss any displayed WeeklyReport") val weeklyReportUseCase: WeeklyReportUseCase = get(WeeklyReportUseCase::class.java) weeklyReportUseCase.updateCurrent() return Result.success() } } companion object { fun scheduleNext(appContext: Context) { var next = ZonedDateTime.now() class WeeklyReportWorkerScheduler(private val appContext: Context) { fun scheduleNext(lastReport: WeeklyReport?) { val now = ZonedDateTime.now() var next = now next = next.with(WeekFields.of(appContext.resources.configuration.locales[0]).dayOfWeek(), 7) next = next.truncatedTo(ChronoUnit.DAYS) if (next.toLocalDate() == lastReport?.timestamp?.atZone(ZoneId.systemDefault())?.toLocalDate() && now.hour >= 11) { next = next.plus(7, ChronoUnit.DAYS) } next = next.plus(11, ChronoUnit.HOURS) val delay = next.toEpochSecond() - ZonedDateTime.now().toEpochSecond() Loading @@ -60,5 +81,23 @@ class WeeklyReportWorker(appContext: Context, workerParams: WorkerParameters) : request ) } fun scheduleDismiss(currentReport: WeeklyReport?) { val next = currentReport?.timestamp?.plus(WeeklyReportUseCase.DISPLAY_DURATION) ?: return val delay = next.toEpochMilli() - Instant.now().toEpochMilli() if (delay < 0) { return } Timber.d("Schedule Dismiss of WeeklyReport of ${currentReport.timestamp} at $next, in $delay milliseconds") val request = OneTimeWorkRequestBuilder<WeeklyReportDismissWorker>() .setInitialDelay(delay, TimeUnit.MILLISECONDS) .build() WorkManager.getInstance(appContext).enqueueUniqueWork( WeeklyReportDismissWorker::class.qualifiedName ?: "", ExistingWorkPolicy.REPLACE, request ) } }