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