From 7cf785d9a8c745b961eaa68c9cddbc20d7bc7fe1 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Thu, 24 Feb 2022 07:27:53 +0000 Subject: Improve trackers statistics accuracy, #4584, #4580, #4588 --- .../e/privacycentralapp/DependencyContainer.kt | 2 +- .../domain/usecases/TrackersStateUseCase.kt | 4 -- .../domain/usecases/TrackersStatisticsUseCase.kt | 52 +++++++++++----- .../features/dashboard/DashboardFeature.kt | 37 +++++++++--- .../features/dashboard/DashboardFragment.kt | 8 +++ .../features/trackers/TrackersFeature.kt | 49 +++++++++++---- .../features/trackers/TrackersFragment.kt | 8 +++ .../trackers/apptrackers/AppTrackersFeature.kt | 70 +++++++++++++++------- .../trackers/apptrackers/AppTrackersFragment.kt | 8 +++ .../trackers/apptrackers/AppTrackersViewModel.kt | 12 ++-- 10 files changed, 181 insertions(+), 69 deletions(-) (limited to 'app/src/main/java/foundation/e') diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index e2cca4a..87159d2 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -121,6 +121,6 @@ class DependencyContainer constructor(val app: Application) { } val appTrackersViewModelFactory by lazy { - AppTrackersViewModelFactory(trackersStateUseCase) + AppTrackersViewModelFactory(trackersStateUseCase, trackersStatisticsUseCase) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt index e140b19..e8759cb 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt @@ -68,10 +68,6 @@ class TrackersStateUseCase( return permissionsPrivacyModule.getApplicationDescription(packageName) } - fun getTrackers(appUid: Int): List { - return trackersPrivacyModule.getTrackersForApp(appUid) - } - fun isWhitelisted(appUid: Int): Boolean { return blockTrackersPrivacyModule.isWhitelisted(appUid) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt index fcc3676..2ed1077 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt @@ -18,37 +18,59 @@ package foundation.e.privacycentralapp.domain.usecases import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.Tracker +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow class TrackersStatisticsUseCase( private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule ) { - fun getPastDayTrackersCalls(): List { - return trackTrackersPrivacyModule.getPastDayTrackersCalls().pruneEmptyHistoric() + fun listenUpdates(): Flow = callbackFlow { + val listener = object : ITrackTrackersPrivacyModule.Listener { + override fun onNewData() { + offer(Unit) + } + } + awaitClose { trackTrackersPrivacyModule.removeListener(listener) } } - fun getDayMonthYearStatistics(): Triple, List, List> { - return Triple( - trackTrackersPrivacyModule.getPastDayTrackersCalls().pruneEmptyHistoric(), - trackTrackersPrivacyModule.getPastMonthTrackersCalls().pruneEmptyHistoric(), - trackTrackersPrivacyModule.getPastYearTrackersCalls().pruneEmptyHistoric() - ) + fun listenDayStatistics(): Flow, Int, Int>> = callbackFlow { + val listener = object : ITrackTrackersPrivacyModule.Listener { + override fun onNewData() { + offer(getDayStatistics()) + } + } + + offer(getDayStatistics()) + trackTrackersPrivacyModule.addListener(listener) + awaitClose { trackTrackersPrivacyModule.removeListener(listener) } } - fun getDayMonthYearCounts(): Triple { + fun getDayStatistics(): Triple, Int, Int> { return Triple( + trackTrackersPrivacyModule.getPastDayTrackersCalls(), trackTrackersPrivacyModule.getPastDayTrackersCount(), - trackTrackersPrivacyModule.getPastMonthTrackersCount(), - trackTrackersPrivacyModule.getPastYearTrackersCount() + trackTrackersPrivacyModule.getTrackersCount() ) } - fun getPastDayTrackersCount(): Int { - return trackTrackersPrivacyModule.getPastDayTrackersCount() + fun getDayMonthYearStatistics(): Triple< + Pair, Int>, + Pair, Int>, + Pair, Int>> { + return with(trackTrackersPrivacyModule) { + Triple( + getPastDayTrackersCalls() to getPastDayTrackersCount(), + getPastMonthTrackersCalls() to getPastMonthTrackersCount(), + getPastYearTrackersCalls() to getPastYearTrackersCount() + ) + } } - fun getTrackersCount(): Int { - return trackTrackersPrivacyModule.getTrackersCount() + fun getTrackers(appUid: Int): List { + return trackTrackersPrivacyModule.getTrackersForApp(appUid) } private fun List.pruneEmptyHistoric(): List { diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt index c1d6559..5a37246 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt @@ -30,7 +30,6 @@ import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -66,6 +65,7 @@ class DashboardFeature( object NavigateToInternetActivityPrivacySingleEvent : SingleEvent() object NavigateToLocationSingleEvent : SingleEvent() object NavigateToPermissionsSingleEvent : SingleEvent() + object NewStatisticsAvailableSingleEvent : SingleEvent() } sealed class Action { @@ -75,6 +75,7 @@ class DashboardFeature( object ShowInternetActivityPrivacyAction : Action() object ShowAppsPermissions : Action() object ShowTrackers : Action() + object FetchStatistics : Action() } sealed class Effect { @@ -92,6 +93,7 @@ class DashboardFeature( object OpenInternetActivityPrivacyEffect : Effect() object OpenAppsPermissionsEffect : Effect() object OpenTrackersEffect : Effect() + object NewStatisticsAvailablesEffect : Effect() } companion object { @@ -134,21 +136,25 @@ class DashboardFeature( Action.InitAction -> merge( getPrivacyStateUseCase.quickPrivacyEnabledFlow.map { - Effect.UpdateStateEffect(it) }, ipScramblingStateUseCase.internetPrivacyMode.map { Effect.IpScramblingModeUpdatedEffect(it) }, - flow { - emit( + trackersStatisticsUseCase.listenUpdates().map { + Effect.NewStatisticsAvailablesEffect + }, + flowOf( + // trackersStatisticsUseCase.listenDayStatistics().map { + trackersStatisticsUseCase.getDayStatistics().let { + (dayStatistics, dayTrackersCount, trackersCount) -> Effect.TrackersStatisticsUpdatedEffect( - dayStatistics = trackersStatisticsUseCase.getPastDayTrackersCalls(), - dayTrackersCount = trackersStatisticsUseCase.getPastDayTrackersCount(), - trackersCount = trackersStatisticsUseCase.getTrackersCount() + dayStatistics, + dayTrackersCount, + trackersCount ) - ) - }, + } + ), trackersStateUseCase.areAllTrackersBlocked.map { Effect.TrackersBlockedUpdatedEffect(it) }, @@ -162,6 +168,17 @@ class DashboardFeature( Effect.OpenInternetActivityPrivacyEffect ) Action.ShowTrackers -> flowOf(Effect.OpenTrackersEffect) + Action.FetchStatistics -> flowOf( + // trackersStatisticsUseCase.listenDayStatistics().map { + trackersStatisticsUseCase.getDayStatistics().let { + (dayStatistics, dayTrackersCount, trackersCount) -> + Effect.TrackersStatisticsUpdatedEffect( + dayStatistics, + dayTrackersCount, + trackersCount + ) + } + ) } }, singleEventProducer = { state, _, effect -> @@ -175,6 +192,8 @@ class DashboardFeature( SingleEvent.NavigateToPermissionsSingleEvent is Effect.OpenTrackersEffect -> SingleEvent.NavigateToTrackersSingleEvent + is Effect.NewStatisticsAvailablesEffect -> + SingleEvent.NewStatisticsAvailableSingleEvent else -> null } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt index 60cef54..441f3d6 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt @@ -90,6 +90,9 @@ class DashboardFragment : addToBackStack("dashboard") } } + DashboardFeature.SingleEvent.NewStatisticsAvailableSingleEvent -> { + viewModel.submitAction(DashboardFeature.Action.FetchStatistics) + } } } } @@ -122,6 +125,11 @@ class DashboardFragment : } } + override fun onResume() { + super.onResume() + viewModel.submitAction(DashboardFeature.Action.FetchStatistics) + } + override fun getTitle(): String { return getString(R.string.dashboard_title) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt index 64cc71e..d061c4a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt @@ -64,11 +64,13 @@ class TrackersFeature( sealed class SingleEvent { data class ErrorEvent(val error: String) : SingleEvent() data class OpenAppDetailsEvent(val appDesc: ApplicationDescription) : SingleEvent() + object NewStatisticsAvailableSingleEvent : SingleEvent() } sealed class Action { object InitAction : Action() data class ClickAppAction(val packageName: String) : Action() + object FetchStatistics : Action() } sealed class Effect { @@ -86,6 +88,7 @@ class TrackersFeature( data class OpenAppDetailsEffect(val appDesc: ApplicationDescription) : Effect() object QuickPrivacyDisabledWarningEffect : Effect() data class ErrorEffect(val message: String) : Effect() + object NewStatisticsAvailablesEffect : Effect() } companion object { @@ -117,21 +120,25 @@ class TrackersFeature( when (action) { Action.InitAction -> merge( flow { - val statistics = trackersStatisticsUseCase.getDayMonthYearStatistics() - val counts = trackersStatisticsUseCase.getDayMonthYearCounts() - emit( - Effect.TrackersStatisticsLoadedEffect( - dayStatistics = statistics.first, - dayTrackersCount = counts.first, - monthStatistics = statistics.second, - monthTrackersCount = counts.second, - yearStatistics = statistics.third, - yearTrackersCount = counts.third - ) - ) + trackersStatisticsUseCase.getDayMonthYearStatistics() + .let { (day, month, year) -> + emit( + Effect.TrackersStatisticsLoadedEffect( + dayStatistics = day.first, + dayTrackersCount = day.second, + monthStatistics = month.first, + monthTrackersCount = month.second, + yearStatistics = year.first, + yearTrackersCount = year.second + ) + ) + } }, appListUseCase.getBlockableApps().map { apps -> Effect.AvailableAppsListEffect(apps) + }, + trackersStatisticsUseCase.listenUpdates().map { + Effect.NewStatisticsAvailablesEffect } ) @@ -142,13 +149,29 @@ class TrackersFeature( } ?: run { Effect.ErrorEffect("Can't find back app.") } } else Effect.QuickPrivacyDisabledWarningEffect ) + is Action.FetchStatistics -> flow { + trackersStatisticsUseCase.getDayMonthYearStatistics() + .let { (day, month, year) -> + emit( + Effect.TrackersStatisticsLoadedEffect( + dayStatistics = day.first, + dayTrackersCount = day.second, + monthStatistics = month.first, + monthTrackersCount = month.second, + yearStatistics = year.first, + yearTrackersCount = year.second + ) + ) + } + } } }, singleEventProducer = { _, _, effect -> when (effect) { is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) is Effect.OpenAppDetailsEffect -> SingleEvent.OpenAppDetailsEvent(effect.appDesc) - Effect.QuickPrivacyDisabledWarningEffect -> SingleEvent.ErrorEvent("Enabled Quick Privacy to use functionalities") + is Effect.QuickPrivacyDisabledWarningEffect -> SingleEvent.ErrorEvent("Enabled Quick Privacy to use functionalities") + is Effect.NewStatisticsAvailablesEffect -> SingleEvent.NewStatisticsAvailableSingleEvent else -> null } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt index fed5fe9..c017abd 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt @@ -73,6 +73,9 @@ class TrackersFragment : addToBackStack("apptrackers") } } + is TrackersFeature.SingleEvent.NewStatisticsAvailableSingleEvent -> { + viewModel.submitAction(TrackersFeature.Action.FetchStatistics) + } } } } @@ -107,6 +110,11 @@ class TrackersFragment : } } + override fun onResume() { + super.onResume() + viewModel.submitAction(TrackersFeature.Action.FetchStatistics) + } + override fun getTitle() = getString(R.string.trackers_title) override fun render(state: TrackersFeature.State) { diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt index 11bcf0c..18cbb93 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt @@ -23,11 +23,13 @@ import foundation.e.flowmvi.Reducer import foundation.e.flowmvi.SingleEventProducer import foundation.e.flowmvi.feature.BaseFeature import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase +import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import foundation.e.privacymodules.permissions.data.ApplicationDescription import foundation.e.privacymodules.trackers.Tracker import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge // Define a state machine for Tracker feature. @@ -62,43 +64,43 @@ class AppTrackersFeature( sealed class SingleEvent { data class ErrorEvent(val error: String) : SingleEvent() + object NewStatisticsAvailableSingleEvent : SingleEvent() } sealed class Action { data class InitAction(val packageName: String) : Action() data class BlockAllToggleAction(val isBlocked: Boolean) : Action() data class ToggleTrackerAction(val tracker: Tracker, val isBlocked: Boolean) : Action() + object FetchStatistics : Action() } sealed class Effect { data class SetAppEffect(val appDesc: ApplicationDescription) : Effect() data class AppTrackersBlockingActivatedEffect(val isBlockingActivated: Boolean) : Effect() data class AvailableTrackersListEffect( - val isBlockingActivated: Boolean, + // val isBlockingActivated: Boolean, val trackers: List, - val whitelist: List + // val whitelist: List ) : Effect() data class TrackersWhitelistUpdateEffect(val whitelist: List) : Effect() // object QuickPrivacyDisabledWarningEffect : Effect() data class ErrorEffect(val message: String) : Effect() + object NewStatisticsAvailablesEffect : Effect() } companion object { fun create( initialState: State = State(), coroutineScope: CoroutineScope, - trackersStateUseCase: TrackersStateUseCase + trackersStateUseCase: TrackersStateUseCase, + trackersStatisticsUseCase: TrackersStatisticsUseCase ) = AppTrackersFeature( initialState, coroutineScope, reducer = { state, effect -> when (effect) { is Effect.SetAppEffect -> state.copy(appDesc = effect.appDesc) - is Effect.AvailableTrackersListEffect -> state.copy( - isBlockingActivated = effect.isBlockingActivated, - trackers = effect.trackers, - whitelist = effect.whitelist - ) + is Effect.AvailableTrackersListEffect -> state.copy(trackers = effect.trackers) is Effect.AppTrackersBlockingActivatedEffect -> state.copy(isBlockingActivated = effect.isBlockingActivated) @@ -106,27 +108,39 @@ class AppTrackersFeature( is Effect.TrackersWhitelistUpdateEffect -> state.copy(whitelist = effect.whitelist) is Effect.ErrorEffect -> state + else -> state } }, actor = { state, action -> when (action) { - is Action.InitAction -> merge( - flow { - val appDesc = - trackersStateUseCase.getApplicationPermission(action.packageName) - emit(Effect.SetAppEffect(appDesc)) + is Action.InitAction -> { + val appDesc = + trackersStateUseCase.getApplicationPermission(action.packageName) + merge( + flow { - emit( - Effect.AvailableTrackersListEffect( - isBlockingActivated = !trackersStateUseCase.isWhitelisted( - appDesc.uid - ), - trackers = trackersStateUseCase.getTrackers(appDesc.uid), - whitelist = trackersStateUseCase.getTrackersWhitelistIds(appDesc.uid) + emit(Effect.SetAppEffect(appDesc)) + emit( + Effect.AppTrackersBlockingActivatedEffect( + !trackersStateUseCase.isWhitelisted(appDesc.uid) + ) ) - ) - } - ) + emit( + Effect.TrackersWhitelistUpdateEffect( + trackersStateUseCase.getTrackersWhitelistIds(appDesc.uid) + ) + ) + emit( + Effect.AvailableTrackersListEffect( + trackers = trackersStatisticsUseCase.getTrackers(appDesc.uid) + ) + ) + }, + trackersStatisticsUseCase.listenUpdates().map { + Effect.NewStatisticsAvailablesEffect + } + ) + } is Action.BlockAllToggleAction -> state.appDesc?.uid?.let { appUid -> flow { @@ -157,11 +171,21 @@ class AppTrackersFeature( } } ?: run { flowOf(Effect.ErrorEffect("No appDesc.")) } } + is Action.FetchStatistics -> flowOf( + state.appDesc?.uid?.let { + Effect.AvailableTrackersListEffect( + trackers = trackersStatisticsUseCase.getTrackers(it) + ) + } ?: Effect.ErrorEffect("No appDesc.") + + ) } }, singleEventProducer = { _, _, effect -> when (effect) { is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) + is Effect.NewStatisticsAvailablesEffect -> + SingleEvent.NewStatisticsAvailableSingleEvent else -> null } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt index 6b57719..5b09be5 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt @@ -71,6 +71,9 @@ class AppTrackersFragment : viewModel.feature.singleEvents.collect { event -> when (event) { is SingleEvent.ErrorEvent -> displayToast(event.error) + is SingleEvent.NewStatisticsAvailableSingleEvent -> { + viewModel.submitAction(Action.FetchStatistics) + } } } } @@ -111,6 +114,11 @@ class AppTrackersFragment : } } + override fun onResume() { + super.onResume() + viewModel.submitAction(Action.FetchStatistics) + } + override fun render(state: State) { binding.blockAllToggle.isChecked = state.isBlockingActivated diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt index 8acbcac..37fdb85 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt @@ -22,12 +22,14 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.common.Factory import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase +import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch class AppTrackersViewModel( - private val trackersStateUseCase: TrackersStateUseCase + private val trackersStateUseCase: TrackersStateUseCase, + private val trackersStatisticsUseCase: TrackersStatisticsUseCase ) : ViewModel() { private val _actions = MutableSharedFlow() @@ -36,7 +38,8 @@ class AppTrackersViewModel( val feature: AppTrackersFeature by lazy { AppTrackersFeature.create( coroutineScope = viewModelScope, - trackersStateUseCase = trackersStateUseCase + trackersStateUseCase = trackersStateUseCase, + trackersStatisticsUseCase = trackersStatisticsUseCase ) } @@ -49,10 +52,11 @@ class AppTrackersViewModel( } class AppTrackersViewModelFactory( - private val trackersStateUseCase: TrackersStateUseCase + private val trackersStateUseCase: TrackersStateUseCase, + private val trackersStatisticsUseCase: TrackersStatisticsUseCase ) : Factory { override fun create(): AppTrackersViewModel { - return AppTrackersViewModel(trackersStateUseCase) + return AppTrackersViewModel(trackersStateUseCase, trackersStatisticsUseCase) } } -- cgit v1.2.1