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