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.permission_group.READ_MEDIA_VISUAL 20 import android.Manifest.permission_group.STORAGE 21 import android.app.AppOpsManager 22 import android.app.AppOpsManager.MODE_ALLOWED 23 import android.app.AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES 24 import android.app.Application 25 import android.app.role.RoleManager 26 import android.content.pm.PackageManager 27 import android.content.pm.PermissionInfo 28 import android.os.Build 29 import android.os.UserHandle 30 import android.permission.PermissionManager 31 import android.util.Log 32 import com.android.permissioncontroller.PermissionControllerApplication 33 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup 34 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo 35 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission 36 import com.android.permissioncontroller.permission.utils.KotlinUtils 37 import com.android.permissioncontroller.permission.utils.LocationUtils 38 import com.android.permissioncontroller.permission.utils.Utils 39 import com.android.permissioncontroller.permission.utils.Utils.OS_PKG 40 41 /** 42 * A LiveData which represents the permissions for one package and permission group. 43 * 44 * @param app The current application 45 * @param packageName The name of the package 46 * @param permGroupName The name of the permission group 47 * @param user The user of the package 48 */ 49 class LightAppPermGroupLiveData 50 private constructor( 51 private val app: Application, 52 private val packageName: String, 53 private val permGroupName: String, 54 private val user: UserHandle, 55 private val deviceId: Int 56 ) : SmartUpdateMediatorLiveData<LightAppPermGroup?>(), LocationUtils.LocationListener { 57 58 private val LOG_TAG = this::class.java.simpleName 59 60 private var isSpecialLocation = false 61 private val permStateLiveData = PermStateLiveData[packageName, permGroupName, user, deviceId] 62 private val permGroupLiveData = PermGroupLiveData[permGroupName] 63 private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user, deviceId] 64 private val fgPermNamesLiveData = ForegroundPermNamesLiveData 65 66 init { 67 isSpecialLocation = 68 LocationUtils.isLocationGroupAndProvider(app, permGroupName, packageName) || 69 LocationUtils.isLocationGroupAndControllerExtraPackage( 70 app, 71 permGroupName, 72 packageName 73 ) 74 75 addSource(fgPermNamesLiveData) { update() } 76 77 val key = KotlinUtils.Quadruple(packageName, permGroupName, user, deviceId) 78 79 addSource(permStateLiveData) { permStates -> 80 if (permStates == null && permStateLiveData.isInitialized) { 81 invalidateSingle(key) 82 value = null 83 } else { 84 update() 85 } 86 } 87 88 addSource(permGroupLiveData) { permGroup -> 89 if (permGroup == null && permGroupLiveData.isInitialized) { 90 invalidateSingle(key) 91 value = null 92 } else { 93 update() 94 } 95 } 96 97 addSource(packageInfoLiveData) { packageInfo -> 98 if (packageInfo == null && packageInfoLiveData.isInitialized) { 99 invalidateSingle(key) 100 value = null 101 } else { 102 update() 103 } 104 } 105 } 106 107 override fun onUpdate() { 108 val permStates = permStateLiveData.value ?: return 109 val permGroup = permGroupLiveData.value ?: return 110 val packageInfo = packageInfoLiveData.value ?: return 111 val allForegroundPerms = fgPermNamesLiveData.value ?: return 112 113 // Do not allow toggling pre-M custom perm groups 114 if ( 115 packageInfo.targetSdkVersion < Build.VERSION_CODES.M && 116 permGroup.groupInfo.packageName != OS_PKG 117 ) { 118 value = LightAppPermGroup(packageInfo, permGroup.groupInfo, emptyMap()) 119 return 120 } 121 122 val permissionMap = mutableMapOf<String, LightPermission>() 123 for ((permName, permState) in permStates) { 124 val permInfo = permGroup.permissionInfos[permName] ?: continue 125 val foregroundPerms = allForegroundPerms[permName] 126 permissionMap[permName] = 127 LightPermission(packageInfo, permInfo, permState, foregroundPerms) 128 } 129 130 131 val hasInstallToRuntimeSplit = hasInstallToRuntimeSplit(packageInfo, permissionMap) 132 value = 133 LightAppPermGroup( 134 packageInfo, 135 permGroup.groupInfo, 136 permissionMap, 137 hasInstallToRuntimeSplit, 138 isSpecialLocationGranted(app, packageName, permGroupName, user), 139 isSpecialFixedStorageGranted(app, packageName, permGroupName, packageInfo.uid) 140 ) 141 } 142 143 /** 144 * Check if permission group contains a runtime permission that split from an installed 145 * permission and the split happened in an Android version higher than app's targetSdk. 146 * 147 * @return `true` if there is such permission, `false` otherwise 148 */ 149 private fun hasInstallToRuntimeSplit( 150 packageInfo: LightPackageInfo, 151 permissionMap: Map<String, LightPermission> 152 ): Boolean { 153 val permissionManager = app.getSystemService(PermissionManager::class.java) ?: return false 154 155 for (spi in permissionManager.splitPermissions) { 156 val splitPerm = spi.splitPermission 157 158 val pi = 159 try { 160 app.packageManager.getPermissionInfo(splitPerm, 0) 161 } catch (e: PackageManager.NameNotFoundException) { 162 Log.w(LOG_TAG, "No such permission: $splitPerm", e) 163 continue 164 } 165 166 // Skip if split permission is not "install" permission. 167 if (pi.protection != PermissionInfo.PROTECTION_NORMAL) { 168 continue 169 } 170 171 val newPerms = spi.newPermissions 172 for (permName in newPerms) { 173 val newPerm = permissionMap[permName]?.permInfo ?: continue 174 175 // Skip if new permission is not "runtime" permission. 176 if (newPerm.protection != PermissionInfo.PROTECTION_DANGEROUS) { 177 continue 178 } 179 180 if (packageInfo.targetSdkVersion < spi.targetSdk) { 181 return true 182 } 183 } 184 } 185 return false 186 } 187 188 override fun onLocationStateChange(enabled: Boolean) { 189 update() 190 } 191 192 override fun onActive() { 193 super.onActive() 194 195 if (isSpecialLocation) { 196 LocationUtils.addLocationListener(this) 197 update() 198 } 199 } 200 201 override fun onInactive() { 202 if (isSpecialLocation) { 203 LocationUtils.removeLocationListener(this) 204 } 205 206 super.onInactive() 207 } 208 209 /** 210 * Repository for AppPermGroupLiveDatas. 211 * 212 * <p> Key value is a triple of string package name, string permission group name, and 213 * UserHandle, value is its corresponding LiveData. 214 */ 215 companion object : 216 DataRepositoryForDevice< 217 KotlinUtils.Quadruple<String, String, UserHandle, Int>, LightAppPermGroupLiveData 218 >() { 219 override fun newValue( 220 key: KotlinUtils.Quadruple<String, String, UserHandle, Int>, 221 deviceId: Int 222 ): LightAppPermGroupLiveData { 223 return LightAppPermGroupLiveData( 224 PermissionControllerApplication.get(), 225 key.first, 226 key.second, 227 key.third, 228 deviceId 229 ) 230 } 231 232 private const val SYSTEM_GALLERY_ROLE_NAME = "android.app.role.SYSTEM_GALLERY" 233 234 // Returns true if this app is the location provider or location extra package, and location 235 // access is granted, false if it is the provider/extra, and location is not granted, and 236 // null if it is not a special package 237 fun isSpecialLocationGranted( 238 app: Application, 239 packageName: String, 240 permGroupName: String, 241 user: UserHandle 242 ): Boolean? { 243 val userContext = Utils.getUserContext(app, user) 244 return if ( 245 LocationUtils.isLocationGroupAndProvider(userContext, permGroupName, packageName) 246 ) { 247 LocationUtils.isLocationEnabled(userContext) 248 } else if ( 249 LocationUtils.isLocationGroupAndControllerExtraPackage(app, permGroupName, packageName) 250 ) { 251 // The permission of the extra location controller package is determined by the status 252 // of the controller package itself. 253 LocationUtils.isExtraLocationControllerPackageEnabled(userContext) 254 } else { 255 null 256 } 257 } 258 259 // Gallery role is static, so we only need to get the set gallery app once 260 private val systemGalleryApps: List<String> by lazy { 261 val roleManager = PermissionControllerApplication.get() 262 .getSystemService(RoleManager::class.java) ?: return@lazy emptyList() 263 roleManager.getRoleHolders(SYSTEM_GALLERY_ROLE_NAME) 264 } 265 266 fun isSpecialFixedStorageGranted( 267 app: Application, 268 packageName: String, 269 permGroupName: String, 270 uid: Int 271 ): Boolean { 272 if (permGroupName != READ_MEDIA_VISUAL && permGroupName != STORAGE) { 273 return false 274 } 275 if (packageName !in systemGalleryApps) { 276 return false 277 } 278 // This is the storage group, and the gallery app. Check the write media app op 279 val appOps = app.getSystemService(AppOpsManager::class.java) 280 return appOps.checkOpNoThrow(OPSTR_WRITE_MEDIA_IMAGES, uid, packageName) == MODE_ALLOWED 281 } 282 } 283 } 284