1 /*
<lambda>null2  * Copyright (C) 2019 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.data
18 
19 import android.Manifest
20 import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
21 import android.Manifest.permission_group.STORAGE
22 import android.app.AppOpsManager
23 import android.app.Application
24 import android.content.pm.PackageManager
25 import android.content.pm.PermissionInfo
26 import android.os.Build
27 import android.os.UserHandle
28 import com.android.permissioncontroller.PermissionControllerApplication
29 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo
30 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
31 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
32 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo
33 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermInfo
34 import com.android.permissioncontroller.permission.model.livedatatypes.PermState
35 import com.android.permissioncontroller.permission.utils.LocationUtils
36 import com.android.permissioncontroller.permission.utils.PermissionMapping.isPlatformPermissionGroup
37 import com.android.permissioncontroller.permission.utils.Utils
38 import kotlinx.coroutines.Job
39 
40 /**
41  * A LiveData representing UI properties of an App Permission Group:
42  * <ul>
43  * <li>shouldShow</li>
44  * <li>isSystem</li>
45  * <li>isGranted</li>
46  * </ul>
47  *
48  * @param app The current application
49  * @param packageName The name of the package
50  * @param permGroupName The name of the permission group whose permissions are observed
51  * @param user The user of the package
52  */
53 class AppPermGroupUiInfoLiveData
54 private constructor(
55     private val app: Application,
56     private val packageName: String,
57     private val permGroupName: String,
58     private val user: UserHandle
59 ) : SmartAsyncMediatorLiveData<AppPermGroupUiInfo>(), LocationUtils.LocationListener {
60 
61     private var isSpecialLocation = false
62     private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user]
63     private val permGroupLiveData = PermGroupLiveData[permGroupName]
64     private val permissionStateLiveData = PermStateLiveData[packageName, permGroupName, user]
65     private val isStorage = permGroupName == STORAGE
66     private val isHealth = Utils.isHealthPermissionGroup(permGroupName)
67 
68     init {
69         isSpecialLocation =
70             LocationUtils.isLocationGroupAndProvider(app, permGroupName, packageName) ||
71                 LocationUtils.isLocationGroupAndControllerExtraPackage(
72                     app,
73                     permGroupName,
74                     packageName
75                 )
76 
77         addSource(packageInfoLiveData) { update() }
78 
79         addSource(permGroupLiveData) { update() }
80 
81         addSource(permissionStateLiveData) { update() }
82     }
83 
84     override suspend fun loadDataAndPostValue(job: Job) {
85         if (job.isCancelled) {
86             return
87         }
88         val packageInfo = packageInfoLiveData.value
89         val permissionGroup = permGroupLiveData.value
90         val permissionState = permissionStateLiveData.value
91 
92         if (packageInfo == null || permissionGroup == null || permissionState == null) {
93             if (
94                 packageInfoLiveData.isInitialized &&
95                     permGroupLiveData.isInitialized &&
96                     permissionStateLiveData.isInitialized
97             ) {
98                 invalidateSingle(Triple(packageName, permGroupName, user))
99                 postValue(null)
100             }
101             return
102         }
103 
104         postValue(
105             getAppPermGroupUiInfo(
106                 packageInfo,
107                 permissionGroup.groupInfo,
108                 permissionGroup.permissionInfos,
109                 permissionState
110             )
111         )
112     }
113 
114     /**
115      * Determines if the UI should show a given package, if that package is a system app, and if it
116      * has granted permissions in this LiveData's permission group.
117      *
118      * @param packageInfo The PackageInfo of the package we wish to examine
119      * @param groupInfo The groupInfo of the permission group we wish to examine
120      * @param allPermInfos All of the PermissionInfos in the permission group
121      * @param permissionState The flags and grant state for all permissions in the permission group
122      *   that this package requests
123      */
124     private fun getAppPermGroupUiInfo(
125         packageInfo: LightPackageInfo,
126         groupInfo: LightPermGroupInfo,
127         allPermInfos: Map<String, LightPermInfo>,
128         permissionState: Map<String, PermState>
129     ): AppPermGroupUiInfo {
130         /*
131          * Filter out any permission infos in the permission group that this package
132          * does not request.
133          */
134         val requestedPermissionInfos =
135             allPermInfos.filter { permissionState.containsKey(it.key) }.values
136 
137         val shouldShow =
138             packageInfo.enabled &&
139                 isGrantableAndNotLegacyPlatform(packageInfo, groupInfo, requestedPermissionInfos) &&
140                 (!isStorage || Utils.shouldShowStorage(packageInfo)) &&
141                 (!isHealth || Utils.shouldShowHealthPermission(packageInfo, groupInfo.name))
142 
143         val isSystemApp = !isUserSensitive(permissionState)
144 
145         val isUserSet = isUserSet(permissionState)
146 
147         val permGrantState =
148             getGrantedIncludingBackground(permissionState, allPermInfos, packageInfo)
149 
150         return AppPermGroupUiInfo(shouldShow, permGrantState, isSystemApp, isUserSet)
151     }
152 
153     /**
154      * Determines if a package permission group is able to be granted, and whether or not it is a
155      * legacy system permission group.
156      *
157      * @param packageInfo The PackageInfo of the package we are examining
158      * @param groupInfo The Permission Group Info of the permission group we are examining
159      * @param permissionInfos The LightPermInfos corresponding to the permissions in the permission
160      *   group that this package requests
161      * @return True if the app permission group is grantable, and is not a legacy system permission,
162      *   false otherwise.
163      */
164     private fun isGrantableAndNotLegacyPlatform(
165         packageInfo: LightPackageInfo,
166         groupInfo: LightPermGroupInfo,
167         permissionInfos: Collection<LightPermInfo>
168     ): Boolean {
169         if (groupInfo.packageName == Utils.OS_PKG && !isPlatformPermissionGroup(groupInfo.name)) {
170             return false
171         }
172 
173         var hasInstantPerm = false
174         var hasPreRuntime = false
175 
176         for (permissionInfo in permissionInfos) {
177             if (
178                 permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY == 0
179             ) {
180                 hasPreRuntime = true
181             }
182 
183             if (permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_INSTANT != 0) {
184                 hasInstantPerm = true
185             }
186         }
187 
188         val isGrantingAllowed =
189             (!packageInfo.isInstantApp || hasInstantPerm) &&
190                 (packageInfo.targetSdkVersion >= Build.VERSION_CODES.M || hasPreRuntime)
191         if (!isGrantingAllowed) {
192             return false
193         }
194 
195         return true
196     }
197 
198     /**
199      * Determines if an app's permission group is user-sensitive. If an app is not user sensitive,
200      * then it is considered a system app, and hidden in the UI by default.
201      *
202      * @param permissionState The permission flags and grant state corresponding to the permissions
203      *   in this group requested by a given app
204      * @return Whether or not this package requests a user sensitive permission in the given
205      *   permission group
206      */
207     private fun isUserSensitive(permissionState: Map<String, PermState>): Boolean {
208         if (!isPlatformPermissionGroup(permGroupName)) {
209             return true
210         }
211 
212         for (permissionName in permissionState.keys) {
213             val flags = permissionState[permissionName]?.permFlags ?: return true
214             val granted = permissionState[permissionName]?.granted ?: return true
215             if (
216                 (granted &&
217                     flags and PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED != 0) ||
218                     (!granted &&
219                         flags and PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED != 0)
220             ) {
221                 return true
222             }
223         }
224         return false
225     }
226 
227     /**
228      * Determines if the app permission group is user set
229      *
230      * @param permissionState The permission flags and grant state corresponding to the permissions
231      *   in this group requested by a given app
232      * @return Whether or not any of the permissions in this group have been set or fixed by the
233      *   user
234      */
235     private fun isUserSet(permissionState: Map<String, PermState>): Boolean {
236         val flagMask =
237             PackageManager.FLAG_PERMISSION_USER_SET or PackageManager.FLAG_PERMISSION_USER_FIXED
238         return permissionState.any { (it.value.permFlags and flagMask) != 0 }
239     }
240 
241     /**
242      * Determines if this app permission group is granted, granted in foreground only, or denied. It
243      * is granted if it either requests no background permissions, and has at least one requested
244      * permission that is granted, or has granted at least one requested background permission. It
245      * is granted in foreground only if it has at least one non-background permission granted, and
246      * has denied all requested background permissions. It is denied if all requested permissions
247      * are denied.
248      *
249      * @param permissionState The permission flags and grant state corresponding to the permissions
250      *   in this group requested by a given app
251      * @param allPermInfos All of the permissionInfos in the permission group of this app permission
252      *   group
253      * @return The int code corresponding to the app permission group state, either allowed, allowed
254      *   in foreground only, or denied.
255      */
256     private fun getGrantedIncludingBackground(
257         permissionState: Map<String, PermState>,
258         allPermInfos: Map<String, LightPermInfo>,
259         pkg: LightPackageInfo
260     ): PermGrantState {
261         val specialLocationState = getIsSpecialLocationState()
262         if (isStorage && isFullFilesAccessGranted(pkg)) {
263             return PermGrantState.PERMS_ALLOWED
264         }
265 
266         var hasPermWithBackground = false
267         var isUserFixed = false
268 
269         for ((permName, permState) in permissionState) {
270             val permInfo = allPermInfos[permName] ?: continue
271             permInfo.backgroundPermission?.let { backgroundPerm ->
272                 hasPermWithBackground = true
273                 if (
274                     permissionState[backgroundPerm]?.granted == true &&
275                         (permissionState[backgroundPerm]!!.permFlags and
276                             PackageManager.FLAG_PERMISSION_ONE_TIME == 0) &&
277                         specialLocationState != false
278                 ) {
279                     return PermGrantState.PERMS_ALLOWED_ALWAYS
280                 }
281             }
282             isUserFixed =
283                 isUserFixed ||
284                     permState.permFlags and PackageManager.FLAG_PERMISSION_USER_FIXED != 0
285         }
286 
287         // isOneTime indicates whether all granted permissions in permission states are one-time
288         // permissions
289         val isOneTime =
290             permissionState.any {
291                 it.value.permFlags and PackageManager.FLAG_PERMISSION_ONE_TIME != 0
292             } &&
293                 !permissionState.any {
294                     it.value.permFlags and PackageManager.FLAG_PERMISSION_ONE_TIME == 0 &&
295                         it.value.granted
296                 }
297 
298         val supportsRuntime = pkg.targetSdkVersion >= Build.VERSION_CODES.M
299         val anyAllowed =
300             specialLocationState
301                 ?: permissionState.any { (_, state) ->
302                     state.granted ||
303                         (supportsRuntime &&
304                             (state.permFlags and PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) !=
305                                 0)
306                 }
307         val onlySelectedPhotosGranted =
308             permissionState.containsKey(READ_MEDIA_VISUAL_USER_SELECTED) &&
309                 permissionState.all { (permName, state) ->
310                     (permName == READ_MEDIA_VISUAL_USER_SELECTED && state.granted) ||
311                         (permName != READ_MEDIA_VISUAL_USER_SELECTED && !state.granted)
312                 }
313         if (anyAllowed && (hasPermWithBackground || shouldShowAsForegroundGroup())) {
314             return if (isOneTime) {
315                 PermGrantState.PERMS_ASK
316             } else {
317                 PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY
318             }
319         } else if (anyAllowed) {
320             return if (isOneTime || onlySelectedPhotosGranted) {
321                 PermGrantState.PERMS_ASK
322             } else {
323                 PermGrantState.PERMS_ALLOWED
324             }
325         }
326         if (isUserFixed) {
327             return PermGrantState.PERMS_DENIED
328         }
329         if (isOneTime) {
330             return PermGrantState.PERMS_ASK
331         }
332         return PermGrantState.PERMS_DENIED
333     }
334 
335     private fun getIsSpecialLocationState(): Boolean? {
336         if (!isSpecialLocation) {
337             return null
338         }
339 
340         val userContext = Utils.getUserContext(app, user)
341         if (LocationUtils.isLocationGroupAndProvider(userContext, permGroupName, packageName)) {
342             return LocationUtils.isLocationEnabled(userContext)
343         }
344         // The permission of the extra location controller package is determined by the
345         // status of the controller package itself.
346         if (
347             LocationUtils.isLocationGroupAndControllerExtraPackage(
348                 userContext,
349                 permGroupName,
350                 packageName
351             )
352         ) {
353             return LocationUtils.isExtraLocationControllerPackageEnabled(userContext)
354         }
355         return null
356     }
357 
358     private fun isFullFilesAccessGranted(pkg: LightPackageInfo): Boolean {
359         val packageState =
360             if (!FullStoragePermissionAppsLiveData.isStale) {
361                 val fullStoragePackages = FullStoragePermissionAppsLiveData.value ?: return false
362                 fullStoragePackages.find { it.packageName == packageName && it.user == user }
363                     ?: return false
364             } else {
365                 val appOpsManager =
366                     Utils.getUserContext(app, UserHandle.getUserHandleForUid(pkg.uid))
367                         .getSystemService(AppOpsManager::class.java)!!
368                 FullStoragePermissionAppsLiveData.getFullStorageStateForPackage(appOpsManager, pkg)
369                     ?: return false
370             }
371         return !packageState.isLegacy && packageState.isGranted
372     }
373 
374     // TODO moltmann-team: Actually change mic/camera to be a foreground only permission
375     private fun shouldShowAsForegroundGroup(): Boolean {
376         return permGroupName.equals(Manifest.permission_group.CAMERA) ||
377             permGroupName.equals(Manifest.permission_group.MICROPHONE)
378     }
379 
380     override fun onLocationStateChange(enabled: Boolean) {
381         update()
382     }
383 
384     override fun onActive() {
385         super.onActive()
386         if (isSpecialLocation) {
387             LocationUtils.addLocationListener(this)
388             update()
389         }
390     }
391 
392     override fun onInactive() {
393         super.onInactive()
394 
395         if (isSpecialLocation) {
396             LocationUtils.removeLocationListener(this)
397         }
398     }
399 
400     /**
401      * Repository for AppPermGroupUiInfoLiveDatas.
402      *
403      * <p> Key value is a triple of string package name, string permission group name, and
404      * UserHandle, value is its corresponding LiveData.
405      */
406     companion object :
407         DataRepositoryForPackage<Triple<String, String, UserHandle>, AppPermGroupUiInfoLiveData>() {
408         override fun newValue(key: Triple<String, String, UserHandle>): AppPermGroupUiInfoLiveData {
409             return AppPermGroupUiInfoLiveData(
410                 PermissionControllerApplication.get(),
411                 key.first,
412                 key.second,
413                 key.third
414             )
415         }
416     }
417 }
418