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