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