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