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