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