diff options
17 files changed, 286 insertions, 71 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index e6d4c42..6ad84a7 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -182,7 +182,6 @@ class ViewModelsFactory( TrackersViewModel::class.java -> TrackersViewModel( - getQuickPrivacyStateUseCase = getQuickPrivacyStateUseCase, trackersStatisticsUseCase = trackersStatisticsUseCase ) FakeLocationViewModel::class.java -> diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt index 16b0144..7b09c51 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/AppsAdapter.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -28,11 +28,11 @@ import foundation.e.privacycentralapp.domain.entities.AppWithCounts class AppsAdapter( private val itemsLayout: Int, - private val listener: (String) -> Unit + private val listener: (Int) -> Unit ) : RecyclerView.Adapter<AppsAdapter.ViewHolder>() { - class ViewHolder(view: View, private val listener: (String) -> Unit) : RecyclerView.ViewHolder(view) { + class ViewHolder(view: View, private val listener: (Int) -> Unit) : RecyclerView.ViewHolder(view) { val appName: TextView = view.findViewById(R.id.title) val counts: TextView = view.findViewById(R.id.counts) val icon: ImageView = view.findViewById(R.id.icon) @@ -46,7 +46,7 @@ class AppsAdapter( ) icon.setImageDrawable(item.icon) - itemView.setOnClickListener { listener(item.packageName) } + itemView.setOnClickListener { listener(item.uid) } } } 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 febda91..65ae478 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2022 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -21,11 +21,13 @@ import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager +import android.content.pm.PackageInfo import foundation.e.privacycentralapp.R import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map @@ -48,24 +50,67 @@ class AppListsRepository( icon = context.getDrawable(R.drawable.ic_e_app_logo) ) - fun getVisibleApps(): Flow<List<ApplicationDescription>> { - coroutineScope.launch { - val (visible, hidden) = splitVisibleToHidden(getAppsUsingInternet()) - appDescriptions.emit( - Pair( - visible.map { permissionsModule.buildApplicationDescription(it, withIcon = true) } + dummySystemApp, - hidden.map { permissionsModule.buildApplicationDescription(it, withIcon = false) }, - ) - ) + private suspend fun fetchAppDescriptions() { + val launcherPackageNames = pm.queryIntentActivities( + Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) }, + 0 + ).mapNotNull { it.activityInfo?.packageName } + + val visibleAppsFilter = { packageInfo: PackageInfo -> + hasInternetPermission(packageInfo) && + isNotHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames) + } + + val hiddenAppsFilter = { packageInfo: PackageInfo -> + hasInternetPermission(packageInfo) && + !isNotHiddenSystemApp(packageInfo.applicationInfo, launcherPackageNames) + } + + val visibleApps = permissionsModule.getApplications(visibleAppsFilter, true) + val hiddenApps = permissionsModule.getApplications(hiddenAppsFilter, false) + + val workProfileVisibleApps = permissionsModule.getWorkProfileApplications(visibleAppsFilter, true) + val workProfileHiddenApps = permissionsModule.getWorkProfileApplications(hiddenAppsFilter, false) + + appDescriptions.emit((visibleApps + dummySystemApp) to hiddenApps) + allProfilesAppDescriptions.emit( + (visibleApps + workProfileVisibleApps + dummySystemApp) + to (hiddenApps + workProfileHiddenApps) + ) + } + + private var refreshAppJob: Job? = null + private fun refreshAppDescriptions() { + if (refreshAppJob != null) { + return + } else { + refreshAppJob = coroutineScope.launch(Dispatchers.IO) { + fetchAppDescriptions() + refreshAppJob = null + } } + } + + fun getVisibleApps(): Flow<List<ApplicationDescription>> { + refreshAppDescriptions() return appDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } } } + fun getHiddenSystemApps(): List<ApplicationDescription> { return appDescriptions.value.second } - fun getVisibleAndHiddenApps(): Flow<List<ApplicationDescription>> = getVisibleApps() - .map { it + getHiddenSystemApps() } + fun getAllProfilesVisibleApps(): Flow<List<ApplicationDescription>> { + refreshAppDescriptions() + return allProfilesAppDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } } + } + + fun getAllProfilesHiddenSystemApps(): List<ApplicationDescription> { + return allProfilesAppDescriptions.value.second + } + + fun getAllApps(): Flow<List<ApplicationDescription>> = getAllProfilesVisibleApps() + .map { it + getAllProfilesHiddenSystemApps() } fun getApplicationDescription(packageName: String): ApplicationDescription? { return appDescriptions.value.first.find { it.packageName == packageName } @@ -77,7 +122,7 @@ class AppListsRepository( fun foldForHiddenSystemApp(appUid: Int, appValueGetter: (Int) -> Int): Int { return if (appUid == dummySystemApp.uid) { - getHiddenSystemApps().fold(0) { acc, app -> + getAllProfilesHiddenSystemApps().fold(0) { acc, app -> acc + appValueGetter(app.uid) } } else appValueGetter(appUid) @@ -92,14 +137,18 @@ class AppListsRepository( ) ) - private fun getAppsUsingInternet(): List<ApplicationInfo> { - return pm.getInstalledPackages(PackageManager.GET_PERMISSIONS).filter { - it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true - }.map { - it.applicationInfo - } + private val allProfilesAppDescriptions = MutableStateFlow( + Pair( + emptyList<ApplicationDescription>(), + emptyList<ApplicationDescription>() + ) + ) + + private fun hasInternetPermission(packageInfo: PackageInfo): Boolean { + return packageInfo.requestedPermissions?.contains(Manifest.permission.INTERNET) == true } + @Suppress("ReturnCount") private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean { if (app.packageName == PNAME_SETTINGS) { return false @@ -116,22 +165,4 @@ class AppListsRepository( } private fun ApplicationInfo.hasFlag(flag: Int) = (flags and flag) == 1 - - private fun splitVisibleToHidden(apps: List<ApplicationInfo>): Pair<List<ApplicationInfo>, List<ApplicationInfo>> { - val launcherPackageNames = pm.queryIntentActivities( - Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) }, - 0 - ).mapNotNull { it.activityInfo?.packageName } - return apps.fold( - mutableListOf<ApplicationInfo>() to mutableListOf<ApplicationInfo>() - ) { - acc, app -> - if (isNotHiddenSystemApp(app, launcherPackageNames)) { - acc.first.add(app) - } else { - acc.second.add(app) - } - acc - } - } } 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 8b37152..11f0466 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -68,7 +68,7 @@ class TrackersStateUseCase( fun getTrackersWhitelistIds(appUid: Int): List<String> { return if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getHiddenSystemApps().fold(mutableSetOf<String>()) { acc, app -> + appListsRepository.getAllProfilesHiddenSystemApps().fold(mutableSetOf<String>()) { acc, app -> acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id }) acc }.toList() @@ -77,7 +77,7 @@ class TrackersStateUseCase( fun toggleAppWhitelist(appUid: Int, isWhitelisted: Boolean) { if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getHiddenSystemApps().forEach { + appListsRepository.getAllProfilesHiddenSystemApps().forEach { blockTrackersPrivacyModule.setWhiteListed(it.uid, isWhitelisted) } } else blockTrackersPrivacyModule.setWhiteListed(appUid, isWhitelisted) @@ -87,7 +87,7 @@ class TrackersStateUseCase( fun blockTracker(appUid: Int, tracker: Tracker, isBlocked: Boolean) { if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getHiddenSystemApps().forEach { + appListsRepository.getAllProfilesHiddenSystemApps().forEach { blockTrackersPrivacyModule.setWhiteListed(tracker, it.uid, !isBlocked) } } else blockTrackersPrivacyModule.setWhiteListed(tracker, appUid, !isBlocked) @@ -107,7 +107,7 @@ fun isWhitelisted( blockTrackersPrivacyModule: IBlockTrackersPrivacyModule ): Boolean { return if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getHiddenSystemApps().any { + appListsRepository.getAllProfilesHiddenSystemApps().any { blockTrackersPrivacyModule.isWhitelisted(it.uid) } } else 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 57ab1a4..7323769 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -47,7 +47,7 @@ class TrackersStatisticsUseCase( private val resources: Resources ) { fun initAppList() { - appListsRepository.getVisibleApps() + appListsRepository.getAllProfilesVisibleApps() } private fun rawUpdates(): Flow<Unit> = callbackFlow { @@ -147,7 +147,7 @@ class TrackersStatisticsUseCase( fun getTrackers(appUid: Int): List<Tracker> { val trackers = if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getHiddenSystemApps().map { + appListsRepository.getAllProfilesHiddenSystemApps().map { trackTrackersPrivacyModule.getTrackersForApp(it.uid) }.flatten().distinctBy { it.id } } else trackTrackersPrivacyModule.getTrackersForApp(appUid) @@ -159,7 +159,7 @@ class TrackersStatisticsUseCase( val trackers: List<Tracker> val whiteListedTrackersIds: Set<String> if (appUid == appListsRepository.dummySystemApp.uid) { - val hiddenApps = appListsRepository.getHiddenSystemApps() + val hiddenApps = appListsRepository.getAllProfilesHiddenSystemApps() trackers = trackTrackersPrivacyModule.getTrackers(hiddenApps.map { it.uid }) whiteListedTrackersIds = hiddenApps.fold(HashSet<String>()) { acc, app -> @@ -177,7 +177,7 @@ class TrackersStatisticsUseCase( fun getCalls(appUid: Int): Pair<Int, Int> { return if (appUid == appListsRepository.dummySystemApp.uid) { - appListsRepository.getHiddenSystemApps().map { + appListsRepository.getAllProfilesHiddenSystemApps().map { trackTrackersPrivacyModule.getPastDayTrackersCallsForApp(it.uid) }.reduce { (accBlocked, accLeaked), (blocked, leaked) -> accBlocked + blocked to accLeaked + leaked @@ -190,7 +190,7 @@ class TrackersStatisticsUseCase( val hiddenAppsTrackersWithWhiteList = getTrackersWithWhiteList(appListsRepository.dummySystemApp.uid) - return appListsRepository.getVisibleApps() + return appListsRepository.getAllProfilesVisibleApps() .map { apps -> val callsByApp = trackTrackersPrivacyModule.getPastDayTrackersCallsByApps() apps.map { app -> @@ -233,7 +233,7 @@ class TrackersStatisticsUseCase( fun getNonBlockedTrackersCount(): Flow<Int> { return if (blockTrackersPrivacyModule.isBlockingEnabled()) - appListsRepository.getVisibleAndHiddenApps().map { apps -> + appListsRepository.getAllApps().map { apps -> val whiteListedTrackers = mutableSetOf<Tracker>() val whiteListedAppUids = blockTrackersPrivacyModule.getWhiteListedApp() apps.forEach { app -> 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 83359e1..cb32c2c 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -78,9 +78,9 @@ class TrackersFragment : binding.apps.apply { layoutManager = LinearLayoutManager(requireContext()) setHasFixedSize(true) - adapter = AppsAdapter(R.layout.trackers_item_app) { packageName -> + adapter = AppsAdapter(R.layout.trackers_item_app) { appUid -> viewModel.submitAction( - TrackersViewModel.Action.ClickAppAction(packageName) + TrackersViewModel.Action.ClickAppAction(appUid) ) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt index 2cdfabc..8b5cc32 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -21,7 +21,6 @@ import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.privacycentralapp.domain.entities.AppWithCounts -import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -35,7 +34,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class TrackersViewModel( - private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, private val trackersStatisticsUseCase: TrackersStatisticsUseCase ) : ViewModel() { @@ -79,7 +77,7 @@ class TrackersViewModel( } private suspend fun actionClickApp(action: Action.ClickAppAction) { - state.value.apps?.find { it.packageName == action.packageName }?.let { + state.value.apps?.find { it.uid == action.appUid }?.let { _singleEvents.emit(SingleEvent.OpenAppDetailsEvent(it)) } } @@ -91,7 +89,7 @@ class TrackersViewModel( } sealed class Action { - data class ClickAppAction(val packageName: String) : Action() + data class ClickAppAction(val appUid: Int) : Action() object ClickLearnMore : Action() } } diff --git a/build.gradle b/build.gradle index 83197c3..812fbfb 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ buildscript { 'targetSdk' : 31, 'version' : [ 'major': 1, - 'minor': 7, + 'minor': 8, 'patch': 0, ], ] diff --git a/permissionse/libs/hidden-apis-stub/build.gradle b/permissionse/libs/hidden-apis-stub/build.gradle index b239e6f..2043edc 100644 --- a/permissionse/libs/hidden-apis-stub/build.gradle +++ b/permissionse/libs/hidden-apis-stub/build.gradle @@ -20,7 +20,7 @@ plugins { } android { - compileSdk 31 + compileSdkVersion buildConfig.compileSdk } diff --git a/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java b/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java index 1c4f527..b7209ef 100644 --- a/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java +++ b/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/PackageManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2022 MURENA SAS * * 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 @@ -18,16 +18,20 @@ package android.content.pm; import android.annotation.TargetApi; +import android.graphics.drawable.Drawable; import android.os.UserHandle; import androidx.annotation.DeprecatedSinceApi; import androidx.annotation.NonNull; import androidx.annotation.RequiresPermission; +import java.util.List; + // Stub based on: // https://gitlab.e.foundation/e/os/android_frameworks_base/-/blob/[SDK_VERSION]/core/java/android/content/pm/PackageManager.java public abstract class PackageManager { + @TargetApi(29) @DeprecatedSinceApi( api = 33, @@ -51,4 +55,23 @@ public abstract class PackageManager { @NonNull String permissionName, @NonNull UserHandle user ); + + @TargetApi(29) + @DeprecatedSinceApi( + api = 33, + message = "Check disponibility in SDK33" + ) + @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") + public abstract List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId); + + // Public + public abstract List<PackageInfo> getInstalledPackages(int flags); + + @NonNull + public abstract Drawable getUserBadgedIcon( + @NonNull Drawable drawable, + @NonNull UserHandle user + ); + + public static final int GET_PERMISSIONS = 0x00001000; } diff --git a/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/UserInfo.java b/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/UserInfo.java new file mode 100644 index 0000000..9418197 --- /dev/null +++ b/permissionse/libs/hidden-apis-stub/src/main/java/android/content/pm/UserInfo.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 MURENA SAS + * + * 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 <https://www.gnu.org/licenses/>. + */ + +package android.content.pm; + +import android.annotation.TargetApi; +import android.os.UserHandle; + +import androidx.annotation.DeprecatedSinceApi; + +public class UserInfo { + public int id; + + @TargetApi(29) + @DeprecatedSinceApi( + api = 33, + message = "Check availability in SDK33" + ) + public UserHandle getUserHandle() { + return null; + } +} diff --git a/permissionse/libs/hidden-apis-stub/src/main/java/android/os/UserManager.java b/permissionse/libs/hidden-apis-stub/src/main/java/android/os/UserManager.java new file mode 100644 index 0000000..d2e80d4 --- /dev/null +++ b/permissionse/libs/hidden-apis-stub/src/main/java/android/os/UserManager.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 MURENA SAS + * + * 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 <https://www.gnu.org/licenses/>. + */ + +package android.os; + +import android.annotation.TargetApi; +import android.content.pm.UserInfo; + +import androidx.annotation.DeprecatedSinceApi; +import androidx.annotation.RequiresPermission; +import java.util.List; + +public class UserManager { + + @TargetApi(29) + @DeprecatedSinceApi( + api = 33, + message = "Check availability in SDK33" + ) + @RequiresPermission("android.permission.MANAGE_USERS") + public List<UserInfo> getProfiles(int userHandle) { + return null; + } + + @TargetApi(29) + @DeprecatedSinceApi( + api = 33, + message = "Check availability in SDK33" + ) + @RequiresPermission("android.permission.MANAGE_USERS") + public boolean isManagedProfile(int userId) { + return false; + } +} diff --git a/permissionse/src/main/AndroidManifest.xml b/permissionse/src/main/AndroidManifest.xml index 428a612..3625087 100644 --- a/permissionse/src/main/AndroidManifest.xml +++ b/permissionse/src/main/AndroidManifest.xml @@ -35,4 +35,8 @@ tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN" tools:ignore="ProtectedPermissions" /> + <uses-permission android:name="android.permission.MANAGE_USERS" + tools:ignore="ProtectedPermissions" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" + tools:ignore="ProtectedPermissions" /> </manifest> diff --git a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt index c07f367..c2e3e2c 100644 --- a/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt +++ b/permissionse/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -23,12 +23,16 @@ import android.app.AppOpsManager.OP_NONE import android.app.AppOpsManager.strOpToOp import android.app.NotificationChannel import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.UserInfo import android.net.IConnectivityManager import android.net.VpnManager import android.net.VpnManager.TYPE_VPN_SERVICE import android.os.Build import android.os.ServiceManager import android.os.UserHandle +import android.os.UserManager import android.util.Log import foundation.e.privacymodules.permissions.data.AppOpModes import foundation.e.privacymodules.permissions.data.ApplicationDescription @@ -157,6 +161,45 @@ class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(con return false } + private fun getWorkProfile(): UserInfo? { + val userManager: UserManager = context.getSystemService(UserManager::class.java) + val userId = UserHandle.myUserId() + for (user in userManager.getProfiles(UserHandle.myUserId())) { + if (user.id != userId && userManager.isManagedProfile(user.id)) { + return user + } + } + return null + } + + override fun getApplications( + filter: ((PackageInfo) -> Boolean)?, + withIcon: Boolean + ): List<ApplicationDescription> { + return context.packageManager + .getInstalledPackages(PackageManager.GET_PERMISSIONS) + .filter { filter?.invoke(it) ?: true } + .map { buildApplicationDescription(it.applicationInfo, withIcon = withIcon) } + } + + override fun getWorkProfileApplications( + filter: ((PackageInfo) -> Boolean)?, + withIcon: Boolean + ): List<ApplicationDescription> { + val pm = context.packageManager + return getWorkProfile()?.let { workProfile -> + pm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, workProfile.id) + .filter { filter?.invoke(it) ?: true } + .map { + val appDesc = buildApplicationDescription(it.applicationInfo, withIcon = withIcon) + appDesc.icon = appDesc.icon?.let { + pm.getUserBadgedIcon(it, workProfile.getUserHandle()) + } + appDesc + } + } ?: emptyList() + } + override fun getAlwaysOnVpnPackage(): String? { return when (Build.VERSION.SDK_INT) { 29, 30 -> getAlwaysOnVpnPackageSDK29() diff --git a/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt b/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt index 52dfd08..da7c73e 100644 --- a/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt +++ b/permissionsstandalone/src/main/java/foundation/e/privacymodules/permissions/PermissionsPrivacyModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2022 MURENA SAS * * 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 @@ -19,6 +19,8 @@ package foundation.e.privacymodules.permissions import android.app.NotificationChannel import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager import foundation.e.privacymodules.permissions.data.AppOpModes import foundation.e.privacymodules.permissions.data.ApplicationDescription @@ -26,6 +28,22 @@ import foundation.e.privacymodules.permissions.data.ApplicationDescription * Implements [IPermissionsPrivacyModule] using only API authorized on the PlayStore. */ class PermissionsPrivacyModule(context: Context) : APermissionsPrivacyModule(context) { + override fun getApplications( + filter: ((PackageInfo) -> Boolean)?, + withIcon: Boolean + ): List<ApplicationDescription> { + return context.packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS) + .filter { filter?.invoke(it) == true } + .map { buildApplicationDescription(it.applicationInfo, withIcon = withIcon) } + } + + override fun getWorkProfileApplications( + filter: ((PackageInfo) -> Boolean)?, + withIcon: Boolean + ): List<ApplicationDescription> { + return emptyList() + } + /** * @see IPermissionsPrivacyModule.toggleDangerousPermission * Return an ManualAction to go toggle manually the permission in the ap page of the settings. diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt index 9d7e675..d0e2e75 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt +++ b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/APermissionsPrivacyModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -150,7 +150,7 @@ abstract class APermissionsPrivacyModule(protected val context: Context) : IPerm packageName = appInfo.packageName, uid = appInfo.uid, label = getAppLabel(appInfo), - icon = if (withIcon) getApplicationIcon(appInfo.packageName) else null + icon = if (withIcon) getApplicationIcon(appInfo) else null ) } @@ -158,6 +158,10 @@ abstract class APermissionsPrivacyModule(protected val context: Context) : IPerm return context.packageManager.getApplicationLabel(appInfo) } + private fun getApplicationIcon(appInfo: ApplicationInfo): Drawable? { + return context.packageManager.getApplicationIcon(appInfo) + } + override fun getApplicationIcon(packageName: String): Drawable? { return context.packageManager.getApplicationIcon(packageName) } diff --git a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt index ff0b3d7..b64762f 100644 --- a/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt +++ b/privacymodule-api/src/main/java/foundation/e/privacymodules/permissions/IPermissionsPrivacyModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 E FOUNDATION + * Copyright (C) 2021 E FOUNDATION, 2022 MURENA SAS * * 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 @@ -19,6 +19,7 @@ package foundation.e.privacymodules.permissions import android.app.NotificationChannel import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo import android.graphics.drawable.Drawable import foundation.e.privacymodules.permissions.data.AppOpModes import foundation.e.privacymodules.permissions.data.ApplicationDescription @@ -34,6 +35,16 @@ interface IPermissionsPrivacyModule { withIcon: Boolean = true ): ApplicationDescription + fun getApplications( + filter: ((PackageInfo) -> Boolean)?, + withIcon: Boolean + ): List<ApplicationDescription> + + fun getWorkProfileApplications( + filter: ((PackageInfo) -> Boolean)?, + withIcon: Boolean + ): List<ApplicationDescription> + /** * List the installed application on the device which have not the FLAGS_SYSTEM. * @return list of filled up [ApplicationDescription] |