From 41480b04ee31e8e694d370184c15de8c4dce03d0 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 1 Mar 2022 08:06:10 +0000 Subject: Hide systems app behind parameter app in lists. --- .../e/privacycentralapp/DependencyContainer.kt | 13 ++- .../data/repositories/AppListsRepository.kt | 104 +++++++++++++++++++++ .../domain/usecases/AppListUseCase.kt | 103 +------------------- .../domain/usecases/IpScramblingStateUseCase.kt | 40 ++++++-- .../domain/usecases/TrackersStateUseCase.kt | 31 +++++- .../domain/usecases/TrackersStatisticsUseCase.kt | 10 +- .../features/trackers/TrackersFeature.kt | 2 +- .../trackers/apptrackers/AppTrackersFeature.kt | 6 +- .../trackers/apptrackers/AppTrackersFragment.kt | 1 - .../trackers/apptrackers/ToggleTrackersAdapter.kt | 9 +- 10 files changed, 195 insertions(+), 124 deletions(-) create mode 100644 app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt (limited to 'app/src/main/java/foundation') diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index c8d7fb2..76a9539 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -20,6 +20,7 @@ package foundation.e.privacycentralapp import android.app.Application import android.content.Context import android.os.Process +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.data.repositories.TrackersRepository import foundation.e.privacycentralapp.domain.usecases.AppListUseCase @@ -73,22 +74,26 @@ class DependencyContainer constructor(val app: Application) { // Repositories private val localStateRepository by lazy { LocalStateRepository(context) } private val trackersRepository by lazy { TrackersRepository(context) } + private val appListsRepository by lazy { AppListsRepository(permissionsModule, context, GlobalScope) } // Usecases private val getQuickPrivacyStateUseCase by lazy { GetQuickPrivacyStateUseCase(localStateRepository) } private val ipScramblingStateUseCase by lazy { - IpScramblingStateUseCase(ipScramblerModule, permissionsModule, appDesc, localStateRepository, GlobalScope) + IpScramblingStateUseCase( + ipScramblerModule, permissionsModule, appDesc, localStateRepository, + appListsRepository, GlobalScope + ) } - private val appListUseCase = AppListUseCase(permissionsModule, blockTrackersPrivacyModule, GlobalScope) + private val appListUseCase = AppListUseCase(appListsRepository) private val trackersStatisticsUseCase by lazy { - TrackersStatisticsUseCase(trackTrackersPrivacyModule, context.resources) + TrackersStatisticsUseCase(trackTrackersPrivacyModule, appListsRepository, context.resources) } private val trackersStateUseCase by lazy { - TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, permissionsModule, localStateRepository, trackersRepository, GlobalScope) + TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, permissionsModule, localStateRepository, trackersRepository, appListsRepository, GlobalScope) } private val fakeLocationStateUseCase by lazy { 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 new file mode 100644 index 0000000..4718923 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt @@ -0,0 +1,104 @@ +/* + * 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.data.repositories + +import android.Manifest +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import foundation.e.privacymodules.permissions.PermissionsPrivacyModule +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class AppListsRepository( + private val permissionsModule: PermissionsPrivacyModule, + private val context: Context, + private val coroutineScope: CoroutineScope +) { + val dummySystemApp = permissionsModule.getApplicationDescription("com.android.settings") + + fun getVisibleApps(): Flow> { + coroutineScope.launch { + val (visible, hidden) = splitVisibleToHidden(getAppsUsingInternet()) + appDescriptions.emit( + visible.map { it.toApplicationDescription(withIcon = true) } + to hidden.map { it.toApplicationDescription() } + ) + } + return appDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } } + } + fun getHiddenSystemApps(): List { + return appDescriptions.value.second + } + + private val pm get() = context.packageManager + + private val appDescriptions = MutableStateFlow( + emptyList() to emptyList() + ) + + private fun getAppsUsingInternet(): List { + return pm.getInstalledApplications(0) + .filter { + permissionsModule.getPermissions(it.packageName) + .contains(Manifest.permission.INTERNET) + } + } + + private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List): Boolean { + if (app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { + return true + } else if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) { + return true + } else if (launcherApps.contains(app.packageName)) { + return true + } + return false + } + + private fun ApplicationInfo.hasFlag(flag: Int) = (flags and flag) == 1 + + private fun splitVisibleToHidden(apps: List): Pair, List> { + val launcherPackageNames = pm.queryIntentActivities( + Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) }, + 0 + ).mapNotNull { it.activityInfo?.packageName } + return apps.fold( + mutableListOf() to mutableListOf() + ) { + acc, app -> + if (isNotHiddenSystemApp(app, launcherPackageNames)) { + acc.first.add(app) + } else { + acc.second.add(app) + } + acc + } + } + + private fun ApplicationInfo.toApplicationDescription(withIcon: Boolean = true) = ApplicationDescription( + packageName = packageName, + uid = uid, + label = pm.getApplicationLabel(this), + icon = if (withIcon) pm.getApplicationIcon(packageName) else null + ) +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt index acb8a36..4821349 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt @@ -17,112 +17,15 @@ package foundation.e.privacycentralapp.domain.usecases -import android.Manifest -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch class AppListUseCase( - private val permissionsModule: PermissionsPrivacyModule, - private val blockTrackersPrivacyModule: BlockTrackersPrivacyModule, - private val corouteineScope: CoroutineScope + private val appListsRepository: AppListsRepository ) { - private val _appsUsingInternet = MutableStateFlow>(emptyList()) - private val _appsBlockableTrackers = MutableStateFlow>(emptyList()) - init { - corouteineScope.launch { - _appsUsingInternet.value = getAppsUsingInternetList() - } - } - - fun getBlockableApps(): Flow> { - corouteineScope.launch { - _appsBlockableTrackers.value = getBlockableAppsList() - } - return _appsBlockableTrackers - } - - private fun getBlockableAppsList(): List { - return blockTrackersPrivacyModule.getBlockableApps() - .filter { - permissionsModule.getPermissions(it.packageName) - .contains(Manifest.permission.INTERNET) - }.map { - it.icon = permissionsModule.getApplicationIcon(it.packageName) - it - }.sortedWith(object : Comparator { - override fun compare( - p0: ApplicationDescription?, - p1: ApplicationDescription? - ): Int { - return if (p0?.icon != null && p1?.icon != null) { - p0.label.toString().compareTo(p1.label.toString()) - } else if (p0?.icon == null) { - 1 - } else { - -1 - } - } - }) - } - - private fun getInstalledAppsUsingInternetList(): List { - return permissionsModule.getInstalledApplications() - .filter { - permissionsModule.getPermissions(it.packageName) - .contains(Manifest.permission.INTERNET) - }.map { - it.icon = permissionsModule.getApplicationIcon(it.packageName) - it - }.sortedWith(object : Comparator { - override fun compare( - p0: ApplicationDescription?, - p1: ApplicationDescription? - ): Int { - return if (p0?.icon != null && p1?.icon != null) { - p0.label.toString().compareTo(p1.label.toString()) - } else if (p0?.icon == null) { - 1 - } else { - -1 - } - } - }) - } - fun getAppsUsingInternet(): Flow> { - corouteineScope.launch { - _appsUsingInternet.value = getAppsUsingInternetList() - } - return _appsUsingInternet - } - - private fun getAppsUsingInternetList(): List { - return permissionsModule.getAllApplications() - .filter { - permissionsModule.getPermissions(it.packageName) - .contains(Manifest.permission.INTERNET) - }.map { - it.icon = permissionsModule.getApplicationIcon(it.packageName) - it - }.sortedWith(object : Comparator { - override fun compare( - p0: ApplicationDescription?, - p1: ApplicationDescription? - ): Int { - return if (p0?.icon != null && p1?.icon != null) { - p0.label.toString().compareTo(p1.label.toString()) - } else if (p0?.icon == null) { - 1 - } else { - -1 - } - } - }) + return appListsRepository.getVisibleApps() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt index 237e5b2..6cc8e4a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.domain.usecases +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule @@ -36,10 +37,10 @@ class IpScramblingStateUseCase( private val permissionsPrivacyModule: IPermissionsPrivacyModule, private val appDesc: ApplicationDescription, private val localStateRepository: LocalStateRepository, + private val appListsRepository: AppListsRepository, coroutineScope: CoroutineScope ) { - // private val internetPrivacyModeMutableFlow = MutableStateFlow(InternetPrivacyMode.REAL_IP) val internetPrivacyMode: StateFlow = callbackFlow { val listener = object : IIpScramblerModule.Listener { override fun onStatusChanged(newStatus: IIpScramblerModule.Status) { @@ -79,16 +80,41 @@ class IpScramblingStateUseCase( applySettings(true, hideIp) } - val bypassTorApps: Set get() = ipScramblerModule.appList + private fun getHiddenPackageNames(): List { + return appListsRepository.getHiddenSystemApps().map { it.packageName } + } + + val bypassTorApps: Set get() { + var whitelist = ipScramblerModule.appList + + if (getHiddenPackageNames().any { it in whitelist }) { + val mutable = whitelist.toMutableSet() + mutable.removeAll(getHiddenPackageNames()) + mutable.add(appListsRepository.dummySystemApp.packageName) + whitelist = mutable + } + + return whitelist + } fun toggleBypassTor(packageName: String) { - val currentList = bypassTorApps.toMutableSet() - if (currentList.contains(packageName)) { - currentList.remove(packageName) + val visibleList = bypassTorApps.toMutableSet() + val rawList = ipScramblerModule.appList.toMutableSet() + + if (visibleList.contains(packageName)) { + if (packageName == appListsRepository.dummySystemApp.packageName) { + rawList.removeAll(getHiddenPackageNames()) + } else { + rawList.remove(packageName) + } } else { - currentList.add(packageName) + if (packageName == appListsRepository.dummySystemApp.packageName) { + rawList.addAll(getHiddenPackageNames()) + } else { + rawList.add(packageName) + } } - ipScramblerModule.appList = currentList + ipScramblerModule.appList = rawList } private fun applySettings(isQuickPrivacyEnabled: Boolean, isIpScramblingEnabled: Boolean) { 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 e8759cb..16a1a82 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 @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.domain.usecases +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.data.repositories.TrackersRepository import foundation.e.privacymodules.permissions.PermissionsPrivacyModule @@ -36,6 +37,7 @@ class TrackersStateUseCase( private val permissionsPrivacyModule: PermissionsPrivacyModule, private val localStateRepository: LocalStateRepository, private val trackersRepository: TrackersRepository, + private val appListsRepository: AppListsRepository, coroutineScope: CoroutineScope ) { @@ -64,25 +66,44 @@ class TrackersStateUseCase( blockTrackersPrivacyModule.isWhiteListEmpty() } - fun getApplicationPermission(packageName: String): ApplicationDescription { + fun getApplicationDescription(packageName: String): ApplicationDescription { return permissionsPrivacyModule.getApplicationDescription(packageName) } fun isWhitelisted(appUid: Int): Boolean { - return blockTrackersPrivacyModule.isWhitelisted(appUid) + return if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().any { + blockTrackersPrivacyModule.isWhitelisted(it.uid) + } + } else blockTrackersPrivacyModule.isWhitelisted(appUid) } fun getTrackersWhitelistIds(appUid: Int): List { - return blockTrackersPrivacyModule.getWhiteList(appUid).map { it.id } + return if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().fold(mutableSetOf()) { acc, app -> + acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id }) + acc + }.toList() + } else blockTrackersPrivacyModule.getWhiteList(appUid).map { it.id } } fun toggleAppWhitelist(appUid: Int, isWhitelisted: Boolean) { - blockTrackersPrivacyModule.setWhiteListed(appUid, isWhitelisted) + if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().forEach { + blockTrackersPrivacyModule.setWhiteListed(it.uid, isWhitelisted) + } + } else blockTrackersPrivacyModule.setWhiteListed(appUid, isWhitelisted) + updateAllTrackersBlockedState() } fun blockTracker(appUid: Int, tracker: Tracker, isBlocked: Boolean) { - blockTrackersPrivacyModule.setWhiteListed(tracker, appUid, !isBlocked) + if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().forEach { + blockTrackersPrivacyModule.setWhiteListed(tracker, it.uid, !isBlocked) + } + } else blockTrackersPrivacyModule.setWhiteListed(tracker, appUid, !isBlocked) + updateAllTrackersBlockedState() } } 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 ad5c86c..9a8b12a 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 @@ -19,6 +19,7 @@ package foundation.e.privacycentralapp.domain.usecases import android.content.res.Resources import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule import foundation.e.privacymodules.trackers.Tracker @@ -31,6 +32,7 @@ import java.time.temporal.ChronoUnit class TrackersStatisticsUseCase( private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, + private val appListsRepository: AppListsRepository, private val resources: Resources ) { @@ -114,6 +116,12 @@ class TrackersStatisticsUseCase( } fun getTrackers(appUid: Int): List { - return trackTrackersPrivacyModule.getTrackersForApp(appUid) + val trackers = if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().map { + trackTrackersPrivacyModule.getTrackersForApp(it.uid) + }.flatten().distinctBy { it.id } + } else trackTrackersPrivacyModule.getTrackersForApp(appUid) + + return trackers.sortedBy { it.label.lowercase() } } } 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 00e3fb7..e2eb58d 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 @@ -119,7 +119,7 @@ class TrackersFeature( ) } }, - appListUseCase.getBlockableApps().map { apps -> + appListUseCase.getAppsUsingInternet().map { apps -> Effect.AvailableAppsListEffect(apps) }, trackersStatisticsUseCase.listenUpdates().map { 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 ff0c9db..16cd4a0 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 @@ -118,7 +118,7 @@ class AppTrackersFeature( when (action) { is Action.InitAction -> { val appDesc = - trackersStateUseCase.getApplicationPermission(action.packageName) + trackersStateUseCase.getApplicationDescription(action.packageName) merge( flow { @@ -154,9 +154,7 @@ class AppTrackersFeature( emit( Effect.AppTrackersBlockingActivatedEffect( - !trackersStateUseCase.isWhitelisted( - appUid - ) + !trackersStateUseCase.isWhitelisted(appUid) ) ) } 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 1f339ee..440edf7 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 @@ -97,7 +97,6 @@ class AppTrackersFragment : super.onViewCreated(view, savedInstanceState) binding = ApptrackersFragmentBinding.bind(view) - // TODO: crash sqlite ? binding.blockAllToggle.setOnClickListener { viewModel.submitAction(Action.BlockAllToggleAction(binding.blockAllToggle.isChecked)) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt index 0ab3987..580a60c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Switch import android.widget.TextView +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import foundation.e.privacycentralapp.R import foundation.e.privacymodules.trackers.Tracker @@ -38,11 +39,13 @@ class ToggleTrackersAdapter( val title: TextView = view.findViewById(R.id.title) val toggle: Switch = view.findViewById(R.id.toggle) + val toggleOverlay: View = view.findViewById(R.id.toggle_clicker) fun bind(item: Pair, isEnabled: Boolean) { title.text = item.first.label toggle.isChecked = item.second toggle.isEnabled = isEnabled + toggleOverlay.isVisible = !isEnabled } } @@ -58,9 +61,13 @@ class ToggleTrackersAdapter( val view = LayoutInflater.from(parent.context) .inflate(itemsLayout, parent, false) val holder = ViewHolder(view) - holder.itemView.setOnClickListener { + holder.toggle.setOnClickListener { listener(dataSet[holder.adapterPosition].first, holder.toggle.isChecked) } + holder.toggleOverlay.setOnClickListener { + listener(dataSet[holder.adapterPosition].first, false) + } + return holder } -- cgit v1.2.1