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