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