/*
* 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
}
}