1 /* <lambda>null2 * Copyright (C) 2022 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 @file:Suppress("DEPRECATION") 17 18 package com.android.permissioncontroller.permission.ui.model 19 20 import android.Manifest.permission.ACCESS_COARSE_LOCATION 21 import android.Manifest.permission.ACCESS_FINE_LOCATION 22 import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED 23 import android.Manifest.permission_group.LOCATION 24 import android.Manifest.permission_group.NOTIFICATIONS 25 import android.Manifest.permission_group.READ_MEDIA_AURAL 26 import android.Manifest.permission_group.READ_MEDIA_VISUAL 27 import android.Manifest.permission_group.STORAGE 28 import android.annotation.SuppressLint 29 import android.app.Activity 30 import android.app.Application 31 import android.app.admin.DevicePolicyManager 32 import android.content.Context 33 import android.content.Intent 34 import android.content.pm.PackageManager 35 import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED 36 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED 37 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET 38 import android.health.connect.HealthConnectManager.ACTION_REQUEST_HEALTH_PERMISSIONS 39 import android.health.connect.HealthConnectManager.isHealthPermission 40 import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP 41 import android.os.Build 42 import android.os.Bundle 43 import android.os.Process 44 import android.permission.PermissionManager 45 import android.permission.flags.Flags 46 import android.util.ArrayMap 47 import android.util.Log 48 import androidx.core.util.Consumer 49 import androidx.lifecycle.ViewModel 50 import androidx.lifecycle.ViewModelProvider 51 import com.android.modules.utils.build.SdkLevel 52 import com.android.permission.safetylabel.SafetyLabel 53 import com.android.permissioncontroller.Constants 54 import com.android.permissioncontroller.DeviceUtils 55 import com.android.permissioncontroller.PermissionControllerStatsLog 56 import com.android.permissioncontroller.PermissionControllerStatsLog.GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS 57 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED 58 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED 59 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED 60 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED 61 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED 62 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION 63 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED 64 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED 65 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED 66 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS 67 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE 68 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS 69 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED 70 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS 71 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME 72 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED 73 import com.android.permissioncontroller.auto.DrivingDecisionReminderService 74 import com.android.permissioncontroller.ecm.EnhancedConfirmationStatsLogUtils 75 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData 76 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData 77 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData 78 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 79 import com.android.permissioncontroller.permission.data.get 80 import com.android.permissioncontroller.permission.data.v34.SafetyLabelInfoLiveData 81 import com.android.permissioncontroller.permission.model.AppPermissionGroup 82 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup 83 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo 84 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo 85 import com.android.permissioncontroller.permission.service.PermissionChangeStorageImpl 86 import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl 87 import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier 88 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity 89 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.INTENT_PHOTOS_SELECTED 90 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED 91 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED 92 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN 93 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_MORE 94 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS 95 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY 96 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME 97 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED 98 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity 99 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED 100 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT 101 import com.android.permissioncontroller.permission.ui.model.grantPermissions.BackgroundGrantBehavior 102 import com.android.permissioncontroller.permission.ui.model.grantPermissions.BasicGrantBehavior 103 import com.android.permissioncontroller.permission.ui.model.grantPermissions.GrantBehavior 104 import com.android.permissioncontroller.permission.ui.model.grantPermissions.HealthGrantBehavior 105 import com.android.permissioncontroller.permission.ui.model.grantPermissions.LocationGrantBehavior 106 import com.android.permissioncontroller.permission.ui.model.grantPermissions.NotificationGrantBehavior 107 import com.android.permissioncontroller.permission.ui.model.grantPermissions.StorageGrantBehavior 108 import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity 109 import com.android.permissioncontroller.permission.utils.ContextCompat 110 import com.android.permissioncontroller.permission.utils.KotlinUtils 111 import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions 112 import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegroundRuntimePermissions 113 import com.android.permissioncontroller.permission.utils.KotlinUtils.openPhotoPickerForApp 114 import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeBackgroundRuntimePermissions 115 import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeForegroundRuntimePermissions 116 import com.android.permissioncontroller.permission.utils.PermissionMapping 117 import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup 118 import com.android.permissioncontroller.permission.utils.SafetyNetLogger 119 import com.android.permissioncontroller.permission.utils.Utils 120 import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils 121 import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils 122 import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils.isPermissionDeviceAware 123 124 /** 125 * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by the 126 * permissions requested by the user, and generates a RequestInfo object for each group, if action 127 * is needed. It will not return any data if one of the requests is malformed. 128 * 129 * @param app: The current application 130 * @param packageName: The packageName permissions are being requested for 131 * @param requestedPermissions: The list of permissions requested 132 * @param systemRequestedPermissions: The list of permissions requested as a result of a system 133 * triggered dialog, not an app-triggered dialog 134 * @param sessionId: A long to identify this session 135 * @param storedState: Previous state, if this activity was stopped and is being recreated 136 */ 137 class GrantPermissionsViewModel( 138 private val app: Application, 139 private val packageName: String, 140 private val deviceId: Int, 141 private val requestedPermissions: List<String>, 142 private val systemRequestedPermissions: List<String>, 143 private val sessionId: Long, 144 private val storedState: Bundle? 145 ) : ViewModel() { 146 private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName 147 private val user = Process.myUserHandle() 148 private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user, deviceId] 149 private val safetyLabelInfoLiveData = 150 if ( 151 SdkLevel.isAtLeastU() && 152 requestedPermissions 153 .mapNotNull { PermissionMapping.getGroupOfPlatformPermission(it) } 154 .any { PermissionMapping.isSafetyLabelAwarePermissionGroup(it) } 155 ) { 156 SafetyLabelInfoLiveData[packageName, user] 157 } else { 158 null 159 } 160 private val permissionGroupToDeviceIdMap: Map<String, Int> = 161 if (SdkLevel.isAtLeastV() && Flags.allowHostPermissionDialogsOnVirtualDevices()) { 162 requestedPermissions 163 .filter({ PermissionMapping.getGroupOfPlatformPermission(it) != null }) 164 .associateBy({ PermissionMapping.getGroupOfPlatformPermission(it)!! }, { 165 if (isPermissionDeviceAware( 166 app.applicationContext, 167 deviceId, 168 it 169 ) 170 ) deviceId else Context.DEVICE_ID_DEFAULT 171 }) 172 } else { 173 ArrayMap() 174 } 175 private val dpm = app.getSystemService(DevicePolicyManager::class.java)!! 176 private val permissionPolicy = dpm.getPermissionPolicy(null) 177 private val groupStates = mutableMapOf<String, GroupState>() 178 179 private var autoGrantNotifier: AutoGrantPermissionsNotifier? = null 180 181 private fun getAutoGrantNotifier(): AutoGrantPermissionsNotifier? { 182 var fullPackageInfo = packageInfo.toPackageInfo(app) 183 if (fullPackageInfo == null) { 184 // try twice 185 fullPackageInfo = packageInfo.toPackageInfo(app) 186 } 187 if (fullPackageInfo == null) { 188 // We've tried to get our package info twice, and failed twice. Close the grant dialog, 189 // because the app is not accessible. 190 requestInfosLiveData.value = null 191 return null 192 } 193 autoGrantNotifier = AutoGrantPermissionsNotifier(app, fullPackageInfo) 194 return autoGrantNotifier!! 195 } 196 197 private lateinit var packageInfo: LightPackageInfo 198 199 // All permissions that could possibly be affected by the provided requested permissions, before 200 // filtering system fixed, auto grant, etc. 201 private var unfilteredAffectedPermissions = requestedPermissions 202 203 private var appPermGroupLiveDatas = mutableMapOf<String, LightAppPermGroupLiveData>() 204 205 internal data class ResultCallback(val consumer: Consumer<Intent?>, val requestCode: Int) 206 207 private var activityResultCallback: ResultCallback? = null 208 209 init { 210 if (storedState?.containsKey(SAVED_REQUEST_CODE_KEY) == true) { 211 if (storedState.getInt(SAVED_REQUEST_CODE_KEY) == PHOTO_PICKER_REQUEST_CODE) { 212 setPhotoPickerCallback() 213 } 214 } 215 } 216 217 /** 218 * An internal class which represents the state of a current AppPermissionGroup grant request. 219 * It is made up of the following: 220 * 221 * @param group The LightAppPermGroup representing the current state of the permissions for this 222 * app 223 * @param affectedPermissions The permissions that should be affected by this 224 */ 225 internal class GroupState( 226 internal val group: LightAppPermGroup, 227 internal val affectedPermissions: MutableSet<String> = mutableSetOf(), 228 internal var state: Int = STATE_UNKNOWN, 229 ) { 230 val fgPermissions = affectedPermissions - group.backgroundPermNames.toSet() 231 val bgPermissions = affectedPermissions - fgPermissions 232 233 override fun toString(): String { 234 val stateStr: String = 235 when (state) { 236 STATE_UNKNOWN -> "unknown" 237 STATE_GRANTED -> "granted" 238 STATE_DENIED -> "denied" 239 STATE_FG_GRANTED_BG_UNKNOWN -> "foreground granted, background unknown" 240 else -> "skipped" 241 } 242 return "${group.permGroupName} $stateStr $affectedPermissions" 243 } 244 } 245 246 data class RequestInfo( 247 val groupInfo: LightPermGroupInfo, 248 val prompt: Prompt, 249 val deny: DenyButton, 250 val showRationale: Boolean, 251 val deviceId: Int = ContextCompat.DEVICE_ID_DEFAULT 252 ) { 253 val groupName = groupInfo.name 254 } 255 256 val requestInfosLiveData = 257 object : SmartUpdateMediatorLiveData<List<RequestInfo>>() { 258 private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName 259 private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user] 260 261 init { 262 addSource(packagePermissionsLiveData) { onPackageLoaded() } 263 addSource(packageInfoLiveData) { onPackageLoaded() } 264 if (safetyLabelInfoLiveData != null) { 265 addSource(safetyLabelInfoLiveData) { onPackageLoaded() } 266 } 267 268 // Load package state, if available 269 onPackageLoaded() 270 } 271 272 private fun onPackageLoaded() { 273 if ( 274 packageInfoLiveData.isStale || 275 packagePermissionsLiveData.isStale || 276 (safetyLabelInfoLiveData != null && safetyLabelInfoLiveData.isStale) 277 ) { 278 return 279 } 280 281 val groups = packagePermissionsLiveData.value 282 val pI = packageInfoLiveData.value 283 if (groups.isNullOrEmpty() || pI == null) { 284 Log.e(LOG_TAG, "Package $packageName not found") 285 value = null 286 return 287 } 288 packageInfo = pI 289 290 if ( 291 packageInfo.requestedPermissions.isEmpty() || 292 packageInfo.targetSdkVersion < Build.VERSION_CODES.M 293 ) { 294 Log.e( 295 LOG_TAG, 296 "Package $packageName has no requested permissions, or " + "is a pre-M app" 297 ) 298 value = null 299 return 300 } 301 302 val affectedPermissions = requestedPermissions.toMutableSet() 303 for (requestedPerm in requestedPermissions) { 304 affectedPermissions.addAll(getAffectedSplitPermissions(requestedPerm)) 305 } 306 if (packageInfo.targetSdkVersion < Build.VERSION_CODES.O) { 307 // For < O apps all permissions of the groups of the requested ones are affected 308 for (affectedPerm in affectedPermissions.toSet()) { 309 val otherGroupPerms = 310 groups.values.firstOrNull { affectedPerm in it } ?: emptyList() 311 affectedPermissions.addAll(otherGroupPerms) 312 } 313 } 314 unfilteredAffectedPermissions = affectedPermissions.toList() 315 316 setAppPermGroupsLiveDatas( 317 groups.toMutableMap().apply { 318 remove(PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS) 319 } 320 ) 321 } 322 323 private fun setAppPermGroupsLiveDatas(groups: Map<String, List<String>>) { 324 val requestedGroups = 325 groups.filter { (_, perms) -> 326 perms.any { it in unfilteredAffectedPermissions } 327 } 328 329 if (requestedGroups.isEmpty()) { 330 Log.e(LOG_TAG, "None of " + "$unfilteredAffectedPermissions in $groups") 331 value = null 332 return 333 } 334 335 val getLiveDataFun = { groupName: String -> 336 LightAppPermGroupLiveData[packageName, groupName, user, 337 permissionGroupToDeviceIdMap.get(groupName) ?: deviceId] 338 } 339 setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun) 340 } 341 342 override fun onUpdate() { 343 if (appPermGroupLiveDatas.any { it.value.isStale }) { 344 return 345 } 346 var newGroups = false 347 for ((groupName, groupLiveData) in appPermGroupLiveDatas) { 348 val appPermGroup = groupLiveData.value 349 if (appPermGroup == null) { 350 Log.e(LOG_TAG, "Group $packageName $groupName invalid") 351 groupStates[groupName]?.state = STATE_SKIPPED 352 continue 353 } 354 355 packageInfo = appPermGroup.packageInfo 356 357 val state = groupStates[groupName] 358 if (state != null) { 359 val allAffectedGranted = 360 state.affectedPermissions.all { perm -> 361 appPermGroup.permissions[perm]?.isGranted == true && 362 appPermGroup.permissions[perm]?.isRevokeWhenRequested == false 363 } 364 if (allAffectedGranted) { 365 groupStates[groupName]!!.state = STATE_GRANTED 366 } 367 } else { 368 newGroups = true 369 } 370 } 371 372 if (newGroups) { 373 addRequiredGroupStates(appPermGroupLiveDatas.mapNotNull { it.value.value }) 374 } 375 setRequestInfosFromGroupStates() 376 } 377 378 private fun setRequestInfosFromGroupStates() { 379 val requestInfos = mutableListOf<RequestInfo>() 380 for (groupState in groupStates.values) { 381 if (!isStateUnknown(groupState.state)) { 382 continue 383 } 384 val behavior = getGrantBehavior(groupState.group) 385 val isSystemTriggered = 386 groupState.affectedPermissions.any { it in systemRequestedPermissions } 387 val prompt = 388 behavior.getPrompt( 389 groupState.group, 390 groupState.affectedPermissions, 391 isSystemTriggered 392 ) 393 if (prompt == Prompt.NO_UI_REJECT_ALL_GROUPS) { 394 value = null 395 return 396 } 397 if (prompt == Prompt.NO_UI_REJECT_THIS_GROUP) { 398 reportRequestResult( 399 groupState.affectedPermissions, 400 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED 401 ) 402 continue 403 } 404 405 val denyBehavior = 406 behavior.getDenyButton( 407 groupState.group, 408 groupState.affectedPermissions, 409 prompt 410 ) 411 val safetyLabel = safetyLabelInfoLiveData?.value?.safetyLabel 412 requestInfos.add( 413 RequestInfo( 414 groupState.group.permGroupInfo, 415 prompt, 416 denyBehavior, 417 shouldShowPermissionRationale( 418 safetyLabel, 419 groupState.group.permGroupName 420 ), 421 permissionGroupToDeviceIdMap.get(groupState.group.permGroupName) 422 ?: deviceId 423 ) 424 ) 425 } 426 sortPermissionGroups(requestInfos) 427 428 value = 429 if ( 430 requestInfos.any { it.prompt == Prompt.NO_UI_SETTINGS_REDIRECT } && 431 requestInfos.size > 1 432 ) { 433 Log.e( 434 LOG_TAG, 435 "For R+ apps, background permissions must be requested " + 436 "individually" 437 ) 438 null 439 } else { 440 requestInfos 441 } 442 } 443 } 444 445 private fun sortPermissionGroups(requestInfos: MutableList<RequestInfo>) { 446 requestInfos.sortWith { rhs, lhs -> 447 val rhsHasOneTime = isOneTimePrompt(rhs.prompt) 448 val lhsHasOneTime = isOneTimePrompt(lhs.prompt) 449 if (rhsHasOneTime && !lhsHasOneTime) { 450 -1 451 } else if ( 452 (!rhsHasOneTime && lhsHasOneTime) || Utils.isHealthPermissionGroup(rhs.groupName) 453 ) { 454 1 455 } else { 456 rhs.groupName.compareTo(lhs.groupName) 457 } 458 } 459 } 460 461 private fun isOneTimePrompt(prompt: Prompt): Boolean { 462 return prompt in 463 setOf( 464 Prompt.ONE_TIME_FG, 465 Prompt.SETTINGS_LINK_WITH_OT, 466 Prompt.LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT, 467 Prompt.LOCATION_TWO_BUTTON_FINE_HIGHLIGHT, 468 Prompt.LOCATION_COARSE_ONLY, 469 Prompt.LOCATION_FINE_UPGRADE 470 ) 471 } 472 473 private fun shouldShowPermissionRationale( 474 safetyLabel: SafetyLabel?, 475 permissionGroupName: String? 476 ): Boolean { 477 if (safetyLabel == null || permissionGroupName == null) { 478 return false 479 } 480 481 val purposes = 482 SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup(safetyLabel, permissionGroupName) 483 return purposes.isNotEmpty() 484 } 485 486 /** 487 * Converts a list of LightAppPermGroups into a list of GroupStates, and adds new GroupState 488 * objects to the tracked groupStates. 489 */ 490 private fun addRequiredGroupStates(groups: List<LightAppPermGroup>) { 491 val filteredPermissions = 492 unfilteredAffectedPermissions.filter { perm -> 493 val group = getGroupWithPerm(perm, groups) 494 group != null && isPermissionGrantableAndNotFixed(perm, group) 495 } 496 val newGroupStates = mutableMapOf<String, GroupState>() 497 for (perm in filteredPermissions) { 498 val group = getGroupWithPerm(perm, groups)!! 499 500 val oldGroupState = groupStates[group.permGroupName] 501 if (!isStateUnknown(oldGroupState?.state)) { 502 // we've already dealt with this group 503 continue 504 } 505 506 val groupState = newGroupStates.getOrPut(group.permGroupName) { GroupState(group) } 507 508 var currGroupState = groupState.state 509 if (storedState != null && !isStateUnknown(groupState.state)) { 510 currGroupState = storedState.getInt(group.permGroupName, STATE_UNKNOWN) 511 } 512 513 val otherAffectedPermissionsInGroup = 514 filteredPermissions.filter { it in group.permissions }.toSet() 515 val groupStateOfPerm = getGroupState(perm, group, otherAffectedPermissionsInGroup) 516 if (groupStateOfPerm != STATE_UNKNOWN) { 517 // update the state if it is allowed, denied, or granted in foreground 518 currGroupState = groupStateOfPerm 519 } 520 521 if (currGroupState != STATE_UNKNOWN) { 522 groupState.state = currGroupState 523 } 524 525 groupState.affectedPermissions.add(perm) 526 } 527 newGroupStates.forEach { (groupName, groupState) -> groupStates[groupName] = groupState } 528 } 529 530 /** 531 * Add additional permissions that should be granted in this request. For permissions that have 532 * split permissions, and apps that target an SDK before the split, this method automatically 533 * adds the split off permission. 534 * 535 * @param perm The requested permission 536 * @return The requested permissions plus any needed split permissions 537 */ 538 private fun getAffectedSplitPermissions( 539 perm: String, 540 ): List<String> { 541 val requestingAppTargetSDK = packageInfo.targetSdkVersion 542 543 // If a permission is split, all permissions the original permission is split into are 544 // affected 545 val extendedBySplitPerms = mutableListOf(perm) 546 547 val splitPerms = app.getSystemService(PermissionManager::class.java)!!.splitPermissions 548 for (splitPerm in splitPerms) { 549 if (requestingAppTargetSDK < splitPerm.targetSdk && perm == splitPerm.splitPermission) { 550 extendedBySplitPerms.addAll(splitPerm.newPermissions) 551 } 552 } 553 return extendedBySplitPerms 554 } 555 556 private fun isPermissionGrantableAndNotFixed(perm: String, group: LightAppPermGroup): Boolean { 557 // If the permission is restricted it does not show in the UI and 558 // is not added to the group at all, so check that first. 559 if (perm in group.packageInfo.requestedPermissions && perm !in group.permissions) { 560 reportRequestResult( 561 perm, 562 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION 563 ) 564 return false 565 } 566 567 val subGroup = 568 if (perm in group.backgroundPermNames) { 569 group.background 570 } else { 571 group.foreground 572 } 573 574 val lightPermission = group.permissions[perm] ?: return false 575 576 if (!subGroup.isGrantable) { 577 reportRequestResult(perm, PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED) 578 // Skip showing groups that we know cannot be granted. 579 return false 580 } 581 582 if (subGroup.isPolicyFixed && !subGroup.isGranted || lightPermission.isPolicyFixed) { 583 reportRequestResult( 584 perm, 585 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED 586 ) 587 return false 588 } 589 590 val behavior = getGrantBehavior(group) 591 if (behavior.isPermissionFixed(group, perm)) { 592 reportRequestResult( 593 perm, 594 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED 595 ) 596 return false 597 } 598 599 return true 600 } 601 602 private fun getGroupState( 603 perm: String, 604 group: LightAppPermGroup, 605 groupRequestedPermissions: Set<String> 606 ): Int { 607 val policyState = getStateFromPolicy(perm, group) 608 if (!isStateUnknown(policyState)) { 609 return policyState 610 } 611 612 val isBackground = perm in group.backgroundPermNames 613 614 val behavior = getGrantBehavior(group) 615 return if (behavior.isGroupFullyGranted(group, groupRequestedPermissions)) { 616 if (group.permissions[perm]?.isGranted == false) { 617 if (isBackground) { 618 grantBackgroundRuntimePermissions(app, group, listOf(perm)) 619 } else { 620 grantForegroundRuntimePermissions(app, group, listOf(perm), group.isOneTime) 621 } 622 KotlinUtils.setGroupFlags( 623 app, 624 group, 625 FLAG_PERMISSION_USER_SET to false, 626 FLAG_PERMISSION_USER_FIXED to false, 627 filterPermissions = listOf(perm) 628 ) 629 reportRequestResult( 630 perm, 631 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED 632 ) 633 } 634 STATE_GRANTED 635 } else if (behavior.isForegroundFullyGranted(group, groupRequestedPermissions)) { 636 STATE_FG_GRANTED_BG_UNKNOWN 637 } else { 638 STATE_UNKNOWN 639 } 640 } 641 642 private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int { 643 val isBackground = perm in group.backgroundPermNames 644 var state = STATE_UNKNOWN 645 when (permissionPolicy) { 646 DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT -> { 647 if ( 648 AdminRestrictedPermissionsUtils.mayAdminGrantPermission( 649 app, 650 perm, 651 user.identifier 652 ) 653 ) { 654 if (isBackground) { 655 grantBackgroundRuntimePermissions(app, group, listOf(perm)) 656 } else { 657 grantForegroundRuntimePermissions(app, group, listOf(perm)) 658 } 659 KotlinUtils.setGroupFlags( 660 app, 661 group, 662 FLAG_PERMISSION_POLICY_FIXED to true, 663 FLAG_PERMISSION_USER_SET to false, 664 FLAG_PERMISSION_USER_FIXED to false, 665 filterPermissions = listOf(perm) 666 ) 667 state = STATE_GRANTED 668 getAutoGrantNotifier()?.onPermissionAutoGranted(perm) 669 reportRequestResult( 670 perm, 671 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED 672 ) 673 } 674 } 675 DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY -> { 676 if (group.permissions[perm]?.isPolicyFixed == false) { 677 KotlinUtils.setGroupFlags( 678 app, 679 group, 680 FLAG_PERMISSION_POLICY_FIXED to true, 681 FLAG_PERMISSION_USER_SET to false, 682 FLAG_PERMISSION_USER_FIXED to false, 683 filterPermissions = listOf(perm) 684 ) 685 } 686 state = STATE_DENIED 687 reportRequestResult( 688 perm, 689 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED 690 ) 691 } 692 } 693 return state 694 } 695 696 /** 697 * Upon the user clicking a button, grant permissions, if applicable. 698 * 699 * @param groupName The name of the permission group which was changed 700 * @param affectedForegroundPermissions The name of the foreground permission which was changed 701 * @param result The choice the user made regarding the group. 702 */ 703 fun onPermissionGrantResult( 704 groupName: String?, 705 affectedForegroundPermissions: List<String>?, 706 result: Int 707 ) { 708 onPermissionGrantResult(groupName, affectedForegroundPermissions, result, false) 709 } 710 711 private fun onPermissionGrantResult( 712 groupName: String?, 713 affectedForegroundPermissions: List<String>?, 714 result: Int, 715 alreadyRequestedStorageGroupsIfNeeded: Boolean 716 ) { 717 if (groupName == null) { 718 return 719 } 720 721 // If this is a legacy app, and a storage group is requested: request all storage groups 722 if ( 723 !alreadyRequestedStorageGroupsIfNeeded && 724 groupName in PermissionMapping.STORAGE_SUPERGROUP_PERMISSIONS && 725 packageInfo.targetSdkVersion <= Build.VERSION_CODES.S_V2 726 ) { 727 for (storageGroupName in PermissionMapping.STORAGE_SUPERGROUP_PERMISSIONS) { 728 val groupPerms = 729 appPermGroupLiveDatas[storageGroupName]?.value?.allPermissions?.keys?.toList() 730 onPermissionGrantResult(storageGroupName, groupPerms, result, true) 731 } 732 return 733 } 734 735 val groupState = groupStates[groupName] ?: return 736 when (result) { 737 CANCELED -> { 738 reportRequestResult( 739 groupState.affectedPermissions, 740 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED 741 ) 742 groupState.state = STATE_SKIPPED 743 requestInfosLiveData.update() 744 return 745 } 746 GRANTED_ALWAYS -> { 747 onPermissionGrantResultSingleState( 748 groupState, 749 affectedForegroundPermissions, 750 granted = true, 751 isOneTime = false, 752 foregroundOnly = false, 753 doNotAskAgain = false 754 ) 755 } 756 GRANTED_FOREGROUND_ONLY -> { 757 onPermissionGrantResultSingleState( 758 groupState, 759 affectedForegroundPermissions, 760 granted = true, 761 isOneTime = false, 762 foregroundOnly = true, 763 doNotAskAgain = false 764 ) 765 } 766 GRANTED_ONE_TIME -> { 767 onPermissionGrantResultSingleState( 768 groupState, 769 affectedForegroundPermissions, 770 granted = true, 771 isOneTime = true, 772 foregroundOnly = false, 773 doNotAskAgain = false 774 ) 775 } 776 GRANTED_USER_SELECTED, 777 DENIED_MORE -> { 778 grantUserSelectedVisualGroupPermissions(groupState) 779 } 780 DENIED -> { 781 onPermissionGrantResultSingleState( 782 groupState, 783 affectedForegroundPermissions, 784 granted = false, 785 isOneTime = false, 786 foregroundOnly = false, 787 doNotAskAgain = false 788 ) 789 } 790 DENIED_DO_NOT_ASK_AGAIN -> { 791 onPermissionGrantResultSingleState( 792 groupState, 793 affectedForegroundPermissions, 794 granted = false, 795 isOneTime = false, 796 foregroundOnly = false, 797 doNotAskAgain = true 798 ) 799 } 800 } 801 } 802 803 private fun grantUserSelectedVisualGroupPermissions(groupState: GroupState) { 804 val userSelectedPerm = 805 groupState.group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] ?: return 806 if (userSelectedPerm.isImplicit) { 807 val nonSelectedPerms = 808 groupState.group.permissions.keys.filter { it != READ_MEDIA_VISUAL_USER_SELECTED } 809 // If the permission is implicit, grant USER_SELECTED as user set, and all other 810 // permissions as one time, and without app ops. 811 grantForegroundRuntimePermissions( 812 app, 813 groupState.group, 814 listOf(READ_MEDIA_VISUAL_USER_SELECTED) 815 ) 816 grantForegroundRuntimePermissions( 817 app, 818 groupState.group, 819 nonSelectedPerms, 820 isOneTime = true, 821 userFixed = false, 822 withoutAppOps = true 823 ) 824 val appPermGroup = 825 AppPermissionGroup.create( 826 app, 827 packageName, 828 groupState.group.permGroupName, 829 groupState.group.userHandle, 830 false 831 ) 832 appPermGroup.setSelfRevoked() 833 appPermGroup.persistChanges(false, null, nonSelectedPerms.toSet()) 834 } else { 835 val partialPerms = 836 getPartialStorageGrantPermissionsForGroup(groupState.group).filter { 837 it in groupState.affectedPermissions 838 } 839 val nonSelectedPerms = groupState.affectedPermissions.filter { it !in partialPerms } 840 val setUserFixed = userSelectedPerm.isUserFixed || userSelectedPerm.isUserSet 841 grantForegroundRuntimePermissions( 842 app, 843 groupState.group, 844 partialPerms.toList(), 845 userFixed = setUserFixed 846 ) 847 revokeForegroundRuntimePermissions( 848 app, 849 groupState.group, 850 userFixed = setUserFixed, 851 oneTime = false, 852 filterPermissions = nonSelectedPerms 853 ) 854 } 855 groupState.state = STATE_GRANTED 856 reportButtonClickResult( 857 groupState, 858 groupState.affectedPermissions, 859 true, 860 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED 861 ) 862 } 863 864 @SuppressLint("NewApi") 865 private fun onPermissionGrantResultSingleState( 866 groupState: GroupState, 867 affectedForegroundPermissions: List<String>?, 868 granted: Boolean, 869 foregroundOnly: Boolean, 870 isOneTime: Boolean, 871 doNotAskAgain: Boolean 872 ) { 873 if (!isStateUnknown(groupState.state)) { 874 // We already dealt with this group, don't re-grant/re-revoke 875 return 876 } 877 val shouldAffectBackgroundPermissions = 878 groupState.bgPermissions.isNotEmpty() && !foregroundOnly 879 val shouldAffectForegroundPermssions = groupState.state != STATE_FG_GRANTED_BG_UNKNOWN 880 val result: Int 881 if (granted) { 882 result = 883 if (isOneTime) { 884 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME 885 } else { 886 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED 887 } 888 var affectedPermissions: Collection<String> = groupState.affectedPermissions 889 if (shouldAffectBackgroundPermissions) { 890 grantBackgroundRuntimePermissions(app, groupState.group, affectedPermissions) 891 } else if (shouldAffectForegroundPermssions) { 892 if (affectedForegroundPermissions == null) { 893 grantForegroundRuntimePermissions( 894 app, 895 groupState.group, 896 affectedPermissions, 897 isOneTime 898 ) 899 // This prevents weird flag state when app targetSDK switches from S+ to R- 900 if (groupState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) { 901 KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, groupState.group, true) 902 } 903 } else { 904 affectedPermissions = affectedForegroundPermissions 905 val newGroup = 906 grantForegroundRuntimePermissions( 907 app, 908 groupState.group, 909 affectedPermissions, 910 isOneTime 911 ) 912 if (!isOneTime || newGroup.isOneTime) { 913 KotlinUtils.setFlagsWhenLocationAccuracyChanged( 914 app, 915 newGroup, 916 affectedForegroundPermissions.contains(ACCESS_FINE_LOCATION) 917 ) 918 } 919 } 920 } 921 val shouldDenyFullGroupGrant = 922 groupState.group.isPlatformPermissionGroup && 923 affectedPermissions.none { 924 groupState.group.permissions[it]?.isPlatformOrSystem == true 925 } 926 groupState.state = 927 if (shouldDenyFullGroupGrant) { 928 STATE_UNKNOWN 929 } else { 930 STATE_GRANTED 931 } 932 } else { 933 if (shouldAffectBackgroundPermissions) { 934 revokeBackgroundRuntimePermissions( 935 app, 936 groupState.group, 937 userFixed = doNotAskAgain, 938 filterPermissions = groupState.affectedPermissions 939 ) 940 } else if (shouldAffectForegroundPermssions) { 941 if ( 942 affectedForegroundPermissions == null || 943 affectedForegroundPermissions.contains(ACCESS_COARSE_LOCATION) 944 ) { 945 revokeForegroundRuntimePermissions( 946 app, 947 groupState.group, 948 userFixed = doNotAskAgain, 949 filterPermissions = groupState.affectedPermissions, 950 oneTime = isOneTime 951 ) 952 } else { 953 revokeForegroundRuntimePermissions( 954 app, 955 groupState.group, 956 userFixed = doNotAskAgain, 957 filterPermissions = affectedForegroundPermissions, 958 oneTime = isOneTime 959 ) 960 } 961 } 962 result = 963 if (doNotAskAgain) { 964 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE 965 } else { 966 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED 967 } 968 groupState.state = STATE_DENIED 969 } 970 val permissionsChanged = 971 if (foregroundOnly) { 972 groupState.fgPermissions 973 } else { 974 groupState.affectedPermissions 975 } 976 reportButtonClickResult(groupState, permissionsChanged, granted, result) 977 } 978 979 private fun reportButtonClickResult( 980 groupState: GroupState, 981 permissions: Set<String>, 982 granted: Boolean, 983 result: Int 984 ) { 985 reportRequestResult(permissions, result) 986 // group state has changed, reload liveData 987 requestInfosLiveData.update() 988 989 if (SdkLevel.isAtLeastT()) { 990 PermissionDecisionStorageImpl.recordPermissionDecision( 991 app.applicationContext, 992 packageName, 993 groupState.group.permGroupName, 994 granted 995 ) 996 PermissionChangeStorageImpl.recordPermissionChange(packageName) 997 } 998 if (granted) { 999 startDrivingDecisionReminderServiceIfNecessary(groupState.group.permGroupName) 1000 } 1001 } 1002 1003 /** 1004 * When distraction optimization is required (the vehicle is in motion), the user may want to 1005 * review their permission grants when they are less distracted. 1006 */ 1007 private fun startDrivingDecisionReminderServiceIfNecessary(permGroupName: String) { 1008 if (!DeviceUtils.isAuto(app.applicationContext)) { 1009 return 1010 } 1011 DrivingDecisionReminderService.startServiceIfCurrentlyRestricted( 1012 Utils.getUserContext(app, user), 1013 packageName, 1014 permGroupName 1015 ) 1016 } 1017 1018 private fun getGroupWithPerm( 1019 perm: String, 1020 groups: List<LightAppPermGroup> 1021 ): LightAppPermGroup? { 1022 val groupsWithPerm = groups.filter { perm in it.permissions } 1023 if (groupsWithPerm.isEmpty()) { 1024 return null 1025 } 1026 return groupsWithPerm.first() 1027 } 1028 1029 private fun reportRequestResult(permissions: Collection<String>, result: Int) { 1030 permissions.forEach { reportRequestResult(it, result) } 1031 } 1032 1033 /** 1034 * Report the result of a grant of a permission. 1035 * 1036 * @param permission The permission that was granted or denied 1037 * @param result The permission grant result 1038 */ 1039 private fun reportRequestResult(permission: String, result: Int) { 1040 val isImplicit = permission !in requestedPermissions 1041 val isPermissionRationaleShown = 1042 shouldShowPermissionRationale( 1043 safetyLabelInfoLiveData?.value?.safetyLabel, 1044 PermissionMapping.getGroupOfPlatformPermission(permission) 1045 ) 1046 val isPackageRestrictedByEnhancedConfirmation = 1047 EnhancedConfirmationStatsLogUtils.isPackageEcmRestricted( 1048 app, 1049 packageName, 1050 packageInfo.uid 1051 ) 1052 1053 Log.i( 1054 LOG_TAG, 1055 "Permission grant result requestId=$sessionId " + 1056 "callingUid=${packageInfo.uid} " + 1057 "callingPackage=$packageName " + 1058 "permission=$permission " + 1059 "isImplicit=$isImplicit result=$result " + 1060 "isPermissionRationaleShown=$isPermissionRationaleShown" + 1061 "isPackageRestrictedByEnhancedConfirmation=" + 1062 "$isPackageRestrictedByEnhancedConfirmation" 1063 ) 1064 1065 PermissionControllerStatsLog.write( 1066 PERMISSION_GRANT_REQUEST_RESULT_REPORTED, 1067 sessionId, 1068 packageInfo.uid, 1069 packageName, 1070 permission, 1071 isImplicit, 1072 result, 1073 isPermissionRationaleShown, 1074 isPackageRestrictedByEnhancedConfirmation 1075 ) 1076 } 1077 1078 /** 1079 * Save the group states of the view model, to allow for state restoration after lifecycle 1080 * events 1081 * 1082 * @param outState The bundle in which to store state 1083 */ 1084 fun saveInstanceState(outState: Bundle) { 1085 for ((groupName, groupState) in groupStates) { 1086 outState.putInt(groupName, groupState.state) 1087 } 1088 activityResultCallback?.let { outState.putInt(SAVED_REQUEST_CODE_KEY, it.requestCode) } 1089 } 1090 1091 /** 1092 * Determine if the activity should return permission state to the caller 1093 * 1094 * @return Whether or not state should be returned. False only if the package is pre-M, true 1095 * otherwise. 1096 */ 1097 fun shouldReturnPermissionState(): Boolean { 1098 return if (packageInfoLiveData.value != null) { 1099 packageInfoLiveData.value!!.targetSdkVersion >= Build.VERSION_CODES.M 1100 } else { 1101 // Should not be reached, as this method shouldn't be called before data is passed to 1102 // the activity for the first time 1103 try { 1104 Utils.getUserContext(app, user) 1105 .packageManager 1106 .getApplicationInfo(packageName, 0) 1107 .targetSdkVersion >= Build.VERSION_CODES.M 1108 } catch (e: PackageManager.NameNotFoundException) { 1109 true 1110 } 1111 } 1112 } 1113 1114 fun handleCallback(data: Intent?, requestCode: Int) { 1115 val currCallback = activityResultCallback 1116 if (currCallback == null || requestCode != currCallback.requestCode) { 1117 return 1118 } 1119 currCallback.consumer.accept(data) 1120 activityResultCallback = null 1121 } 1122 1123 fun handleHealthConnectPermissions(activity: Activity) { 1124 if (activityResultCallback == null) { 1125 activityResultCallback = 1126 ResultCallback( 1127 { 1128 groupStates[HEALTH_PERMISSION_GROUP]?.state = STATE_SKIPPED 1129 requestInfosLiveData.update() 1130 }, 1131 APP_PERMISSION_REQUEST_CODE 1132 ) 1133 val healthPermissions = 1134 unfilteredAffectedPermissions 1135 .filter { permission -> isHealthPermission(activity, permission) } 1136 .toTypedArray() 1137 val intent: Intent = 1138 Intent(ACTION_REQUEST_HEALTH_PERMISSIONS) 1139 .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) 1140 .putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, healthPermissions) 1141 .putExtra(Intent.EXTRA_USER, Process.myUserHandle()) 1142 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 1143 activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE) 1144 } 1145 } 1146 1147 /** 1148 * Send the user directly to the AppPermissionFragment. Used for R+ apps. 1149 * 1150 * @param activity The current activity 1151 * @param groupName The name of the permission group whose fragment should be opened 1152 */ 1153 fun sendDirectlyToSettings(activity: Activity, groupName: String) { 1154 if (activityResultCallback == null) { 1155 activityResultCallback = 1156 ResultCallback( 1157 Consumer { data -> 1158 if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) { 1159 // User didn't interact, count against rate limit 1160 val group = groupStates[groupName]?.group ?: return@Consumer 1161 if (group.background.isUserSet) { 1162 KotlinUtils.setGroupFlags( 1163 app, 1164 group, 1165 FLAG_PERMISSION_USER_FIXED to true, 1166 filterPermissions = group.backgroundPermNames 1167 ) 1168 } else { 1169 KotlinUtils.setGroupFlags( 1170 app, 1171 group, 1172 FLAG_PERMISSION_USER_SET to true, 1173 filterPermissions = group.backgroundPermNames 1174 ) 1175 } 1176 } 1177 1178 groupStates[groupName]?.state = STATE_SKIPPED 1179 // Update our liveData now that there is a new skipped group 1180 requestInfosLiveData.update() 1181 }, 1182 APP_PERMISSION_REQUEST_CODE 1183 ) 1184 startAppPermissionFragment(activity, groupName) 1185 } 1186 } 1187 1188 fun openPhotoPicker(activity: Activity) { 1189 if (activityResultCallback != null) { 1190 return 1191 } 1192 if (groupStates[READ_MEDIA_VISUAL]?.affectedPermissions == null) { 1193 return 1194 } 1195 setPhotoPickerCallback() 1196 openPhotoPickerForApp( 1197 activity, 1198 packageInfo.uid, 1199 unfilteredAffectedPermissions, 1200 PHOTO_PICKER_REQUEST_CODE 1201 ) 1202 } 1203 1204 private fun setPhotoPickerCallback() { 1205 activityResultCallback = 1206 ResultCallback( 1207 { data -> 1208 val anySelected = data?.getBooleanExtra(INTENT_PHOTOS_SELECTED, true) == true 1209 if (anySelected) { 1210 onPermissionGrantResult(READ_MEDIA_VISUAL, null, GRANTED_USER_SELECTED) 1211 } else { 1212 onPermissionGrantResult(READ_MEDIA_VISUAL, null, CANCELED) 1213 } 1214 requestInfosLiveData.update() 1215 }, 1216 PHOTO_PICKER_REQUEST_CODE 1217 ) 1218 } 1219 1220 /** 1221 * Send the user to the AppPermissionFragment from a link. Used for Q- apps 1222 * 1223 * @param activity The current activity 1224 * @param groupName The name of the permission group whose fragment should be opened 1225 */ 1226 fun sendToSettingsFromLink(activity: Activity, groupName: String) { 1227 startAppPermissionFragment(activity, groupName) 1228 activityResultCallback = 1229 ResultCallback( 1230 { data -> 1231 val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) 1232 if (returnGroupName != null) { 1233 groupStates[returnGroupName]?.state = STATE_SKIPPED 1234 val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, -1) 1235 logSettingsInteraction(returnGroupName, result) 1236 requestInfosLiveData.update() 1237 } 1238 }, 1239 APP_PERMISSION_REQUEST_CODE 1240 ) 1241 } 1242 1243 /** 1244 * Shows the Permission Rationale Dialog. For use with U+ only, otherwise no-op. 1245 * 1246 * @param activity The current activity 1247 * @param groupName The name of the permission group whose fragment should be opened 1248 */ 1249 fun showPermissionRationaleActivity(activity: Activity, groupName: String) { 1250 if (!SdkLevel.isAtLeastU()) { 1251 return 1252 } 1253 1254 val intent = 1255 Intent(activity, PermissionRationaleActivity::class.java).apply { 1256 putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) 1257 putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName) 1258 putExtra(Constants.EXTRA_SESSION_ID, sessionId) 1259 } 1260 activityResultCallback = 1261 ResultCallback( 1262 { data -> 1263 val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) 1264 if (returnGroupName != null) { 1265 groupStates[returnGroupName]?.state = STATE_SKIPPED 1266 val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, CANCELED) 1267 logSettingsInteraction(returnGroupName, result) 1268 requestInfosLiveData.update() 1269 } 1270 }, 1271 APP_PERMISSION_REQUEST_CODE 1272 ) 1273 activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE) 1274 } 1275 1276 private fun startAppPermissionFragment(activity: Activity, groupName: String) { 1277 val intent = 1278 Intent(Intent.ACTION_MANAGE_APP_PERMISSION) 1279 .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) 1280 .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName) 1281 .putExtra(Intent.EXTRA_USER, user) 1282 .putExtra( 1283 ManagePermissionsActivity.EXTRA_CALLER_NAME, 1284 GrantPermissionsActivity::class.java.name 1285 ) 1286 .putExtra(Constants.EXTRA_SESSION_ID, sessionId) 1287 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 1288 activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE) 1289 } 1290 1291 private fun getGrantBehavior(group: LightAppPermGroup): GrantBehavior { 1292 return when (group.permGroupName) { 1293 LOCATION -> LocationGrantBehavior 1294 HEALTH_PERMISSION_GROUP -> HealthGrantBehavior 1295 NOTIFICATIONS -> NotificationGrantBehavior 1296 STORAGE, 1297 READ_MEDIA_VISUAL, 1298 READ_MEDIA_AURAL -> StorageGrantBehavior 1299 else -> { 1300 if (Utils.hasPermWithBackgroundModeCompat(group)) { 1301 BackgroundGrantBehavior 1302 } else { 1303 BasicGrantBehavior 1304 } 1305 } 1306 } 1307 } 1308 1309 private fun logSettingsInteraction(groupName: String, result: Int) { 1310 val groupState = groupStates[groupName] ?: return 1311 val backgroundPerms = 1312 groupState.affectedPermissions.filter { it in groupState.group.backgroundPermNames } 1313 val foregroundPerms = groupState.affectedPermissions.filter { it !in backgroundPerms } 1314 val deniedPrejudiceInSettings = 1315 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS 1316 when (result) { 1317 GRANTED_ALWAYS -> { 1318 reportRequestResult( 1319 groupState.affectedPermissions, 1320 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS 1321 ) 1322 } 1323 GRANTED_FOREGROUND_ONLY -> { 1324 reportRequestResult( 1325 foregroundPerms, 1326 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS 1327 ) 1328 if (backgroundPerms.isNotEmpty()) { 1329 reportRequestResult( 1330 backgroundPerms, 1331 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS 1332 ) 1333 } 1334 } 1335 DENIED -> { 1336 reportRequestResult( 1337 groupState.affectedPermissions, 1338 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS 1339 ) 1340 } 1341 DENIED_DO_NOT_ASK_AGAIN -> { 1342 reportRequestResult(groupState.affectedPermissions, deniedPrejudiceInSettings) 1343 } 1344 } 1345 } 1346 1347 /** Log all permission groups which were requested */ 1348 fun logRequestedPermissionGroups() { 1349 if (groupStates.isEmpty()) { 1350 return 1351 } 1352 val groups = groupStates.map { it.value.group } 1353 SafetyNetLogger.logPermissionsRequested(packageName, packageInfo.uid, groups) 1354 } 1355 1356 /** 1357 * Log information about the buttons which were shown and clicked by the user. 1358 * 1359 * @param groupName The name of the permission group which was interacted with 1360 * @param selectedPrecision Selected precision of the location permission - bit flags indicate 1361 * which locations were chosen 1362 * @param clickedButton The button that was clicked by the user 1363 * @param presentedButtons All buttons which were shown to the user 1364 */ 1365 fun logClickedButtons( 1366 groupName: String?, 1367 selectedPrecision: Int, 1368 clickedButton: Int, 1369 presentedButtons: Int, 1370 isPermissionRationaleShown: Boolean 1371 ) { 1372 if (groupName == null) { 1373 return 1374 } 1375 1376 if (!requestInfosLiveData.isInitialized || !packageInfoLiveData.isInitialized) { 1377 Log.wtf( 1378 LOG_TAG, 1379 "Logged buttons presented and clicked permissionGroupName=" + 1380 "$groupName package=$packageName presentedButtons=$presentedButtons " + 1381 "clickedButton=$clickedButton isPermissionRationaleShown=" + 1382 "$isPermissionRationaleShown sessionId=$sessionId, but requests were not yet" + 1383 "initialized", 1384 IllegalStateException() 1385 ) 1386 return 1387 } 1388 1389 PermissionControllerStatsLog.write( 1390 GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS, 1391 groupName, 1392 packageInfo.uid, 1393 packageName, 1394 presentedButtons, 1395 clickedButton, 1396 sessionId, 1397 packageInfo.targetSdkVersion, 1398 selectedPrecision, 1399 isPermissionRationaleShown 1400 ) 1401 Log.i( 1402 LOG_TAG, 1403 "Logged buttons presented and clicked permissionGroupName=" + 1404 "$groupName uid=${packageInfo.uid} selectedPrecision=$selectedPrecision " + 1405 "package=$packageName presentedButtons=$presentedButtons " + 1406 "clickedButton=$clickedButton isPermissionRationaleShown=" + 1407 "$isPermissionRationaleShown sessionId=$sessionId " + 1408 "targetSdk=${packageInfo.targetSdkVersion}" 1409 ) 1410 } 1411 1412 /** Use the autoGrantNotifier to notify of auto-granted permissions. */ 1413 fun autoGrantNotify() { 1414 autoGrantNotifier?.notifyOfAutoGrantPermissions(true) 1415 } 1416 1417 private fun isStateUnknown(state: Int?): Boolean { 1418 return state == null || state == STATE_UNKNOWN || state == STATE_FG_GRANTED_BG_UNKNOWN 1419 } 1420 1421 companion object { 1422 const val APP_PERMISSION_REQUEST_CODE = 1 1423 const val PHOTO_PICKER_REQUEST_CODE = 2 1424 const val ECM_REQUEST_CODE = 3 1425 const val SAVED_REQUEST_CODE_KEY = "saved_request_code" 1426 private const val STATE_UNKNOWN = 0 1427 private const val STATE_GRANTED = 1 1428 private const val STATE_DENIED = 2 1429 private const val STATE_SKIPPED = 3 1430 private const val STATE_FG_GRANTED_BG_UNKNOWN = 4 1431 } 1432 } 1433 1434 /** 1435 * Factory for an AppPermissionViewModel 1436 * 1437 * @param app The current application 1438 * @param packageName The name of the package this ViewModel represents 1439 */ 1440 class GrantPermissionsViewModelFactory( 1441 private val app: Application, 1442 private val packageName: String, 1443 private val deviceId: Int, 1444 private val requestedPermissions: List<String>, 1445 private val systemRequestedPermissions: List<String>, 1446 private val sessionId: Long, 1447 private val savedState: Bundle? 1448 ) : ViewModelProvider.Factory { createnull1449 override fun <T : ViewModel> create(modelClass: Class<T>): T { 1450 @Suppress("UNCHECKED_CAST") 1451 return GrantPermissionsViewModel( 1452 app, 1453 packageName, 1454 deviceId, 1455 requestedPermissions, 1456 systemRequestedPermissions, 1457 sessionId, 1458 savedState 1459 ) 1460 as T 1461 } 1462 } 1463 1464 enum class Prompt { 1465 BASIC, // Allow/Deny 1466 ONE_TIME_FG, // Allow in foreground/one time/deny 1467 FG_ONLY, // Allow in foreground/deny 1468 SETTINGS_LINK_FOR_BG, // Allow in foreground/deny, with link to settings to change background 1469 SETTINGS_LINK_WITH_OT, // Same as above, but with a one time button 1470 UPGRADE_SETTINGS_LINK, // Keep foreground, with link to settings to grant background 1471 OT_UPGRADE_SETTINGS_LINK, // Same as above, but the button is "keep one time" 1472 LOCATION_TWO_BUTTON_COARSE_HIGHLIGHT, // Choose coarse/fine, foreground/one time/deny, coarse 1473 // button highlighted 1474 LOCATION_TWO_BUTTON_FINE_HIGHLIGHT, // Same as above, but fine location highlighted 1475 LOCATION_COARSE_ONLY, // Only coarse location, foreground/one time/deny 1476 LOCATION_FINE_UPGRADE, // Upgrade coarse to fine, upgrade to fine/ one time/ keep coarse 1477 SELECT_PHOTOS, // Select photos/allow all photos/deny 1478 SELECT_MORE_PHOTOS, // Select more photos/allow all photos/don't allow more 1479 // These next two are for T+ devices, and < T apps. They request the old "storage" group, and 1480 // we "grant" it, while actually granting the new visual and audio groups 1481 STORAGE_SUPERGROUP_Q_TO_S, // Allow/deny, special message 1482 STORAGE_SUPERGROUP_PRE_Q, // Allow/deny, special message (different from above) 1483 NO_UI_SETTINGS_REDIRECT, // Send the user directly to permission settings 1484 NO_UI_PHOTO_PICKER_REDIRECT, // Send the user directly to the photo picker 1485 NO_UI_HEALTH_REDIRECT, // Send the user directly to the Health Connect settings 1486 NO_UI_REJECT_THIS_GROUP, // Auto deny this permission group 1487 NO_UI_REJECT_ALL_GROUPS, // Auto deny all permission groups in this request 1488 NO_UI_FILTER_THIS_GROUP, // Do not act on this permission group. Remove it from results. 1489 } 1490 1491 enum class DenyButton { 1492 DENY, 1493 DENY_DONT_ASK_AGAIN, 1494 NO_UPGRADE, 1495 NO_UPGRADE_OT, 1496 NO_UPGRADE_AND_DONT_ASK_AGAIN, 1497 NO_UPGRADE_AND_DONT_ASK_AGAIN_OT, 1498 DONT_SELECT_MORE, // used in the SELECT_MORE_PHOTOS dialog 1499 NONE, 1500 } 1501