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