From 5cdeb5520c74fbaab1909dc1876361ed73f11b55 Mon Sep 17 00:00:00 2001 From: jacquarg Date: Tue, 12 Apr 2022 09:09:40 +0200 Subject: 5249 display whitelisted trackers count in app trackers menu subtitle. --- .../common/BootCompletedReceiver.kt | 146 ++++++++++----------- .../data/repositories/AppListsRepository.kt | 4 + .../domain/usecases/TrackersStatisticsUseCase.kt | 20 +++ .../features/dashboard/DashboardFeature.kt | 52 ++++---- .../features/dashboard/DashboardFragment.kt | 2 +- 5 files changed, 120 insertions(+), 104 deletions(-) (limited to 'app/src/main/java/foundation') diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt b/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt index a26c06a..f43c2cc 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/BootCompletedReceiver.kt @@ -1,73 +1,73 @@ -/* - * Copyright (C) 2022 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.privacycentralapp.common - -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import androidx.core.app.NotificationCompat -import foundation.e.privacycentralapp.R -import foundation.e.privacycentralapp.data.repositories.LocalStateRepository - -class BootCompletedReceiver : BroadcastReceiver() { - companion object { - const val FIRST_BOOT_NOTIFICATION_ID = 10 - } - - override fun onReceive(context: Context, intent: Intent?) { - if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { - val localStateRepository = LocalStateRepository(context) - if (localStateRepository.firstBoot) { - showNotification(context) - localStateRepository.firstBoot = false - } - } - } - - private fun showNotification(context: Context) { - val channelId = "first_boot_notification" - val pendingIntent = - PendingIntent.getActivity( - context, - 0, - context.packageManager.getLaunchIntentForPackage(context.packageName), - PendingIntent.FLAG_IMMUTABLE - ) - val notificationBuilder: NotificationCompat.Builder = - NotificationCompat.Builder(context, channelId) - .setSmallIcon(R.drawable.ic_notification_logo) - .setContentTitle(context.getString(R.string.first_notification_title)) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - .setStyle( - NotificationCompat.BigTextStyle() - .bigText(context.getString(R.string.first_notification_summary)) - ) - val notificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val name: CharSequence = "First Boot" - val importance = NotificationManager.IMPORTANCE_HIGH - val mChannel = NotificationChannel(channelId, name, importance) - notificationManager.createNotificationChannel(mChannel) - notificationManager.notify(FIRST_BOOT_NOTIFICATION_ID, notificationBuilder.build()) - } -} +/* + * Copyright (C) 2022 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.privacycentralapp.common + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.data.repositories.LocalStateRepository + +class BootCompletedReceiver : BroadcastReceiver() { + companion object { + const val FIRST_BOOT_NOTIFICATION_ID = 10 + } + + override fun onReceive(context: Context, intent: Intent?) { + if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { + val localStateRepository = LocalStateRepository(context) + if (localStateRepository.firstBoot) { + showNotification(context) + localStateRepository.firstBoot = false + } + } + } + + private fun showNotification(context: Context) { + val channelId = "first_boot_notification" + val pendingIntent = + PendingIntent.getActivity( + context, + 0, + context.packageManager.getLaunchIntentForPackage(context.packageName), + PendingIntent.FLAG_IMMUTABLE + ) + val notificationBuilder: NotificationCompat.Builder = + NotificationCompat.Builder(context, channelId) + .setSmallIcon(R.drawable.ic_notification_logo) + .setContentTitle(context.getString(R.string.first_notification_title)) + .setAutoCancel(true) + .setContentIntent(pendingIntent) + .setStyle( + NotificationCompat.BigTextStyle() + .bigText(context.getString(R.string.first_notification_summary)) + ) + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + val name: CharSequence = "First Boot" + val importance = NotificationManager.IMPORTANCE_HIGH + val mChannel = NotificationChannel(channelId, name, importance) + notificationManager.createNotificationChannel(mChannel) + notificationManager.notify(FIRST_BOOT_NOTIFICATION_ID, notificationBuilder.build()) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt index 9242765..72dffe9 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt @@ -59,6 +59,10 @@ class AppListsRepository( return appDescriptions.value.second } + fun getVisibleAndHiddenApps(): List { + return appDescriptions.value.first + appDescriptions.value.second + } + fun getApplicationDescription(packageName: String): ApplicationDescription? { return appDescriptions.value.first.find { it.packageName == packageName } } 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 4262055..55887ed 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 @@ -153,4 +153,24 @@ class TrackersStatisticsUseCase( } } } + + fun getNonBLockedTrackersCount(): Int { + return if (blockTrackersPrivacyModule.isBlockingEnabled()) { + val whiteListedTrackers = mutableSetOf() + + val whiteListedAppUids = blockTrackersPrivacyModule.getWhiteListedApp() + + appListsRepository.getVisibleAndHiddenApps().forEach { app -> + if (app.uid in whiteListedAppUids) { + whiteListedTrackers.addAll(getTrackers(app.uid)) + } else { + whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid)) + } + } + + whiteListedTrackers.size + } else { + trackTrackersPrivacyModule.getTrackersCount() + } + } } 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 8a4ee54..e8302f4 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,6 +30,7 @@ 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 @@ -53,8 +54,7 @@ class DashboardFeature( val isAllTrackersBlocked: Boolean = false, val locationMode: LocationMode = LocationMode.REAL_LOCATION, val internetPrivacyMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP, - val totalGraph: Int? = null, - // val graphData + val leakedTrackersCount: Int? = null, val trackersCount: Int? = null, val activeTrackersCount: Int? = null, val dayStatistics: List>? = null, @@ -87,7 +87,8 @@ class DashboardFeature( val dayStatistics: List>, val dayLabels: List, val dayTrackersCount: Int, - val trackersCount: Int + val trackersCount: Int, + val activeTrackersCount: Int ) : Effect() data class TrackersBlockedUpdatedEffect(val areAllTrackersBlocked: Boolean) : Effect() data class UpdateLocationModeEffect(val mode: LocationMode) : Effect() @@ -117,8 +118,9 @@ class DashboardFeature( is Effect.TrackersStatisticsUpdatedEffect -> state.copy( dayStatistics = effect.dayStatistics, dayLabels = effect.dayLabels, - activeTrackersCount = effect.dayTrackersCount, - trackersCount = effect.trackersCount + leakedTrackersCount = effect.dayTrackersCount, + trackersCount = effect.trackersCount, + activeTrackersCount = effect.activeTrackersCount ) is Effect.TrackersBlockedUpdatedEffect -> state.copy( @@ -133,7 +135,7 @@ class DashboardFeature( when (action) { Action.TogglePrivacyAction -> { getPrivacyStateUseCase.toggle() - flowOf(Effect.NoEffect) + flowOf(Effect.NewStatisticsAvailablesEffect) } Action.InitAction -> merge( @@ -146,18 +148,6 @@ class DashboardFeature( trackersStatisticsUseCase.listenUpdates().map { Effect.NewStatisticsAvailablesEffect }, - flowOf( - // trackersStatisticsUseCase.listenDayStatistics().map { - trackersStatisticsUseCase.getDayStatistics().let { - (dayStatistics, trackersCount) -> - Effect.TrackersStatisticsUpdatedEffect( - dayStatistics = dayStatistics.callsBlockedNLeaked, - dayLabels = dayStatistics.periods, - dayTrackersCount = dayStatistics.trackersCount, - trackersCount = trackersCount - ) - } - ), trackersStateUseCase.areAllTrackersBlocked.map { Effect.TrackersBlockedUpdatedEffect(it) }, @@ -171,18 +161,20 @@ class DashboardFeature( Effect.OpenInternetActivityPrivacyEffect ) Action.ShowTrackers -> flowOf(Effect.OpenTrackersEffect) - Action.FetchStatistics -> flowOf( - // trackersStatisticsUseCase.listenDayStatistics().map { - trackersStatisticsUseCase.getDayStatistics().let { - (dayStatistics, trackersCount) -> - Effect.TrackersStatisticsUpdatedEffect( - dayStatistics = dayStatistics.callsBlockedNLeaked, - dayLabels = dayStatistics.periods, - dayTrackersCount = dayStatistics.trackersCount, - trackersCount = trackersCount - ) - } - ) + Action.FetchStatistics -> flow { + emit( + trackersStatisticsUseCase.getDayStatistics() + .let { (dayStatistics, trackersCount) -> + Effect.TrackersStatisticsUpdatedEffect( + dayStatistics = dayStatistics.callsBlockedNLeaked, + dayLabels = dayStatistics.periods, + dayTrackersCount = dayStatistics.trackersCount, + trackersCount = trackersCount, + activeTrackersCount = trackersStatisticsUseCase.getNonBLockedTrackersCount() + ) + } + ) + } } }, singleEventProducer = { _, _, effect -> 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 51dee3d..96ace56 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 @@ -231,7 +231,7 @@ class DashboardFragment : binding.graphLegend.text = Html.fromHtml( getString( R.string.dashboard_graph_trackers_legend, - state.activeTrackersCount?.toString() ?: "No" + state.leakedTrackersCount?.toString() ?: "No" ), FROM_HTML_MODE_LEGACY ) -- cgit v1.2.1 From 14815678aaa5020e81f0cca9274f289e6ac7e2d5 Mon Sep 17 00:00:00 2001 From: jacquarg Date: Tue, 12 Apr 2022 18:55:02 +0200 Subject: 5248 hide whitelist app trackers and fix counts when privacy protection disabled. --- .../data/repositories/AppListsRepository.kt | 5 ++-- .../domain/usecases/TrackersStatisticsUseCase.kt | 33 +++++++++++----------- .../features/dashboard/DashboardFeature.kt | 29 +++++++++---------- .../trackers/apptrackers/AppTrackersFeature.kt | 6 ++-- .../trackers/apptrackers/AppTrackersFragment.kt | 7 ++++- 5 files changed, 42 insertions(+), 38 deletions(-) (limited to 'app/src/main/java/foundation') diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt index 72dffe9..4b19083 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt @@ -59,9 +59,8 @@ class AppListsRepository( return appDescriptions.value.second } - fun getVisibleAndHiddenApps(): List { - return appDescriptions.value.first + appDescriptions.value.second - } + fun getVisibleAndHiddenApps(): Flow> = getVisibleApps() + .map { it + getHiddenSystemApps() } fun getApplicationDescription(packageName: String): ApplicationDescription? { return appDescriptions.value.first.find { it.packageName == packageName } 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 55887ed..6b4e098 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 @@ -28,6 +28,7 @@ import foundation.e.privacymodules.trackers.Tracker import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -142,7 +143,8 @@ class TrackersStatisticsUseCase( apps.map { app -> AppWithCounts( app, - blockTrackersPrivacyModule.isWhitelisted(app.uid), + !blockTrackersPrivacyModule.isBlockingEnabled() || + blockTrackersPrivacyModule.isWhitelisted(app.uid), appListsRepository.foldForHiddenSystemApp(app.uid) { trackersCounts.getOrDefault(it, 0) }, @@ -154,23 +156,20 @@ class TrackersStatisticsUseCase( } } - fun getNonBLockedTrackersCount(): Int { - return if (blockTrackersPrivacyModule.isBlockingEnabled()) { - val whiteListedTrackers = mutableSetOf() - - val whiteListedAppUids = blockTrackersPrivacyModule.getWhiteListedApp() - - appListsRepository.getVisibleAndHiddenApps().forEach { app -> - if (app.uid in whiteListedAppUids) { - whiteListedTrackers.addAll(getTrackers(app.uid)) - } else { - whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid)) + fun getNonBlockedTrackersCount(): Flow { + return if (blockTrackersPrivacyModule.isBlockingEnabled()) + appListsRepository.getVisibleAndHiddenApps().map { apps -> + val whiteListedTrackers = mutableSetOf() + val whiteListedAppUids = blockTrackersPrivacyModule.getWhiteListedApp() + apps.forEach { app -> + if (app.uid in whiteListedAppUids) { + whiteListedTrackers.addAll(getTrackers(app.uid)) + } else { + whiteListedTrackers.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid)) + } } + whiteListedTrackers.size } - - whiteListedTrackers.size - } else { - trackTrackersPrivacyModule.getTrackersCount() - } + else flowOf(trackTrackersPrivacyModule.getTrackersCount()) } } 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 e8302f4..ca45393 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 @@ -161,20 +160,20 @@ class DashboardFeature( Effect.OpenInternetActivityPrivacyEffect ) Action.ShowTrackers -> flowOf(Effect.OpenTrackersEffect) - Action.FetchStatistics -> flow { - emit( - trackersStatisticsUseCase.getDayStatistics() - .let { (dayStatistics, trackersCount) -> - Effect.TrackersStatisticsUpdatedEffect( - dayStatistics = dayStatistics.callsBlockedNLeaked, - dayLabels = dayStatistics.periods, - dayTrackersCount = dayStatistics.trackersCount, - trackersCount = trackersCount, - activeTrackersCount = trackersStatisticsUseCase.getNonBLockedTrackersCount() - ) - } - ) - } + Action.FetchStatistics -> + trackersStatisticsUseCase.getNonBlockedTrackersCount() + .map { nonBlockedTrackersCount -> + trackersStatisticsUseCase.getDayStatistics() + .let { (dayStatistics, trackersCount) -> + Effect.TrackersStatisticsUpdatedEffect( + dayStatistics = dayStatistics.callsBlockedNLeaked, + dayLabels = dayStatistics.periods, + dayTrackersCount = dayStatistics.trackersCount, + trackersCount = trackersCount, + activeTrackersCount = nonBlockedTrackersCount + ) + } + } } }, singleEventProducer = { _, _, effect -> 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 270dfcb..c1eef47 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 @@ -66,7 +66,7 @@ class AppTrackersFeature( } fun getTrackersCount() = trackers?.size ?: 0 - fun getBlockedTrackersCount(): Int = if (isBlockingActivated) + fun getBlockedTrackersCount(): Int = if (isQuickPrivacyEnabled && isBlockingActivated) getTrackersCount() - (whitelist?.size ?: 0) else 0 } @@ -160,7 +160,9 @@ class AppTrackersFeature( } ?: flowOf(Effect.ErrorEffect(R.string.apptrackers_error_no_app)) is Action.BlockAllToggleAction -> - state.appDesc?.uid?.let { appUid -> + if (!state.isQuickPrivacyEnabled) { + flowOf(Effect.QuickPrivacyDisabledWarningEffect) + } else state.appDesc?.uid?.let { appUid -> flow { trackersStateUseCase.toggleAppWhitelist(appUid, !action.isBlocked) 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 8e2dc3b..d6edee6 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 @@ -109,6 +109,10 @@ class AppTrackersFragment : viewModel.submitAction(Action.BlockAllToggleAction(binding.blockAllToggle.isChecked)) } + binding.blockAllToggleClicker.setOnClickListener { + viewModel.submitAction(Action.BlockAllToggleAction(false)) + } + binding.trackers.apply { layoutManager = LinearLayoutManager(requireContext()) setHasFixedSize(true) @@ -128,7 +132,6 @@ class AppTrackersFragment : } override fun render(state: State) { - binding.trackersCountSummary.text = if (state.getTrackersCount() == 0) "" else getString( R.string.apptrackers_trackers_count_summary, @@ -137,6 +140,8 @@ class AppTrackersFragment : ) binding.blockAllToggle.isChecked = state.isBlockingActivated + binding.blockAllToggle.isEnabled = state.isQuickPrivacyEnabled + binding.blockAllToggleClicker.isVisible = !state.isQuickPrivacyEnabled binding.trackersListTitle.isVisible = state.isBlockingActivated -- cgit v1.2.1