1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.permissioncontroller.permission.utils.v35 18 19 import android.Manifest 20 import android.app.Application 21 import android.companion.virtual.VirtualDeviceManager 22 import android.content.Context 23 import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME 24 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED 25 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED 26 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED 27 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET 28 import android.os.Build 29 import android.permission.PermissionManager 30 import android.provider.Settings 31 import androidx.annotation.ChecksSdkIntAtLeast 32 import com.android.modules.utils.build.SdkLevel 33 import com.android.permissioncontroller.DeviceUtils 34 import com.android.permissioncontroller.permission.utils.ContextCompat 35 36 object MultiDeviceUtils { 37 const val DEFAULT_REMOTE_DEVICE_NAME = "remote device" 38 39 /** 40 * Defines what runtime permissions are device aware. This can be replaced with an API from VDM 41 * which can take device's capabilities into account 42 */ 43 // TODO: b/298661870 - Use new API to get the list of device aware permissions 44 private val DEVICE_AWARE_PERMISSIONS: Set<String> = 45 setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) 46 47 private const val DEVICE_AWARE_PERMISSION_FLAG_MASK = 48 FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED or 49 FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED or 50 FLAG_PERMISSION_ONE_TIME or 51 FLAG_PERMISSION_USER_SET or 52 FLAG_PERMISSION_USER_FIXED 53 54 @JvmStatic 55 fun isDeviceAwarePermissionSupported(context: Context): Boolean = 56 SdkLevel.isAtLeastV() && 57 !(DeviceUtils.isTelevision(context) || 58 DeviceUtils.isAuto(context) || 59 DeviceUtils.isWear(context)) 60 61 @JvmStatic 62 fun isPermissionDeviceAware(permission: String): Boolean = 63 permission in DEVICE_AWARE_PERMISSIONS 64 65 @JvmStatic 66 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 67 fun isPermissionDeviceAware(context: Context, deviceId: Int, permission: String): Boolean { 68 if (!SdkLevel.isAtLeastV()) { 69 return false 70 } 71 72 if (permission !in DEVICE_AWARE_PERMISSIONS) { 73 return false 74 } 75 76 val vdm = context.getSystemService(VirtualDeviceManager::class.java) ?: return false 77 val virtualDevice = vdm.getVirtualDevice(deviceId) ?: return false 78 79 return when (permission) { 80 Manifest.permission.CAMERA -> virtualDevice.hasCustomCameraSupport() 81 Manifest.permission.RECORD_AUDIO -> virtualDevice.hasCustomAudioInputSupport() 82 else -> false 83 } 84 } 85 86 @JvmStatic 87 fun getDeviceName(context: Context, deviceId: Int): String? { 88 // Pre Android V no permission requests can affect the VirtualDevice, thus return local 89 // device name. 90 if (!SdkLevel.isAtLeastV() || deviceId == ContextCompat.DEVICE_ID_DEFAULT) { 91 return Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) 92 } 93 val vdm: VirtualDeviceManager? = context.getSystemService(VirtualDeviceManager::class.java) 94 if (vdm != null) { 95 val virtualDevice = vdm.getVirtualDevice(deviceId) 96 if (virtualDevice != null) { 97 return if (virtualDevice.displayName != null) virtualDevice.displayName.toString() 98 else DEFAULT_REMOTE_DEVICE_NAME 99 } 100 } 101 throw IllegalArgumentException("No device name for device: $deviceId") 102 } 103 104 @JvmStatic 105 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 106 fun isDefaultDeviceId(persistentDeviceId: String?) = 107 !SdkLevel.isAtLeastV() || 108 persistentDeviceId.isNullOrBlank() || 109 persistentDeviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT 110 111 @JvmStatic 112 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 113 fun getDeviceName(context: Context, persistentDeviceId: String): String { 114 if ( 115 !SdkLevel.isAtLeastV() || 116 persistentDeviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT 117 ) { 118 return Settings.Global.getString(context.contentResolver, Settings.Global.DEVICE_NAME) 119 } 120 val vdm: VirtualDeviceManager = 121 context.getSystemService(VirtualDeviceManager::class.java) 122 ?: throw RuntimeException("VirtualDeviceManager not found") 123 val deviceName = 124 vdm.getDisplayNameForPersistentDeviceId(persistentDeviceId) 125 ?: DEFAULT_REMOTE_DEVICE_NAME 126 return deviceName.toString() 127 } 128 129 @JvmStatic 130 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 131 fun getDefaultDevicePersistentDeviceId(): String = 132 if (!SdkLevel.isAtLeastV()) { 133 "default: ${ContextCompat.DEVICE_ID_DEFAULT}" 134 } else { 135 VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT 136 } 137 138 /** 139 * Grants external device permissions to the specified package. Permissions will be extracted 140 * from the group name. 141 * 142 * @param app The current application 143 * @param persistentDeviceId The external device identifier 144 * @param packageName Name of the package to which permission needs to granted 145 * @param permissions Permissions that needs to be granted 146 * @param userSet Whether to mark the permission as user set 147 * 148 * TODO: b/328839130: This method is meant to use it on External Devices and on Device Aware 149 * permissions only. It does not follow the default device implementation because of the 150 * LightAppPermGroup requirement. The data class LightAppPermGroup is not available for 151 * external devices at present, hence the implementation differs. 152 */ 153 @JvmStatic 154 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 155 fun grantRuntimePermissionsWithPersistentDeviceId( 156 app: Application, 157 persistentDeviceId: String, 158 packageName: String, 159 permissions: Set<String>, 160 userSet: Boolean 161 ) { 162 if (!SdkLevel.isAtLeastV() || isDefaultDeviceId(persistentDeviceId)) { 163 return 164 } 165 permissions 166 .filter { isPermissionDeviceAware(it) } 167 .forEach { permission -> 168 grantRuntimePermissionWithPersistentDeviceId( 169 app, 170 persistentDeviceId, 171 packageName, 172 permission, 173 userSet 174 ) 175 } 176 } 177 178 /** 179 * Grants the external device permission to the specified package 180 * 181 * @param app The current application 182 * @param persistentDeviceId The external device identifier 183 * @param packageName Name of the package to which permission needs to granted 184 * @param permission Permission that needs to be granted 185 * @param userSet Whether to mark the permission as user set 186 * 187 * TODO: b/328839130: This method is meant to use it on External Devices and on Device Aware 188 * permissions only. It does not follow the default device implementation because of the 189 * LightAppPermGroup requirement. The data class LightAppPermGroup is not available for 190 * external devices at present, hence the implementation differs. 191 */ 192 @JvmStatic 193 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 194 private fun grantRuntimePermissionWithPersistentDeviceId( 195 app: Application, 196 persistentDeviceId: String, 197 packageName: String, 198 permission: String, 199 userSet: Boolean 200 ) { 201 if (!SdkLevel.isAtLeastV() || isDefaultDeviceId(persistentDeviceId)) { 202 return 203 } 204 val permissionManager = app.getSystemService(PermissionManager::class.java)!! 205 var newFlag = 206 FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED or 207 FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED 208 if (userSet) { 209 newFlag = newFlag or FLAG_PERMISSION_USER_SET 210 } 211 permissionManager.updatePermissionFlags( 212 packageName, 213 permission, 214 persistentDeviceId, 215 DEVICE_AWARE_PERMISSION_FLAG_MASK, 216 newFlag 217 ) 218 permissionManager.grantRuntimePermission(packageName, permission, persistentDeviceId) 219 } 220 221 /** 222 * Revokes the external device permissions from the specified package. Permissions will be 223 * extracted from the group name. 224 * 225 * @param app The current application 226 * @param persistentDeviceId The external device identifier 227 * @param packageName Name of the package to which permission needs to revoked 228 * @param permissions Permissions that needs to be revoked 229 * @param userSet Whether to mark the permission as user set 230 * @param oneTime Whether this is a one-time permission grant permissions 231 * @param reason The reason for the revoke, or {@code null} for unspecified 232 * 233 * TODO: b/328839130: This method is meant to use it on External Devices and on Device Aware 234 * permissions only. It does not follow the default device implementation because of the 235 * LightAppPermGroup requirement. The data class LightAppPermGroup is not available for 236 * external devices at present, hence the implementation differs. 237 */ 238 @JvmStatic 239 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 240 fun revokeRuntimePermissionsWithPersistentDeviceId( 241 app: Application, 242 persistentDeviceId: String, 243 packageName: String, 244 permissions: Set<String>, 245 userSet: Boolean, 246 oneTime: Boolean, 247 reason: String? = null 248 ) { 249 if (!SdkLevel.isAtLeastV() || isDefaultDeviceId(persistentDeviceId)) { 250 return 251 } 252 permissions 253 .filter { isPermissionDeviceAware(it) } 254 .forEach { permission -> 255 revokeRuntimePermissionWithPersistentDeviceId( 256 app, 257 persistentDeviceId, 258 packageName, 259 permission, 260 userSet, 261 oneTime, 262 reason 263 ) 264 } 265 } 266 267 /** 268 * Revokes the external device permission to the specified package. 269 * 270 * @param app The current application 271 * @param persistentDeviceId The external device identifier 272 * @param packageName Name of the package to which permission needs to revoked 273 * @param permission Permission that needs to be revoked 274 * @param userSet Whether to mark the permission as user set 275 * @param oneTime Whether this is a one-time permission grant permissions 276 * @param reason The reason for the revoke, or {@code null} for unspecified 277 * 278 * TODO: b/328839130: This method is meant to use it on External Devices and on Device Aware 279 * permissions only. It does not follow the default device implementation because of the 280 * LightAppPermGroup requirement. The data class LightAppPermGroup is not available for 281 * external devices at present, hence the implementation differs. 282 */ 283 @JvmStatic 284 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 285 private fun revokeRuntimePermissionWithPersistentDeviceId( 286 app: Application, 287 persistentDeviceId: String, 288 packageName: String, 289 permission: String, 290 userSet: Boolean, 291 oneTime: Boolean, 292 reason: String? = null 293 ) { 294 if (!SdkLevel.isAtLeastV() || isDefaultDeviceId(persistentDeviceId)) { 295 return 296 } 297 val permissionManager = app.getSystemService(PermissionManager::class.java)!! 298 var newFlag = 299 FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED or 300 FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED 301 if (oneTime) { 302 newFlag = newFlag or FLAG_PERMISSION_ONE_TIME 303 } 304 if (userSet) { 305 newFlag = newFlag or FLAG_PERMISSION_USER_SET 306 } 307 if (isPermissionUserFixed(app, persistentDeviceId, packageName, permission) && !oneTime) { 308 newFlag = newFlag or FLAG_PERMISSION_USER_FIXED 309 } 310 permissionManager.updatePermissionFlags( 311 packageName, 312 permission, 313 persistentDeviceId, 314 DEVICE_AWARE_PERMISSION_FLAG_MASK, 315 newFlag 316 ) 317 permissionManager.revokeRuntimePermission( 318 packageName, 319 permission, 320 persistentDeviceId, 321 reason 322 ) 323 } 324 325 /** 326 * Determines if the permission is UserFixed. This method is for to use with V and above only. 327 * Supports both external and default devices, need to specify persistentDeviceId accordingly. 328 */ 329 @ChecksSdkIntAtLeast(Build.VERSION_CODES.VANILLA_ICE_CREAM) 330 private fun isPermissionUserFixed( 331 app: Application, 332 persistentDeviceId: String, 333 packageName: String, 334 permission: String 335 ): Boolean { 336 if (!SdkLevel.isAtLeastV()) { 337 return true 338 } 339 val permissionManager = app.getSystemService(PermissionManager::class.java)!! 340 val flags = 341 permissionManager.getPermissionFlags(packageName, permission, persistentDeviceId) 342 return flags and FLAG_PERMISSION_USER_FIXED != 0 343 } 344 } 345