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