1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.permissioncontroller.permission.ui.model 18 19 import android.Manifest 20 import android.Manifest.permission.ACCESS_COARSE_LOCATION 21 import android.Manifest.permission.ACCESS_FINE_LOCATION 22 import android.Manifest.permission_group.LOCATION 23 import android.app.Activity 24 import android.app.Application 25 import android.app.admin.DevicePolicyManager 26 import android.content.Intent 27 import android.content.pm.PackageManager 28 import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED 29 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED 30 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET 31 import android.os.Build 32 import android.os.Bundle 33 import android.os.Process 34 import android.permission.PermissionManager 35 import android.util.Log 36 import androidx.core.util.Consumer 37 import androidx.lifecycle.ViewModel 38 import androidx.lifecycle.ViewModelProvider 39 import com.android.permissioncontroller.Constants 40 import com.android.permissioncontroller.PermissionControllerStatsLog 41 import com.android.permissioncontroller.PermissionControllerStatsLog.GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS 42 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED 43 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED 44 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED 45 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED 46 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION 47 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED 48 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED 49 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS 50 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE 51 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS 52 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED 53 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS 54 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME 55 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED 56 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData 57 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData 58 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData 59 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData 60 import com.android.permissioncontroller.permission.data.get 61 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup 62 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo 63 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo 64 import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier 65 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity 66 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON 67 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON 68 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON 69 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.COARSE_RADIO_BUTTON 70 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON 71 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON 72 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS 73 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY 74 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY 75 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON 76 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LINK_TO_SETTINGS 77 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LOCATION_ACCURACY_LAYOUT 78 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON 79 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_LOCATION_DIALOG 80 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON 81 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON 82 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON 83 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON 84 import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.PERMISSION_TO_BIT_SHIFT 85 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler 86 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED 87 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN 88 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS 89 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY 90 import com.android.permissioncontroller.permission.ui.handheld.dashboard.getDefaultPrecision 91 import com.android.permissioncontroller.permission.ui.handheld.dashboard.isLocationAccuracyEnabled 92 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity 93 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED 94 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT 95 import com.android.permissioncontroller.permission.utils.AdminRestrictedPermissionsUtils 96 import com.android.permissioncontroller.permission.utils.KotlinUtils 97 import com.android.permissioncontroller.permission.utils.SafetyNetLogger 98 import com.android.permissioncontroller.permission.utils.Utils 99 import kotlinx.coroutines.Dispatchers.Main 100 import kotlinx.coroutines.GlobalScope 101 import kotlinx.coroutines.launch 102 103 /** 104 * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by 105 * the permissions requested by the user, and generates a RequestInfo object for each group, if 106 * action is needed. It will not return any data if one of the requests is malformed. 107 * 108 * @param app: The current application 109 * @param packageName: The packageName permissions are being requested for 110 * @param requestedPermissions: The list of permissions requested 111 * @param sessionId: A long to identify this session 112 * @param storedState: Previous state, if this activity was stopped and is being recreated 113 */ 114 class GrantPermissionsViewModel( 115 private val app: Application, 116 private val packageName: String, 117 private val requestedPermissions: List<String>, 118 private val sessionId: Long, 119 private val storedState: Bundle? 120 ) : ViewModel() { 121 private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName 122 private val user = Process.myUserHandle() 123 private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user] 124 private val dpm = app.getSystemService(DevicePolicyManager::class.java)!! 125 private val permissionPolicy = dpm.getPermissionPolicy(null) 126 private val permGroupsToSkip = mutableListOf<String>() 127 private var groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>() 128 private var isFirstTimeRequestingFineAndCoarse: Boolean = false 129 130 private var autoGrantNotifier: AutoGrantPermissionsNotifier? = null 131 private fun getAutoGrantNotifier(): AutoGrantPermissionsNotifier { 132 autoGrantNotifier = AutoGrantPermissionsNotifier(app, packageInfo.toPackageInfo(app)!!) 133 return autoGrantNotifier!! 134 } 135 136 private lateinit var packageInfo: LightPackageInfo 137 138 // All permissions that could possibly be affected by the provided requested permissions, before 139 // filtering system fixed, auto grant, etc. 140 private var unfilteredAffectedPermissions = requestedPermissions 141 142 /** 143 * A class which represents a correctly requested permission group, and the buttons and messages 144 * which should be shown with it. 145 */ 146 data class RequestInfo( 147 val groupInfo: LightPermGroupInfo, 148 val buttonVisibilities: List<Boolean> = List(NEXT_BUTTON) { false }, 149 val locationVisibilities: List<Boolean> = List(NEXT_LOCATION_DIALOG) { false }, 150 val message: RequestMessage = RequestMessage.FG_MESSAGE, 151 val detailMessage: RequestMessage = RequestMessage.NO_MESSAGE, 152 val sendToSettingsImmediately: Boolean = false 153 ) { 154 val groupName = groupInfo.name 155 } 156 157 var activityResultCallback: Consumer<Intent>? = null 158 159 /** 160 * A LiveData which holds a list of the currently pending RequestInfos 161 */ 162 val requestInfosLiveData = object : 163 SmartUpdateMediatorLiveData<List<RequestInfo>>() { 164 private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName 165 private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user] 166 private val appPermGroupLiveDatas = mutableMapOf<String, LightAppPermGroupLiveData>() 167 168 init { 169 GlobalScope.launch(Main.immediate) { 170 val groups = packagePermissionsLiveData.getInitializedValue() 171 if (groups == null || groups.isEmpty()) { 172 Log.e(LOG_TAG, "Package $packageName not found") 173 value = null 174 return@launch 175 } 176 packageInfo = packageInfoLiveData.getInitializedValue() 177 178 if (packageInfo.requestedPermissions.isEmpty() || 179 packageInfo.targetSdkVersion < Build.VERSION_CODES.M) { 180 Log.e(LOG_TAG, "Package $packageName has no requested permissions, or " + 181 "is a pre-M app") 182 value = null 183 return@launch 184 } 185 186 val allAffectedPermissions = requestedPermissions.toMutableSet() 187 for (requestedPerm in requestedPermissions) { 188 allAffectedPermissions.addAll(computeAffectedPermissions(requestedPerm, groups)) 189 } 190 unfilteredAffectedPermissions = allAffectedPermissions.toList() 191 192 getAppPermGroups(groups.toMutableMap().apply { 193 remove(PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS) 194 }) 195 } 196 } 197 198 private fun getAppPermGroups(groups: Map<String, List<String>>) { 199 200 val requestedGroups = groups.filter { (_, perms) -> 201 perms.any { it in unfilteredAffectedPermissions } 202 } 203 204 if (requestedGroups.isEmpty()) { 205 Log.e(LOG_TAG, "None of " + 206 "$unfilteredAffectedPermissions in $groups") 207 value = null 208 return 209 } 210 211 val getLiveDataFun = { groupName: String -> 212 LightAppPermGroupLiveData[packageName, groupName, user] 213 } 214 setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun) 215 } 216 217 override fun onUpdate() { 218 if (appPermGroupLiveDatas.any { it.value.isStale }) { 219 return 220 } 221 var newGroups = false 222 for ((groupName, groupLiveData) in appPermGroupLiveDatas) { 223 val appPermGroup = groupLiveData.value 224 if (appPermGroup == null || groupName in permGroupsToSkip) { 225 if (appPermGroup == null) { 226 Log.e(LOG_TAG, "Group $packageName $groupName invalid") 227 } 228 groupStates[groupName to true]?.state = STATE_SKIPPED 229 groupStates[groupName to false]?.state = STATE_SKIPPED 230 continue 231 } 232 233 packageInfo = appPermGroup.packageInfo 234 235 val states = groupStates.filter { it.key.first == groupName } 236 if (states.isNotEmpty()) { 237 // some requests might have been granted, check for that 238 for ((key, state) in states) { 239 val allAffectedGranted = state.affectedPermissions.all { perm -> 240 appPermGroup.permissions[perm]?.isGrantedIncludingAppOp == true 241 } 242 if (allAffectedGranted) { 243 groupStates[key]!!.state = STATE_ALLOWED 244 } 245 } 246 } else { 247 newGroups = true 248 } 249 } 250 251 if (newGroups) { 252 groupStates = getRequiredGroupStates( 253 appPermGroupLiveDatas.mapNotNull { it.value.value }) 254 } 255 getRequestInfosFromGroupStates() 256 } 257 258 private fun getRequestInfosFromGroupStates() { 259 val requestInfos = mutableListOf<RequestInfo>() 260 for ((key, groupState) in groupStates) { 261 val groupInfo = groupState.group.permGroupInfo 262 val (groupName, isBackground) = key 263 if (groupState.state != STATE_UNKNOWN) { 264 continue 265 } 266 267 val fgState = groupStates[groupName to false] 268 val bgState = groupStates[groupName to true] 269 var needFgPermissions = false 270 var needBgPermissions = false 271 var isFgUserSet = false 272 var isBgUserSet = false 273 274 if (fgState?.group != null) { 275 val fgGroup = fgState.group 276 for (perm in fgState.affectedPermissions) { 277 if (fgGroup.permissions[perm]?.isGrantedIncludingAppOp == false) { 278 // If any of the requested permissions is not granted, 279 // needFgPermissions = true 280 needFgPermissions = true 281 // If any of the requested permission's UserSet is true and the 282 // permission is not granted, isFgUserSet = true. 283 if (fgGroup.permissions[perm]?.isUserSet == true) { 284 isFgUserSet = true 285 } 286 } 287 } 288 } 289 290 if (bgState?.group?.background?.isGranted == false) { 291 needBgPermissions = true 292 isBgUserSet = bgState.group.background.isUserSet 293 } 294 295 val buttonVisibilities = MutableList(NEXT_BUTTON) { false } 296 buttonVisibilities[ALLOW_BUTTON] = true 297 buttonVisibilities[DENY_BUTTON] = true 298 buttonVisibilities[ALLOW_ONE_TIME_BUTTON] = 299 Utils.supportsOneTimeGrant(groupName) 300 var message = RequestMessage.FG_MESSAGE 301 // Whether or not to use the foreground, background, or no detail message. 302 // null == 303 var detailMessage = RequestMessage.NO_MESSAGE 304 305 if (groupState.group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.R) { 306 if (isBackground || groupState.group.hasPermWithBackgroundMode) { 307 if (needFgPermissions) { 308 if (needBgPermissions) { 309 if (groupState.group.permGroupName 310 .equals(Manifest.permission_group.CAMERA) || 311 groupState.group.permGroupName 312 .equals(Manifest.permission_group.MICROPHONE)) { 313 if (groupState.group.packageInfo.targetSdkVersion >= 314 Build.VERSION_CODES.S) { 315 Log.e(LOG_TAG, 316 "For S apps, background permissions must be " + 317 "requested after foreground permissions are" + 318 " already granted") 319 value = null 320 return 321 } else { 322 // Case: sdk < S, BG&FG mic/camera permission requested 323 buttonVisibilities[ALLOW_BUTTON] = false 324 buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true 325 buttonVisibilities[DENY_BUTTON] = !isFgUserSet 326 buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = 327 isFgUserSet 328 if (needBgPermissions) { 329 // Case: sdk < R, BG/FG permission requesting both 330 message = RequestMessage.BG_MESSAGE 331 detailMessage = RequestMessage.BG_MESSAGE 332 } 333 } 334 } else { 335 // Shouldn't be reached as background must be requested as a 336 // singleton 337 Log.e(LOG_TAG, "For R+ apps, background permissions must be " + 338 "requested after foreground permissions are already" + 339 " granted") 340 value = null 341 return 342 } 343 } else { 344 buttonVisibilities[ALLOW_BUTTON] = false 345 buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true 346 buttonVisibilities[DENY_BUTTON] = !isFgUserSet 347 buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet 348 } 349 } else if (needBgPermissions) { 350 // Case: sdk >= R, BG/FG permission requesting BG only 351 requestInfos.add(RequestInfo( 352 groupInfo, sendToSettingsImmediately = true)) 353 continue 354 } else { 355 // Not reached as the permissions should be auto-granted 356 value = null 357 return 358 } 359 } else { 360 // Case: sdk >= R, Requesting normal permission 361 buttonVisibilities[DENY_BUTTON] = !isFgUserSet 362 buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet 363 } 364 } else { 365 if (isBackground || groupState.group.hasPermWithBackgroundMode) { 366 if (needFgPermissions) { 367 // Case: sdk < R, BG/FG permission requesting both or FG only 368 buttonVisibilities[ALLOW_BUTTON] = false 369 buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = true 370 buttonVisibilities[DENY_BUTTON] = !isFgUserSet 371 buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet 372 if (needBgPermissions) { 373 // Case: sdk < R, BG/FG permission requesting both 374 message = RequestMessage.BG_MESSAGE 375 detailMessage = RequestMessage.BG_MESSAGE 376 } 377 } else if (needBgPermissions) { 378 // Case: sdk < R, BG/FG permission requesting BG only 379 if (!groupState.group.foreground.isGranted) { 380 Log.e(LOG_TAG, "Background permissions can't be requested " + 381 "solely before foreground permissions are granted.") 382 value = null 383 return 384 } 385 message = RequestMessage.UPGRADE_MESSAGE 386 detailMessage = RequestMessage.UPGRADE_MESSAGE 387 buttonVisibilities[ALLOW_BUTTON] = false 388 buttonVisibilities[DENY_BUTTON] = false 389 buttonVisibilities[ALLOW_ONE_TIME_BUTTON] = false 390 if (groupState.group.isOneTime) { 391 buttonVisibilities[NO_UPGRADE_OT_BUTTON] = !isBgUserSet 392 buttonVisibilities[NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON] = 393 isBgUserSet 394 } else { 395 buttonVisibilities[NO_UPGRADE_BUTTON] = !isBgUserSet 396 buttonVisibilities[NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON] = 397 isBgUserSet 398 } 399 } else { 400 // Not reached as the permissions should be auto-granted 401 value = null 402 return 403 } 404 } else { 405 // If no permissions needed, do nothing 406 if (!needFgPermissions && !needBgPermissions) { 407 value = null 408 return 409 } 410 // Case: sdk < R, Requesting normal permission 411 buttonVisibilities[DENY_BUTTON] = !isFgUserSet 412 buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON] = isFgUserSet 413 } 414 } 415 buttonVisibilities[LINK_TO_SETTINGS] = 416 detailMessage != RequestMessage.NO_MESSAGE 417 418 // Show location permission dialogs based on location permissions 419 val locationVisibilities = MutableList(NEXT_LOCATION_DIALOG) { false } 420 if (groupState.group.permGroupName == LOCATION && isLocationAccuracyEnabled() && 421 packageInfo.targetSdkVersion >= Build.VERSION_CODES.S) { 422 if (needFgPermissions) { 423 locationVisibilities[LOCATION_ACCURACY_LAYOUT] = true 424 if (fgState != null && 425 fgState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) { 426 val coarseLocationPerm = 427 groupState.group.allPermissions[ACCESS_COARSE_LOCATION] 428 if (coarseLocationPerm?.isGrantedIncludingAppOp == true) { 429 // Upgrade flow 430 locationVisibilities[DIALOG_WITH_FINE_LOCATION_ONLY] = true 431 message = RequestMessage.FG_FINE_LOCATION_MESSAGE 432 // If COARSE was granted one time, hide 'While in use' button 433 if (coarseLocationPerm.isOneTime) { 434 buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = false 435 } 436 } else { 437 if (!fgState.affectedPermissions.contains(ACCESS_COARSE_LOCATION)) { 438 Log.e(LOG_TAG, "ACCESS_FINE_LOCATION must be requested " + 439 "with ACCESS_COARSE_LOCATION.") 440 value = null 441 return 442 } 443 if (coarseLocationPerm?.isOneTime == false && 444 !coarseLocationPerm.isUserSet && 445 !coarseLocationPerm.isUserFixed) { 446 isFirstTimeRequestingFineAndCoarse = true 447 } 448 // Normal flow with both Coarse and Fine locations 449 locationVisibilities[DIALOG_WITH_BOTH_LOCATIONS] = true 450 // Steps to decide location accuracy default state 451 // 1. If none of the FINE and COARSE isSelectedLocationAccuracy 452 // flags is set, then use default precision from device config. 453 // 2. Otherwise set to whichever isSelectedLocationAccuracy is true. 454 val fineLocationPerm = 455 groupState.group.allPermissions[ACCESS_FINE_LOCATION] 456 if (coarseLocationPerm?.isSelectedLocationAccuracy == false && 457 fineLocationPerm?.isSelectedLocationAccuracy == false) { 458 if (getDefaultPrecision()) { 459 locationVisibilities[FINE_RADIO_BUTTON] = true 460 } else { 461 locationVisibilities[COARSE_RADIO_BUTTON] = true 462 } 463 } else if (coarseLocationPerm?.isSelectedLocationAccuracy == true) { 464 locationVisibilities[COARSE_RADIO_BUTTON] = true 465 } else { 466 locationVisibilities[FINE_RADIO_BUTTON] = true 467 } 468 } 469 } else if (fgState != null && fgState.affectedPermissions 470 .contains(ACCESS_COARSE_LOCATION)) { 471 // Request Coarse only 472 locationVisibilities[DIALOG_WITH_COARSE_LOCATION_ONLY] = true 473 message = RequestMessage.FG_COARSE_LOCATION_MESSAGE 474 } 475 } 476 } 477 478 requestInfos.add(RequestInfo( 479 groupInfo, 480 buttonVisibilities, 481 locationVisibilities, 482 message, 483 detailMessage)) 484 } 485 requestInfos.sortWith(Comparator { rhs, lhs -> 486 val rhsHasOneTime = rhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON] 487 val lhsHasOneTime = lhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON] 488 if (rhsHasOneTime && !lhsHasOneTime) { 489 -1 490 } else if (!rhsHasOneTime && lhsHasOneTime) { 491 1 492 } else { 493 rhs.groupName.compareTo(lhs.groupName) 494 } 495 }) 496 497 value = if (requestInfos.any { it.sendToSettingsImmediately } && 498 requestInfos.size > 1) { 499 Log.e(LOG_TAG, "For R+ apps, background permissions must be requested " + 500 "individually") 501 null 502 } else { 503 requestInfos 504 } 505 } 506 } 507 508 /** 509 * Converts a list of LightAppPermGroups into a list of GroupStates 510 */ 511 private fun getRequiredGroupStates( 512 groups: List<LightAppPermGroup> 513 ): MutableMap<Pair<String, Boolean>, GroupState> { 514 val groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>() 515 val filteredPermissions = unfilteredAffectedPermissions.filter { perm -> 516 val group = getGroupWithPerm(perm, groups) 517 group != null && isPermissionGrantableAndNotFixed(perm, group) 518 } 519 for (perm in filteredPermissions) { 520 val group = getGroupWithPerm(perm, groups)!! 521 522 val isBackground = perm in group.backgroundPermNames 523 val groupStateInfo = groupStates.getOrPut(group.permGroupName to isBackground) { 524 GroupState(group, isBackground) 525 } 526 527 var currGroupState = groupStateInfo.state 528 if (storedState != null && currGroupState != STATE_UNKNOWN) { 529 currGroupState = storedState.getInt(getInstanceStateKey(group.permGroupName, 530 isBackground), STATE_UNKNOWN) 531 } 532 533 val otherGroupPermissions = filteredPermissions.filter { it in group.permissions } 534 val groupStateOfPerm = getGroupState(perm, group, otherGroupPermissions) 535 if (groupStateOfPerm != STATE_UNKNOWN) { 536 currGroupState = groupStateOfPerm 537 } 538 539 if (group.permGroupName in permGroupsToSkip) { 540 currGroupState = STATE_SKIPPED 541 } 542 543 if (currGroupState != STATE_UNKNOWN) { 544 groupStateInfo.state = currGroupState 545 } 546 // If we saved state, load it 547 groupStateInfo.affectedPermissions.add(perm) 548 } 549 return groupStates 550 } 551 552 /** 553 * Get the actually requested permissions when a permission is requested. 554 * 555 * >In some cases requesting to grant a single permission requires the system to grant 556 * additional permissions. E.g. before N-MR1 a single permission of a group caused the whole 557 * group to be granted. Another case are permissions that are split into two. For apps that 558 * target an SDK before the split, this method automatically adds the split off permission. 559 * 560 * @param perm The requested permission 561 * 562 * @return The actually requested permissions 563 */ 564 private fun computeAffectedPermissions( 565 perm: String, 566 appPermissions: Map<String, List<String>> 567 ): List<String> { 568 val requestingAppTargetSDK = packageInfo.targetSdkVersion 569 570 // If a permission is split, all permissions the original permission is split into are 571 // affected 572 val extendedBySplitPerms = mutableListOf(perm) 573 574 val splitPerms = app.getSystemService(PermissionManager::class.java)!!.splitPermissions 575 for (splitPerm in splitPerms) { 576 577 if (requestingAppTargetSDK < splitPerm.targetSdk && perm == splitPerm.splitPermission) { 578 extendedBySplitPerms.addAll(splitPerm.newPermissions) 579 } 580 } 581 582 // For <= N_MR1 apps all permissions of the groups of the requested permissions are affected 583 if (requestingAppTargetSDK <= Build.VERSION_CODES.N_MR1) { 584 val extendedBySplitPermsAndGroup = mutableListOf<String>() 585 586 for (splitPerm in extendedBySplitPerms) { 587 val groups = appPermissions.filter { splitPerm in it.value } 588 if (groups.isEmpty()) { 589 continue 590 } 591 592 val permissionsInGroup = groups.values.first() 593 for (permissionInGroup in permissionsInGroup) { 594 extendedBySplitPermsAndGroup.add(permissionInGroup) 595 } 596 } 597 598 return extendedBySplitPermsAndGroup 599 } else { 600 return extendedBySplitPerms 601 } 602 } 603 604 private fun isPermissionGrantableAndNotFixed(perm: String, group: LightAppPermGroup): Boolean { 605 606 // If the permission is restricted it does not show in the UI and 607 // is not added to the group at all, so check that first. 608 if (perm in group.packageInfo.requestedPermissions && perm !in group.permissions) { 609 reportRequestResult(perm, 610 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION) 611 return false 612 } 613 614 val subGroup = if (perm in group.backgroundPermNames) { 615 group.background 616 } else { 617 group.foreground 618 } 619 620 val lightPermission = group.permissions[perm] ?: return false 621 622 if (!subGroup.isGrantable) { 623 reportRequestResult(perm, PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED) 624 // Skip showing groups that we know cannot be granted. 625 return false 626 } else if (subGroup.isUserFixed) { 627 reportRequestResult(perm, 628 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED) 629 return false 630 } else if (subGroup.isPolicyFixed && !subGroup.isGranted || lightPermission.isPolicyFixed) { 631 reportRequestResult(perm, 632 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED) 633 return false 634 } 635 636 return true 637 } 638 639 private fun getGroupState( 640 perm: String, 641 group: LightAppPermGroup, 642 groupRequestedPermissions: List<String> 643 ): Int { 644 val policyState = getStateFromPolicy(perm, group) 645 if (policyState != STATE_UNKNOWN) { 646 return policyState 647 } 648 649 val isBackground = perm in group.backgroundPermNames 650 651 val hasForegroundRequest = groupRequestedPermissions.any { 652 it !in group.backgroundPermNames 653 } 654 655 // Do not attempt to grant background access if foreground access is not either already 656 // granted or requested 657 if (isBackground && !group.foreground.isGranted && !hasForegroundRequest) { 658 Log.w(LOG_TAG, "Cannot grant $perm as the matching foreground permission is not " + 659 "already granted.") 660 val affectedPermissions = groupRequestedPermissions.filter { 661 it in group.backgroundPermNames 662 } 663 reportRequestResult(affectedPermissions, 664 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED) 665 return STATE_SKIPPED 666 } 667 668 if (isBackground && group.background.isGranted || 669 !isBackground && group.foreground.isGranted) { 670 // If FINE location is not granted, do not grant it automatically when COARSE 671 // location is already granted. 672 if (group.permGroupName == LOCATION && 673 group.allPermissions[ACCESS_FINE_LOCATION]?.isGrantedIncludingAppOp 674 == false) { 675 return STATE_UNKNOWN 676 } 677 678 if (group.permissions[perm]?.isGrantedIncludingAppOp == false) { 679 if (isBackground) { 680 KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm)) 681 } else { 682 KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm)) 683 } 684 KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to false, 685 FLAG_PERMISSION_USER_FIXED to false, filterPermissions = listOf(perm)) 686 reportRequestResult(perm, 687 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED) 688 } 689 690 return if (storedState == null) { 691 STATE_SKIPPED 692 } else { 693 STATE_ALLOWED 694 } 695 } 696 return STATE_UNKNOWN 697 } 698 699 private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int { 700 val isBackground = perm in group.backgroundPermNames 701 var skipGroup = false 702 var state = STATE_UNKNOWN 703 when (permissionPolicy) { 704 DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT -> { 705 if (AdminRestrictedPermissionsUtils.mayAdminGrantPermission( 706 app, perm, user.identifier)) { 707 if (isBackground) { 708 KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm)) 709 } else { 710 KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm)) 711 } 712 KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true, 713 FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false, 714 filterPermissions = listOf(perm)) 715 state = STATE_ALLOWED 716 skipGroup = true 717 718 getAutoGrantNotifier().onPermissionAutoGranted(perm) 719 reportRequestResult(perm, 720 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED) 721 } 722 } 723 724 DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY -> { 725 if (group.permissions[perm]?.isPolicyFixed == false) { 726 KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true, 727 FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false, 728 filterPermissions = listOf(perm)) 729 } 730 state = STATE_DENIED 731 skipGroup = true 732 733 reportRequestResult(perm, 734 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED) 735 } 736 } 737 if (skipGroup && storedState == null) { 738 return STATE_SKIPPED 739 } 740 return state 741 } 742 743 /** 744 * Upon the user clicking a button, grant permissions, if applicable. 745 * 746 * @param groupName The name of the permission group which was changed 747 * @param affectedForegroundPermissions The name of the foreground permission which was changed 748 * @param result The choice the user made regarding the group. 749 */ 750 fun onPermissionGrantResult( 751 groupName: String?, 752 affectedForegroundPermissions: List<String>?, 753 result: Int 754 ) { 755 if (groupName == null) { 756 return 757 } 758 val foregroundGroupState = groupStates[groupName to false] 759 val backgroundGroupState = groupStates[groupName to true] 760 when (result) { 761 GrantPermissionsViewHandler.CANCELED -> { 762 if (foregroundGroupState != null) { 763 reportRequestResult(foregroundGroupState.affectedPermissions, 764 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED) 765 } 766 if (backgroundGroupState != null) { 767 reportRequestResult(backgroundGroupState.affectedPermissions, 768 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED) 769 } 770 return 771 } 772 GRANTED_ALWAYS -> { 773 if (foregroundGroupState != null) { 774 onPermissionGrantResultSingleState(foregroundGroupState, 775 affectedForegroundPermissions, granted = true, isOneTime = false, 776 doNotAskAgain = false) 777 } 778 if (backgroundGroupState != null) { 779 onPermissionGrantResultSingleState(backgroundGroupState, 780 affectedForegroundPermissions, granted = true, isOneTime = false, 781 doNotAskAgain = false) 782 } 783 } 784 GRANTED_FOREGROUND_ONLY -> { 785 if (foregroundGroupState != null) { 786 onPermissionGrantResultSingleState(foregroundGroupState, 787 affectedForegroundPermissions, granted = true, isOneTime = false, 788 doNotAskAgain = false) 789 } 790 if (backgroundGroupState != null) { 791 onPermissionGrantResultSingleState(backgroundGroupState, 792 affectedForegroundPermissions, granted = false, isOneTime = false, 793 doNotAskAgain = false) 794 } 795 } 796 GrantPermissionsViewHandler.GRANTED_ONE_TIME -> { 797 if (foregroundGroupState != null) { 798 onPermissionGrantResultSingleState(foregroundGroupState, 799 affectedForegroundPermissions, granted = true, isOneTime = true, 800 doNotAskAgain = false) 801 } 802 if (backgroundGroupState != null) { 803 onPermissionGrantResultSingleState(backgroundGroupState, 804 affectedForegroundPermissions, granted = false, isOneTime = true, 805 doNotAskAgain = false) 806 } 807 } 808 DENIED -> { 809 if (foregroundGroupState != null) { 810 onPermissionGrantResultSingleState(foregroundGroupState, 811 affectedForegroundPermissions, granted = false, isOneTime = false, 812 doNotAskAgain = false) 813 } 814 if (backgroundGroupState != null) { 815 onPermissionGrantResultSingleState(backgroundGroupState, 816 affectedForegroundPermissions, granted = false, isOneTime = false, 817 doNotAskAgain = false) 818 } 819 } 820 DENIED_DO_NOT_ASK_AGAIN -> { 821 if (foregroundGroupState != null) { 822 onPermissionGrantResultSingleState(foregroundGroupState, 823 affectedForegroundPermissions, granted = false, isOneTime = false, 824 doNotAskAgain = true) 825 } 826 if (backgroundGroupState != null) { 827 onPermissionGrantResultSingleState(backgroundGroupState, 828 affectedForegroundPermissions, granted = false, isOneTime = false, 829 doNotAskAgain = true) 830 } 831 } 832 } 833 } 834 835 private fun onPermissionGrantResultSingleState( 836 groupState: GroupState, 837 affectedForegroundPermissions: List<String>?, 838 granted: Boolean, 839 isOneTime: Boolean, 840 doNotAskAgain: Boolean 841 ) { 842 if (groupState.state != STATE_UNKNOWN) { 843 // We already dealt with this group, don't re-grant/re-revoke 844 return 845 } 846 val result: Int 847 if (granted) { 848 result = if (isOneTime) { 849 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME 850 } else { 851 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED 852 } 853 if (groupState.isBackground) { 854 KotlinUtils.grantBackgroundRuntimePermissions(app, groupState.group, 855 groupState.affectedPermissions) 856 } else { 857 if (affectedForegroundPermissions == null) { 858 KotlinUtils.grantForegroundRuntimePermissions(app, groupState.group, 859 groupState.affectedPermissions, isOneTime) 860 // This prevents weird flag state when app targetSDK switches from S+ to R- 861 if (groupState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) { 862 KotlinUtils.setFlagsWhenLocationAccuracyChanged( 863 app, groupState.group, true) 864 } 865 } else { 866 val newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, 867 groupState.group, affectedForegroundPermissions, isOneTime) 868 if (!isOneTime || newGroup.isOneTime) { 869 KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, newGroup, 870 affectedForegroundPermissions.contains(ACCESS_FINE_LOCATION)) 871 } 872 } 873 } 874 groupState.state = STATE_ALLOWED 875 } else { 876 if (groupState.isBackground) { 877 KotlinUtils.revokeBackgroundRuntimePermissions(app, groupState.group, 878 userFixed = doNotAskAgain, filterPermissions = groupState.affectedPermissions) 879 } else { 880 if (affectedForegroundPermissions == null) { 881 KotlinUtils.revokeForegroundRuntimePermissions(app, groupState.group, 882 userFixed = doNotAskAgain, 883 filterPermissions = groupState.affectedPermissions, oneTime = isOneTime) 884 } else { 885 KotlinUtils.revokeForegroundRuntimePermissions(app, groupState.group, 886 userFixed = doNotAskAgain, 887 filterPermissions = affectedForegroundPermissions, oneTime = isOneTime) 888 } 889 } 890 result = if (doNotAskAgain) { 891 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE 892 } else { 893 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED 894 } 895 groupState.state = STATE_DENIED 896 } 897 reportRequestResult(groupState.affectedPermissions, result) 898 // group state has changed, reload liveData 899 requestInfosLiveData.update() 900 } 901 902 private fun getGroupWithPerm( 903 perm: String, 904 groups: List<LightAppPermGroup> 905 ): LightAppPermGroup? { 906 val groupsWithPerm = groups.filter { perm in it.permissions } 907 if (groupsWithPerm.isEmpty()) { 908 return null 909 } 910 return groupsWithPerm.first() 911 } 912 913 /** 914 * An internal class which represents the state of a current AppPermissionGroup grant request. 915 */ 916 internal class GroupState( 917 internal val group: LightAppPermGroup, 918 internal val isBackground: Boolean, 919 internal val affectedPermissions: MutableList<String> = mutableListOf(), 920 internal var state: Int = STATE_UNKNOWN 921 ) { 922 override fun toString(): String { 923 val stateStr: String = when (state) { 924 STATE_UNKNOWN -> "unknown" 925 STATE_ALLOWED -> "granted" 926 STATE_DENIED -> "denied" 927 else -> "skipped" 928 } 929 return "${group.permGroupName} $isBackground $stateStr $affectedPermissions" 930 } 931 } 932 933 private fun reportRequestResult(permissions: List<String>, result: Int) { 934 for (perm in permissions) { 935 reportRequestResult(perm, result) 936 } 937 } 938 939 /** 940 * Report the result of a grant of a permission. 941 * 942 * @param permission The permission that was granted or denied 943 * @param result The permission grant result 944 */ 945 private fun reportRequestResult(permission: String, result: Int) { 946 val isImplicit = permission !in requestedPermissions 947 948 Log.v(LOG_TAG, "Permission grant result requestId=$sessionId " + 949 "callingUid=${packageInfo.uid} callingPackage=$packageName permission=$permission " + 950 "isImplicit=$isImplicit result=$result") 951 952 PermissionControllerStatsLog.write( 953 PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED, sessionId, 954 packageInfo.uid, packageName, permission, isImplicit, result) 955 } 956 957 /** 958 * Save the group states of the view model, to allow for state restoration after lifecycle 959 * events 960 * 961 * @param outState The bundle in which to store state 962 */ 963 fun saveInstanceState(outState: Bundle) { 964 for ((groupKey, groupState) in groupStates) { 965 val (groupName, isBackground) = groupKey 966 outState.putInt(getInstanceStateKey(groupName, isBackground), groupState.state) 967 } 968 } 969 970 /** 971 * Determine if the activity should return permission state to the caller 972 * 973 * @return Whether or not state should be returned. False only if the package is pre-M, true 974 * otherwise. 975 */ 976 fun shouldReturnPermissionState(): Boolean { 977 return if (packageInfoLiveData.value != null) { 978 packageInfoLiveData.value!!.targetSdkVersion >= Build.VERSION_CODES.M 979 } else { 980 // Should not be reached, as this method shouldn't be called before data is passed to 981 // the activity for the first time 982 try { 983 Utils.getUserContext(app, user).packageManager 984 .getApplicationInfo(packageName, 0).targetSdkVersion >= Build.VERSION_CODES.M 985 } catch (e: PackageManager.NameNotFoundException) { 986 true 987 } 988 } 989 } 990 991 /** 992 * Send the user directly to the AppPermissionFragment. Used for R+ apps. 993 * 994 * @param activity The current activity 995 * @param groupName The name of the permission group whose fragment should be opened 996 */ 997 fun sendDirectlyToSettings(activity: Activity, groupName: String) { 998 if (activityResultCallback == null) { 999 startAppPermissionFragment(activity, groupName) 1000 activityResultCallback = Consumer { data -> 1001 if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) { 1002 // User didn't interact, count against rate limit 1003 val group = groupStates[groupName to false]?.group 1004 ?: groupStates[groupName to true]?.group ?: return@Consumer 1005 if (group.background.isUserSet) { 1006 KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_FIXED to true, 1007 filterPermissions = group.backgroundPermNames) 1008 } else { 1009 KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to true, 1010 filterPermissions = group.backgroundPermNames) 1011 } 1012 } 1013 1014 permGroupsToSkip.add(groupName) 1015 // Update our liveData now that there is a new skipped group 1016 requestInfosLiveData.update() 1017 } 1018 } 1019 } 1020 1021 /** 1022 * Send the user to the AppPermissionFragment from a link. Used for Q- apps 1023 * 1024 * @param activity The current activity 1025 * @param groupName The name of the permission group whose fragment should be opened 1026 */ 1027 fun sendToSettingsFromLink(activity: Activity, groupName: String) { 1028 startAppPermissionFragment(activity, groupName) 1029 activityResultCallback = Consumer { data -> 1030 val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) 1031 if (returnGroupName != null) { 1032 permGroupsToSkip.add(returnGroupName) 1033 val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, -1) 1034 logSettingsInteraction(returnGroupName, result) 1035 requestInfosLiveData.update() 1036 } 1037 } 1038 } 1039 1040 private fun startAppPermissionFragment(activity: Activity, groupName: String) { 1041 val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSION) 1042 .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) 1043 .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName) 1044 .putExtra(Intent.EXTRA_USER, user) 1045 .putExtra(ManagePermissionsActivity.EXTRA_CALLER_NAME, 1046 GrantPermissionsActivity::class.java.name) 1047 .putExtra(Constants.EXTRA_SESSION_ID, sessionId) 1048 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 1049 activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE) 1050 } 1051 1052 private fun getInstanceStateKey(groupName: String, isBackground: Boolean): String { 1053 return "${this::class.java.name}_${groupName}_$isBackground" 1054 } 1055 1056 private fun logSettingsInteraction(groupName: String, result: Int) { 1057 val foregroundGroupState = groupStates[groupName to false] 1058 val backgroundGroupState = groupStates[groupName to true] 1059 val deniedPrejudiceInSettings = 1060 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS 1061 when (result) { 1062 GRANTED_ALWAYS -> { 1063 if (foregroundGroupState != null) { 1064 reportRequestResult(foregroundGroupState.affectedPermissions, 1065 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS) 1066 } 1067 if (backgroundGroupState != null) { 1068 reportRequestResult(backgroundGroupState.affectedPermissions, 1069 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS) 1070 } 1071 } 1072 GRANTED_FOREGROUND_ONLY -> { 1073 if (foregroundGroupState != null) { 1074 reportRequestResult(foregroundGroupState.affectedPermissions, 1075 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS) 1076 } 1077 if (backgroundGroupState != null) { 1078 reportRequestResult(backgroundGroupState.affectedPermissions, 1079 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS) 1080 } 1081 } 1082 DENIED -> { 1083 if (foregroundGroupState != null) { 1084 reportRequestResult(foregroundGroupState.affectedPermissions, 1085 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS) 1086 } 1087 if (backgroundGroupState != null) { 1088 reportRequestResult(backgroundGroupState.affectedPermissions, 1089 PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS) 1090 } 1091 } 1092 DENIED_DO_NOT_ASK_AGAIN -> { 1093 if (foregroundGroupState != null) { 1094 reportRequestResult(foregroundGroupState.affectedPermissions, 1095 deniedPrejudiceInSettings) 1096 } 1097 if (backgroundGroupState != null) { 1098 reportRequestResult(backgroundGroupState.affectedPermissions, 1099 deniedPrejudiceInSettings) 1100 } 1101 } 1102 } 1103 } 1104 1105 /** 1106 * Log all permission groups which were requested 1107 */ 1108 fun logRequestedPermissionGroups() { 1109 if (groupStates.isEmpty()) { 1110 return 1111 } 1112 val groups = groupStates.map { it.value.group } 1113 SafetyNetLogger.logPermissionsRequested(packageName, packageInfo.uid, groups) 1114 } 1115 1116 /** 1117 * Log information about the buttons which were shown and clicked by the user. 1118 * 1119 * @param groupName The name of the permission group which was interacted with 1120 * @param selectedPrecision Selected precision of the location permission - bit flags indicate 1121 * which locations were chosen 1122 * @param clickedButton The button that was clicked by the user 1123 * @param presentedButtons All buttons which were shown to the user 1124 */ 1125 fun logClickedButtons( 1126 groupName: String?, 1127 selectedPrecision: Int, 1128 clickedButton: Int, 1129 presentedButtons: Int 1130 ) { 1131 if (groupName == null) { 1132 return 1133 } 1134 var selectedLocations = 0 1135 // log permissions if it's 1) first time requesting both locations OR 2) upgrade flow 1136 if (isFirstTimeRequestingFineAndCoarse || 1137 selectedPrecision == 1138 1 shl PERMISSION_TO_BIT_SHIFT[ACCESS_FINE_LOCATION]!!) { 1139 selectedLocations = selectedPrecision 1140 } 1141 PermissionControllerStatsLog.write(GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS, 1142 groupName, packageInfo.uid, packageName, presentedButtons, clickedButton, sessionId, 1143 packageInfo.targetSdkVersion, selectedLocations) 1144 Log.v(LOG_TAG, "Logged buttons presented and clicked permissionGroupName=" + 1145 "$groupName uid=${packageInfo.uid} selectedLocations=$selectedLocations " + 1146 "package=$packageName presentedButtons=$presentedButtons " + 1147 "clickedButton=$clickedButton sessionId=$sessionId " + 1148 "targetSdk=${packageInfo.targetSdkVersion}") 1149 } 1150 1151 /** 1152 * Use the autoGrantNotifier to notify of auto-granted permissions. 1153 */ 1154 fun autoGrantNotify() { 1155 autoGrantNotifier?.notifyOfAutoGrantPermissions(true) 1156 } 1157 1158 companion object { 1159 private const val APP_PERMISSION_REQUEST_CODE = 1 1160 private const val STATE_UNKNOWN = 0 1161 private const val STATE_ALLOWED = 1 1162 private const val STATE_DENIED = 2 1163 private const val STATE_SKIPPED = 3 1164 private const val STATE_ALREADY_ALLOWED = 4 1165 1166 /** 1167 * An enum that represents the type of message which should be shown- foreground, 1168 * background, upgrade, or no message. 1169 */ 1170 enum class RequestMessage(request: Int) { 1171 FG_MESSAGE(0), 1172 BG_MESSAGE(1), 1173 UPGRADE_MESSAGE(2), 1174 NO_MESSAGE(3), 1175 FG_FINE_LOCATION_MESSAGE(4), 1176 FG_COARSE_LOCATION_MESSAGE(5) 1177 } 1178 } 1179 } 1180 1181 /** 1182 * Factory for an AppPermissionViewModel 1183 * 1184 * @param app The current application 1185 * @param packageName The name of the package this ViewModel represents 1186 */ 1187 class GrantPermissionsViewModelFactory( 1188 private val app: Application, 1189 private val packageName: String, 1190 private val requestedPermissions: Array<String>, 1191 private val sessionId: Long, 1192 private val savedState: Bundle? 1193 ) : ViewModelProvider.Factory { 1194 override fun <T : ViewModel> create(modelClass: Class<T>): T { 1195 @Suppress("UNCHECKED_CAST") 1196 return GrantPermissionsViewModel(app, packageName, requestedPermissions.toList(), sessionId, 1197 savedState) as T 1198 } 1199 } 1200