1 /*
2  * Copyright (C) 2022 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 @file:Suppress("DEPRECATION")
17 
18 package com.android.permissioncontroller.permission.utils
19 
20 import android.Manifest
21 import android.app.AppOpsManager
22 import android.content.pm.PackageManager
23 import android.content.pm.PermissionInfo
24 import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
25 import android.util.Log
26 import com.android.modules.utils.build.SdkLevel
27 import com.android.permission.safetylabel.DataCategoryConstants
28 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
29 
30 /**
31  * This file contains the canonical mapping of permission to permission group, used in the
32  * Permission settings screens and grant dialog. It also includes methods related to that mapping.
33  */
34 object PermissionMapping {
35 
36     private const val LOG_TAG = "PermissionMapping"
37 
38     private val PERMISSION_GROUPS_TO_DATA_CATEGORIES: Map<String, List<String>> =
39         mapOf(Manifest.permission_group.LOCATION to listOf(DataCategoryConstants.CATEGORY_LOCATION))
40 
41     @JvmField
42     val SENSOR_DATA_PERMISSIONS: List<String> =
43         listOf(
44             Manifest.permission_group.LOCATION,
45             Manifest.permission_group.CAMERA,
46             Manifest.permission_group.MICROPHONE
47         )
48 
49     @JvmField
50     val STORAGE_SUPERGROUP_PERMISSIONS: List<String> =
51         if (!SdkLevel.isAtLeastT()) listOf()
52         else
53             listOf(
54                 Manifest.permission_group.STORAGE,
55                 Manifest.permission_group.READ_MEDIA_AURAL,
56                 Manifest.permission_group.READ_MEDIA_VISUAL
57             )
58 
59     val PARTIAL_MEDIA_PERMISSIONS: MutableSet<String> = mutableSetOf()
60 
61     /** Mapping permission -> group for all dangerous platform permissions */
62     private val PLATFORM_PERMISSIONS: MutableMap<String, String> = mutableMapOf()
63 
64     /** Mapping group -> permissions for all dangerous platform permissions */
65     private val PLATFORM_PERMISSION_GROUPS: MutableMap<String, MutableList<String>> = mutableMapOf()
66 
67     /** Set of groups that will be able to receive one-time grant */
68     private val ONE_TIME_PERMISSION_GROUPS: MutableSet<String> = mutableSetOf()
69 
70     private val HEALTH_PERMISSIONS_SET: MutableSet<String> = mutableSetOf()
71 
72     init {
73         PLATFORM_PERMISSIONS[Manifest.permission.READ_CONTACTS] = Manifest.permission_group.CONTACTS
74         PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CONTACTS] =
75             Manifest.permission_group.CONTACTS
76         PLATFORM_PERMISSIONS[Manifest.permission.GET_ACCOUNTS] = Manifest.permission_group.CONTACTS
77 
78         PLATFORM_PERMISSIONS[Manifest.permission.READ_CALENDAR] = Manifest.permission_group.CALENDAR
79         PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CALENDAR] =
80             Manifest.permission_group.CALENDAR
81 
82         // Any updates to the permissions for the SMS permission group must also be made in
83         // Permissions {@link com.android.role.controller.model.Permissions} in the role
84         // library
85         PLATFORM_PERMISSIONS[Manifest.permission.SEND_SMS] = Manifest.permission_group.SMS
86         PLATFORM_PERMISSIONS[Manifest.permission.RECEIVE_SMS] = Manifest.permission_group.SMS
87         PLATFORM_PERMISSIONS[Manifest.permission.READ_SMS] = Manifest.permission_group.SMS
88         PLATFORM_PERMISSIONS[Manifest.permission.RECEIVE_MMS] = Manifest.permission_group.SMS
89         PLATFORM_PERMISSIONS[Manifest.permission.RECEIVE_WAP_PUSH] = Manifest.permission_group.SMS
90         PLATFORM_PERMISSIONS[Manifest.permission.READ_CELL_BROADCASTS] =
91             Manifest.permission_group.SMS
92 
93         // If permissions are added to the Storage group, they must be added to the
94         // STORAGE_PERMISSIONS list in PermissionManagerService in frameworks/base
95         PLATFORM_PERMISSIONS[Manifest.permission.READ_EXTERNAL_STORAGE] =
96             Manifest.permission_group.STORAGE
97         PLATFORM_PERMISSIONS[Manifest.permission.WRITE_EXTERNAL_STORAGE] =
98             Manifest.permission_group.STORAGE
99         if (!SdkLevel.isAtLeastT()) {
100             PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_MEDIA_LOCATION] =
101                 Manifest.permission_group.STORAGE
102         }
103 
104         if (SdkLevel.isAtLeastT()) {
105             PLATFORM_PERMISSIONS[Manifest.permission.READ_MEDIA_AUDIO] =
106                 Manifest.permission_group.READ_MEDIA_AURAL
107             PLATFORM_PERMISSIONS[Manifest.permission.READ_MEDIA_IMAGES] =
108                 Manifest.permission_group.READ_MEDIA_VISUAL
109             PLATFORM_PERMISSIONS[Manifest.permission.READ_MEDIA_VIDEO] =
110                 Manifest.permission_group.READ_MEDIA_VISUAL
111             PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_MEDIA_LOCATION] =
112                 Manifest.permission_group.READ_MEDIA_VISUAL
113         }
114 
115         if (SdkLevel.isAtLeastU()) {
116             PLATFORM_PERMISSIONS[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] =
117                 Manifest.permission_group.READ_MEDIA_VISUAL
118         }
119 
120         PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_FINE_LOCATION] =
121             Manifest.permission_group.LOCATION
122         PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_COARSE_LOCATION] =
123             Manifest.permission_group.LOCATION
124         PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_BACKGROUND_LOCATION] =
125             Manifest.permission_group.LOCATION
126 
127         if (SdkLevel.isAtLeastS()) {
128             PLATFORM_PERMISSIONS[Manifest.permission.BLUETOOTH_ADVERTISE] =
129                 Manifest.permission_group.NEARBY_DEVICES
130             PLATFORM_PERMISSIONS[Manifest.permission.BLUETOOTH_CONNECT] =
131                 Manifest.permission_group.NEARBY_DEVICES
132             PLATFORM_PERMISSIONS[Manifest.permission.BLUETOOTH_SCAN] =
133                 Manifest.permission_group.NEARBY_DEVICES
134             PLATFORM_PERMISSIONS[Manifest.permission.UWB_RANGING] =
135                 Manifest.permission_group.NEARBY_DEVICES
136         }
137         if (SdkLevel.isAtLeastT()) {
138             PLATFORM_PERMISSIONS[Manifest.permission.NEARBY_WIFI_DEVICES] =
139                 Manifest.permission_group.NEARBY_DEVICES
140         }
141 
142         // Any updates to the permissions for the CALL_LOG permission group must also be made in
143         // Permissions {@link com.android.role.controller.model.Permissions} in the role
144         // library
145         PLATFORM_PERMISSIONS[Manifest.permission.READ_CALL_LOG] = Manifest.permission_group.CALL_LOG
146         PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CALL_LOG] =
147             Manifest.permission_group.CALL_LOG
148         PLATFORM_PERMISSIONS[Manifest.permission.PROCESS_OUTGOING_CALLS] =
149             Manifest.permission_group.CALL_LOG
150 
151         PLATFORM_PERMISSIONS[Manifest.permission.READ_PHONE_STATE] = Manifest.permission_group.PHONE
152         PLATFORM_PERMISSIONS[Manifest.permission.READ_PHONE_NUMBERS] =
153             Manifest.permission_group.PHONE
154         PLATFORM_PERMISSIONS[Manifest.permission.CALL_PHONE] = Manifest.permission_group.PHONE
155         PLATFORM_PERMISSIONS[Manifest.permission.ADD_VOICEMAIL] = Manifest.permission_group.PHONE
156         PLATFORM_PERMISSIONS[Manifest.permission.USE_SIP] = Manifest.permission_group.PHONE
157         PLATFORM_PERMISSIONS[Manifest.permission.ANSWER_PHONE_CALLS] =
158             Manifest.permission_group.PHONE
159         PLATFORM_PERMISSIONS[Manifest.permission.ACCEPT_HANDOVER] = Manifest.permission_group.PHONE
160 
161         PLATFORM_PERMISSIONS[Manifest.permission.RECORD_AUDIO] =
162             Manifest.permission_group.MICROPHONE
163         if (SdkLevel.isAtLeastS()) {
164             PLATFORM_PERMISSIONS[Manifest.permission.RECORD_BACKGROUND_AUDIO] =
165                 Manifest.permission_group.MICROPHONE
166         }
167 
168         PLATFORM_PERMISSIONS[Manifest.permission.ACTIVITY_RECOGNITION] =
169             Manifest.permission_group.ACTIVITY_RECOGNITION
170 
171         PLATFORM_PERMISSIONS[Manifest.permission.CAMERA] = Manifest.permission_group.CAMERA
172         if (SdkLevel.isAtLeastS()) {
173             PLATFORM_PERMISSIONS[Manifest.permission.BACKGROUND_CAMERA] =
174                 Manifest.permission_group.CAMERA
175         }
176 
177         PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS] = Manifest.permission_group.SENSORS
178 
179         if (SdkLevel.isAtLeastT()) {
180             PLATFORM_PERMISSIONS[Manifest.permission.POST_NOTIFICATIONS] =
181                 Manifest.permission_group.NOTIFICATIONS
182             PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS_BACKGROUND] =
183                 Manifest.permission_group.SENSORS
184         }
185 
186         for ((permission, permissionGroup) in PLATFORM_PERMISSIONS) {
<lambda>null187             PLATFORM_PERMISSION_GROUPS.getOrPut(permissionGroup) { mutableListOf() }.add(permission)
188         }
189 
190         ONE_TIME_PERMISSION_GROUPS.add(Manifest.permission_group.LOCATION)
191         ONE_TIME_PERMISSION_GROUPS.add(Manifest.permission_group.CAMERA)
192         ONE_TIME_PERMISSION_GROUPS.add(Manifest.permission_group.MICROPHONE)
193 
194         if (SdkLevel.isAtLeastU()) {
195             PARTIAL_MEDIA_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
196             PARTIAL_MEDIA_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION)
197         }
198     }
199 
200     /**
201      * Get permission group a platform permission belongs to, or null if the permission is not a
202      * platform permission.
203      *
204      * @param permission the permission to resolve
205      * @return The group the permission belongs to
206      */
207     @JvmStatic
getGroupOfPlatformPermissionnull208     fun getGroupOfPlatformPermission(permission: String): String? {
209         return PLATFORM_PERMISSIONS[permission]
210     }
211 
212     /**
213      * Get name of the permission group a permission belongs to.
214      *
215      * @param permission the [info][PermissionInfo] of the permission to resolve
216      * @return The group the permission belongs to
217      */
218     @JvmStatic
getGroupOfPermissionnull219     fun getGroupOfPermission(permission: PermissionInfo): String? {
220         var groupName = getGroupOfPlatformPermission(permission.name)
221         if (groupName == null) {
222             groupName = permission.group
223         }
224         return groupName
225     }
226 
227     /**
228      * Get the names for all platform permissions belonging to a group.
229      *
230      * @param group the group
231      * @return The permission names or an empty list if the group does not have platform runtime
232      *   permissions
233      */
234     @JvmStatic
getPlatformPermissionNamesOfGroupnull235     fun getPlatformPermissionNamesOfGroup(group: String): List<String> {
236         val permissions = PLATFORM_PERMISSION_GROUPS[group]
237         return permissions ?: emptyList()
238     }
239 
240     /**
241      * Get the [infos][PermissionInfo] for all platform permissions belonging to a group.
242      *
243      * @param pm Package manager to use to resolve permission infos
244      * @param group the group
245      * @return The infos for platform permissions belonging to the group or an empty list if the
246      *   group does not have platform runtime permissions
247      */
248     @JvmStatic
getPlatformPermissionsOfGroupnull249     fun getPlatformPermissionsOfGroup(pm: PackageManager, group: String): List<PermissionInfo> {
250         val permInfos = mutableListOf<PermissionInfo>()
251         for (permName in PLATFORM_PERMISSION_GROUPS[group] ?: emptyList()) {
252             val permInfo: PermissionInfo =
253                 try {
254                     pm.getPermissionInfo(permName, 0)
255                 } catch (e: PackageManager.NameNotFoundException) {
256                     throw IllegalStateException("$permName not defined by platform", e)
257                 }
258             permInfos.add(permInfo)
259         }
260         return permInfos
261     }
262 
263     @JvmStatic
isPlatformPermissionGroupnull264     fun isPlatformPermissionGroup(name: String?): Boolean {
265         return PLATFORM_PERMISSION_GROUPS.containsKey(name)
266     }
267 
268     /**
269      * Get the names of the platform permission groups.
270      *
271      * @return the names of the platform permission groups.
272      */
273     @JvmStatic
getPlatformPermissionGroupsnull274     fun getPlatformPermissionGroups(): List<String> {
275         return PLATFORM_PERMISSION_GROUPS.keys.toList()
276     }
277 
278     /**
279      * Get the names of the runtime platform permissions
280      *
281      * @return the names of the runtime platform permissions.
282      */
283     @JvmStatic
getRuntimePlatformPermissionNamesnull284     fun getRuntimePlatformPermissionNames(): List<String> {
285         return PLATFORM_PERMISSIONS.keys.toList()
286     }
287 
288     /**
289      * Is the permissions a platform runtime permission
290      *
291      * @return the names of the runtime platform permissions.
292      */
293     @JvmStatic
isRuntimePlatformPermissionnull294     fun isRuntimePlatformPermission(permission: String): Boolean {
295         return PLATFORM_PERMISSIONS.containsKey(permission)
296     }
297 
298     /**
299      * Whether the permission group supports one-time
300      *
301      * @param permissionGroup The permission group to check
302      * @return `true` iff the group supports one-time
303      */
304     @JvmStatic
supportsOneTimeGrantnull305     fun supportsOneTimeGrant(permissionGroup: String?): Boolean {
306         return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup)
307     }
308 
309     /** Adds health permissions as platform permissions. */
310     @JvmStatic
addHealthPermissionsToPlatformnull311     fun addHealthPermissionsToPlatform(permissions: Set<String>) {
312         if (permissions.isEmpty()) {
313             Log.w(LOG_TAG, "No health connect permissions found.")
314             return
315         }
316 
317         PLATFORM_PERMISSION_GROUPS[HEALTH_PERMISSION_GROUP] = mutableListOf()
318 
319         for (permission in permissions) {
320             PLATFORM_PERMISSIONS[permission] = HEALTH_PERMISSION_GROUP
321             PLATFORM_PERMISSION_GROUPS[HEALTH_PERMISSION_GROUP]?.add(permission)
322             HEALTH_PERMISSIONS_SET.add(permission)
323         }
324     }
325 
326     /**
327      * Get the permissions that, if granted, are considered a "partial grant" of the
328      * READ_MEDIA_VISUAL permission group. If the app declares READ_MEDIA_VISUAL_USER_SELECTED, then
329      * both READ_MEDIA_VISUAL_USER_SELECTED and ACCESS_MEDIA_LOCATION are considered a partial
330      * grant. Otherwise, ACCESS_MEDIA_LOCATION is considered a full grant (for compatibility).
331      */
getPartialStorageGrantPermissionsForGroupnull332     fun getPartialStorageGrantPermissionsForGroup(group: LightAppPermGroup): Set<String> {
333         if (!KotlinUtils.isPhotoPickerPromptSupported()) {
334             return emptySet()
335         }
336 
337         val appSupportsPickerPrompt =
338             group.permissions[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit ==
339                 false
340 
341         return if (appSupportsPickerPrompt) {
342             PARTIAL_MEDIA_PERMISSIONS
343         } else {
344             setOf(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
345         }
346     }
347 
348     /** Returns true if the given permission is a health platform permission. */
349     @JvmStatic
isHealthPermissionnull350     fun isHealthPermission(permissionName: String): Boolean {
351         return HEALTH_PERMISSIONS_SET.contains(permissionName)
352     }
353 
354     /**
355      * Returns the platform permission group for the permission that the provided op backs, if any.
356      */
getPlatformPermissionGroupForOpnull357     fun getPlatformPermissionGroupForOp(opName: String): String? {
358         // The OPSTR_READ_WRITE_HEALTH_DATA is a special case as unlike other ops, it does not
359         // map to a single permission. However it is safe to retrieve a permission group for it,
360         // as all permissions it maps to, map to the same permission group
361         // HEALTH_PERMISSION_GROUP.
362         if (opName == AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA) {
363             return HEALTH_PERMISSION_GROUP
364         }
365 
366         // The following app ops are special cased as they don't back any permissions on their own,
367         // but do indicate usage of certain permissions.
368         if (opName == AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE) {
369             return Manifest.permission_group.MICROPHONE
370         }
371         if (SdkLevel.isAtLeastT() && opName == AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
372             return Manifest.permission_group.MICROPHONE
373         }
374         if (opName == AppOpsManager.OPSTR_PHONE_CALL_CAMERA) {
375             return Manifest.permission_group.CAMERA
376         }
377 
378         return try {
379             AppOpsManager.opToPermission(opName)?.let { getGroupOfPlatformPermission(it) }
380         } catch (e: IllegalArgumentException) {
381             Log.wtf(LOG_TAG, "No permission group found for $opName")
382             null
383         }
384     }
385 
386     /**
387      * Get the SafetyLabel categories pertaining to a specified permission group.
388      *
389      * @return The categories, or an empty list if the group does not have a supported mapping to
390      *   safety label category
391      */
getDataCategoriesForPermissionGroupnull392     fun getDataCategoriesForPermissionGroup(permissionGroupName: String): List<String> {
393         return if (isSafetyLabelAwarePermissionGroup(permissionGroupName)) {
394             PERMISSION_GROUPS_TO_DATA_CATEGORIES[permissionGroupName] ?: emptyList()
395         } else {
396             emptyList()
397         }
398     }
399 
400     /**
401      * Whether this permission group maps to a SafetyLabel data category.
402      *
403      * @param permissionGroupName the permission group name
404      */
405     @JvmStatic
isSafetyLabelAwarePermissionGroupnull406     fun isSafetyLabelAwarePermissionGroup(permissionGroupName: String): Boolean {
407         if (!KotlinUtils.isPermissionRationaleEnabled()) {
408             return false
409         }
410 
411         return PERMISSION_GROUPS_TO_DATA_CATEGORIES.containsKey(permissionGroupName)
412     }
413 }
414