/* * 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 * 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.advancedprivacy.permissions.externalinterfaces import android.annotation.TargetApi import android.app.AppOpsManager 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.graphics.drawable.Drawable 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.advancedprivacy.domain.entities.AppOpModes import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import foundation.e.advancedprivacy.domain.entities.ProfileType.MAIN import foundation.e.advancedprivacy.domain.entities.ProfileType.WORK import foundation.e.advancedprivacy.externalinterfaces.permissions.PermissionsPrivacyModuleBase /** * Implements [IPermissionsPrivacyModule] with all privileges of a system app. */ class PermissionsPrivacyModuleImpl(context: Context) : PermissionsPrivacyModuleBase(context) { private val appOpsManager: AppOpsManager get() = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager /** * @see IPermissionsPrivacyModule.toggleDangerousPermission * Always return true, permission is set using privileged capacities. */ override fun toggleDangerousPermission( appDesc: ApplicationDescription, permissionName: String, grant: Boolean ): Boolean { try { if (grant) { context.packageManager.grantRuntimePermission( appDesc.packageName, permissionName, android.os.Process.myUserHandle() ) } else { context.packageManager.revokeRuntimePermission( appDesc.packageName, permissionName, android.os.Process.myUserHandle() ) } } catch (e: Exception) { Log.e("Permissions-e", "Exception while setting permission", e) return false } return true } override fun setAppOpMode( appDesc: ApplicationDescription, appOpPermissionName: String, status: AppOpModes ): Boolean { val op = strOpToOp(appOpPermissionName) if (op != OP_NONE) { appOpsManager.setMode(op, appDesc.uid, appDesc.packageName, status.modeValue) } return true } override fun getApplications( filter: ((PackageInfo) -> Boolean)? ): List { val pm = context.packageManager val mainUserId = UserHandle.myUserId() val workProfileId = getWorkProfile()?.id val userIds = listOf(mainUserId, workProfileId).filterNotNull() return userIds.map { profileId -> pm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, profileId) .filter { filter?.invoke(it) ?: true } .map { buildApplicationDescription( appInfo = it.applicationInfo, profileId = profileId, profileType = if (profileId == mainUserId) MAIN else WORK ) } }.flatten() } override fun getApplicationIcon(app: ApplicationDescription): Drawable? { return if (app.profileType == WORK) { getWorkProfile()?.let { workProfile -> val pm = context.packageManager getApplicationIcon( pm.getApplicationInfoAsUser(app.packageName, 0, workProfile.id) )?.let { pm.getUserBadgedIcon(it, workProfile.getUserHandle()) } } } else getApplicationIcon(app.packageName) } override fun setBlockable(notificationChannel: NotificationChannel) { when (Build.VERSION.SDK_INT) { 29 -> notificationChannel.setBlockableSystem(true) 30, 31, 32, 33 -> notificationChannel.setBlockable(true) else -> { Log.e("Permissions-e", "Bad android sdk version") } } } override fun setVpnPackageAuthorization(packageName: String): Boolean { return when (Build.VERSION.SDK_INT) { 29 -> setVpnPackageAuthorizationSDK29(packageName) 30 -> setVpnPackageAuthorizationSDK30(packageName) 31, 32, 33 -> setVpnPackageAuthorizationSDK32(packageName) else -> { Log.e("Permissions-e", "Bad android sdk version") false } } } @TargetApi(29) private fun setVpnPackageAuthorizationSDK29(packageName: String): Boolean { val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE) ) try { if (service.prepareVpn(null, packageName, UserHandle.myUserId())) { // Authorize this app to initiate VPN connections in the future without user // intervention. service.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), true) return true } } catch (e: java.lang.Exception) { Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) } catch (e: NoSuchMethodError) { Log.e("Permissions-e", "Bad android sdk version", e) } return false } @TargetApi(30) private fun setVpnPackageAuthorizationSDK30(packageName: String): Boolean { val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE) ) try { if (service.prepareVpn(null, packageName, UserHandle.myUserId())) { // Authorize this app to initiate VPN connections in the future without user // intervention. service.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), TYPE_VPN_SERVICE) return true } } catch (e: java.lang.Exception) { Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) } catch (e: NoSuchMethodError) { Log.e("Permissions-e", "Bad android sdk version", e) } return false } @TargetApi(31) private fun setVpnPackageAuthorizationSDK32(packageName: String): Boolean { val vpnManager = context.getSystemService(Context.VPN_MANAGEMENT_SERVICE) as VpnManager try { if (vpnManager.prepareVpn(null, packageName, UserHandle.myUserId())) { // Authorize this app to initiate VPN connections in the future without user // intervention. vpnManager.setVpnPackageAuthorization(packageName, UserHandle.myUserId(), TYPE_VPN_SERVICE) return true } } catch (e: java.lang.Exception) { Log.e("Permissions-e", "Exception while setting VpnPackageAuthorization", e) } catch (e: NoSuchMethodError) { Log.e("Permissions-e", "Bad android sdk version", e) } return false } override fun getAlwaysOnVpnPackage(): String? { return when (Build.VERSION.SDK_INT) { 29, 30 -> getAlwaysOnVpnPackageSDK29() 31, 32, 33 -> getAlwaysOnVpnPackageSDK32() else -> { Log.e("Permissions-e", "Bad android sdk version") null } } } @TargetApi(29) private fun getAlwaysOnVpnPackageSDK29(): String? { val service: IConnectivityManager = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE) ) return try { service.getAlwaysOnVpnPackage(UserHandle.myUserId()) } catch (e: java.lang.Exception) { Log.e("Permissions-e", "Bad android sdk version ", e) return null } } @TargetApi(31) private fun getAlwaysOnVpnPackageSDK32(): String? { val vpnManager = context.getSystemService(Context.VPN_MANAGEMENT_SERVICE) as VpnManager return try { vpnManager.getAlwaysOnVpnPackageForUser(UserHandle.myUserId()) } catch (e: java.lang.Exception) { Log.e("Permissions-e", "Bad android sdk version ", e) return null } } 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 } }