• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.permissioncontroller.permission.ui.model
18 
19 import android.Manifest
20 import android.Manifest.permission.ACCESS_COARSE_LOCATION
21 import android.Manifest.permission.ACCESS_FINE_LOCATION
22 import android.Manifest.permission_group.LOCATION
23 import android.annotation.SuppressLint
24 import android.app.AppOpsManager
25 import android.app.AppOpsManager.MODE_ALLOWED
26 import android.app.AppOpsManager.MODE_ERRORED
27 import android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE
28 import android.app.Application
29 import android.content.Intent
30 import android.os.Build
31 import android.os.Bundle
32 import android.os.UserHandle
33 import android.util.Log
34 import androidx.annotation.ChecksSdkIntAtLeast
35 import androidx.annotation.StringRes
36 import androidx.fragment.app.Fragment
37 import androidx.lifecycle.MutableLiveData
38 import androidx.lifecycle.ViewModel
39 import androidx.lifecycle.ViewModelProvider
40 import androidx.navigation.fragment.findNavController
41 import com.android.modules.utils.build.SdkLevel
42 import com.android.permissioncontroller.PermissionControllerStatsLog
43 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED
44 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_VIEWED
45 import com.android.permissioncontroller.R
46 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
47 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState
48 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
49 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
50 import com.android.permissioncontroller.permission.data.get
51 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
52 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
53 import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl
54 import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs
55 
56 import com.android.permissioncontroller.permission.ui.handheld.v31.getDefaultPrecision
57 import com.android.permissioncontroller.permission.ui.handheld.v31.isLocationAccuracyEnabled
58 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW
59 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_ALWAYS
60 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND
61 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ASK
62 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ASK_ONCE
63 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY
64 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY_FOREGROUND
65 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.LOCATION_ACCURACY
66 import com.android.permissioncontroller.permission.utils.KotlinUtils
67 import com.android.permissioncontroller.permission.utils.LocationUtils
68 import com.android.permissioncontroller.permission.utils.SafetyNetLogger
69 import com.android.permissioncontroller.permission.utils.Utils
70 import com.android.permissioncontroller.permission.utils.navigateSafe
71 import com.android.settingslib.RestrictedLockUtils
72 import java.util.Random
73 import kotlin.collections.component1
74 import kotlin.collections.component2
75 
76 /**
77  * ViewModel for the AppPermissionFragment. Determines button state and detail text strings, logs
78  * permission change information, and makes permission changes.
79  *
80  * @param app The current application
81  * @param packageName The name of the package this ViewModel represents
82  * @param permGroupName The name of the permission group this ViewModel represents
83  * @param user The user of the package
84  * @param sessionId A session ID used in logs to identify this particular session
85  */
86 class AppPermissionViewModel(
87     private val app: Application,
88     private val packageName: String,
89     private val permGroupName: String,
90     private val user: UserHandle,
91     private val sessionId: Long
92 ) : ViewModel() {
93 
94     companion object {
95         private val LOG_TAG = AppPermissionViewModel::class.java.simpleName
96 
97         private const val DEVICE_PROFILE_ROLE_PREFIX = "android.app.role"
98     }
99 
100     interface ConfirmDialogShowingFragment {
101         fun showConfirmDialog(
102             changeRequest: ChangeRequest,
103             @StringRes messageId: Int,
104             buttonPressed: Int,
105             oneTime: Boolean
106         )
107 
108         fun showAdvancedConfirmDialog(args: AdvancedConfirmDialogArgs)
109     }
110 
111     enum class ChangeRequest(val value: Int) {
112         GRANT_FOREGROUND(1),
113         REVOKE_FOREGROUND(2),
114         GRANT_BACKGROUND(4),
115         REVOKE_BACKGROUND(8),
116         GRANT_BOTH(GRANT_FOREGROUND.value or GRANT_BACKGROUND.value),
117         REVOKE_BOTH(REVOKE_FOREGROUND.value or REVOKE_BACKGROUND.value),
118         GRANT_FOREGROUND_ONLY(GRANT_FOREGROUND.value or REVOKE_BACKGROUND.value),
119         GRANT_All_FILE_ACCESS(16),
120         GRANT_FINE_LOCATION(32),
121         REVOKE_FINE_LOCATION(64),
122         GRANT_STORAGE_SUPERGROUP(128),
123         REVOKE_STORAGE_SUPERGROUP(256),
124         GRANT_STORAGE_SUPERGROUP_CONFIRMED(
125                 GRANT_STORAGE_SUPERGROUP.value or GRANT_FOREGROUND.value),
126         REVOKE_STORAGE_SUPERGROUP_CONFIRMED(REVOKE_STORAGE_SUPERGROUP.value or REVOKE_BOTH.value);
127 
128         infix fun andValue(other: ChangeRequest): Int {
129             return value and other.value
130         }
131     }
132 
133     enum class ButtonType(val type: Int) {
134         ALLOW(0),
135         ALLOW_ALWAYS(1),
136         ALLOW_FOREGROUND(2),
137         ASK_ONCE(3),
138         ASK(4),
139         DENY(5),
140         DENY_FOREGROUND(6),
141         LOCATION_ACCURACY(7);
142     }
143 
144     private val isStorageAndLessThanT =
145         permGroupName == Manifest.permission_group.STORAGE && !SdkLevel.isAtLeastT()
146     private var hasConfirmedRevoke = false
147     private var lightAppPermGroup: LightAppPermGroup? = null
148 
149     private val mediaStorageSupergroupPermGroups = mutableMapOf<String, LightAppPermGroup>()
150 
151     /* Whether the current ViewModel is Location permission with both Coarse and Fine */
152     private var shouldShowLocationAccuracy: Boolean? = null
153 
154     /**
155      * A livedata which determines which detail string, if any, should be shown
156      */
157     val detailResIdLiveData = MutableLiveData<Pair<Int, Int?>>()
158     /**
159      * A livedata which stores the device admin, if there is one
160      */
161     val showAdminSupportLiveData = MutableLiveData<RestrictedLockUtils.EnforcedAdmin>()
162 
163     /**
164      * A livedata which determines which detail string, if any, should be shown
165      */
166     val fullStorageStateLiveData = object : SmartUpdateMediatorLiveData<FullStoragePackageState>() {
167         init {
168             if (isStorageAndLessThanT) {
169                 addSource(FullStoragePermissionAppsLiveData) {
170                     update()
171                 }
172             } else {
173                 value = null
174             }
175         }
176         override fun onUpdate() {
177             for (state in FullStoragePermissionAppsLiveData.value ?: return) {
178                 if (state.packageName == packageName && state.user == user) {
179                     value = state
180                     return
181                 }
182             }
183             value = null
184             return
185         }
186     }
187 
188     data class ButtonState(
189         var isChecked: Boolean,
190         var isEnabled: Boolean,
191         var isShown: Boolean,
192         var customRequest: ChangeRequest?
193     ) {
194         constructor() : this(false, true, false, null)
195     }
196 
197     /**
198      * A livedata which computes the state of the radio buttons
199      */
200     val buttonStateLiveData = object
201         : SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<ButtonType, ButtonState>>() {
202 
203         private val appPermGroupLiveData = LightAppPermGroupLiveData[packageName, permGroupName,
204             user]
205         private val mediaStorageSupergroupLiveData =
206             mutableMapOf<String, LightAppPermGroupLiveData>()
207 
208         init {
209 
210             addSource(appPermGroupLiveData) { appPermGroup ->
211                 lightAppPermGroup = appPermGroup
212                 if (permGroupName in Utils.STORAGE_SUPERGROUP_PERMISSIONS) {
213                     onMediaPermGroupUpdate(permGroupName, appPermGroup)
214                 }
215                 if (appPermGroupLiveData.isInitialized && appPermGroup == null) {
216                     value = null
217                 } else if (appPermGroup != null) {
218                     if (isStorageAndLessThanT && !fullStorageStateLiveData.isInitialized) {
219                         return@addSource
220                     }
221                     if (value == null) {
222                         logAppPermissionFragmentViewed()
223                     }
224                     update()
225                 }
226             }
227 
228             if (isStorageAndLessThanT) {
229                 addSource(fullStorageStateLiveData) {
230                     update()
231                 }
232             }
233 
234             if (permGroupName in Utils.STORAGE_SUPERGROUP_PERMISSIONS) {
235                 for (permGroupName in Utils.STORAGE_SUPERGROUP_PERMISSIONS) {
236                     val liveData = LightAppPermGroupLiveData[packageName, permGroupName, user]
237                     mediaStorageSupergroupLiveData[permGroupName] = liveData
238                 }
239                 for (permGroupName in mediaStorageSupergroupLiveData.keys) {
240                     val liveData = mediaStorageSupergroupLiveData[permGroupName]!!
241                     addSource(liveData) { permGroup ->
242                         onMediaPermGroupUpdate(permGroupName, permGroup)
243                     }
244                 }
245             }
246         }
247 
248         private fun onMediaPermGroupUpdate(permGroupName: String, permGroup: LightAppPermGroup?) {
249             if (permGroup == null) {
250                 mediaStorageSupergroupPermGroups.remove(permGroupName)
251                 value = null
252             } else {
253                 mediaStorageSupergroupPermGroups[permGroupName] = permGroup!!
254                 update()
255             }
256         }
257 
258         override fun onUpdate() {
259             val group = appPermGroupLiveData.value ?: return
260             for (mediaGroupLiveData in mediaStorageSupergroupLiveData.values) {
261                 if (!mediaGroupLiveData.isInitialized) {
262                     return
263                 }
264             }
265 
266             val admin = RestrictedLockUtils.getProfileOrDeviceOwner(app, user)
267 
268             val allowedState = ButtonState()
269             val allowedAlwaysState = ButtonState()
270             val allowedForegroundState = ButtonState()
271             val askOneTimeState = ButtonState()
272             val askState = ButtonState()
273             val deniedState = ButtonState()
274             val deniedForegroundState = ButtonState()
275 
276             askOneTimeState.isShown = group.foreground.isGranted && group.isOneTime
277             askState.isShown = Utils.supportsOneTimeGrant(permGroupName) &&
278                     !(group.foreground.isGranted && group.isOneTime)
279             deniedState.isShown = true
280 
281             if (group.hasPermWithBackgroundMode) {
282                 // Background / Foreground / Deny case
283                 allowedForegroundState.isShown = true
284                 if (group.hasBackgroundGroup) {
285                     allowedAlwaysState.isShown = true
286                 }
287 
288                 allowedAlwaysState.isChecked = group.background.isGranted &&
289                     group.foreground.isGranted && !group.background.isOneTime
290                 allowedForegroundState.isChecked = group.foreground.isGranted &&
291                         (!group.background.isGranted || group.background.isOneTime) &&
292                         !group.foreground.isOneTime
293                 askState.isChecked = !group.foreground.isGranted && group.isOneTime
294                 askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime
295                 askOneTimeState.isShown = askOneTimeState.isChecked
296                 deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime
297                 if (applyFixToForegroundBackground(group, group.foreground.isSystemFixed,
298                         group.background.isSystemFixed, allowedAlwaysState,
299                         allowedForegroundState, askState, deniedState,
300                         deniedForegroundState) ||
301                     applyFixToForegroundBackground(group, group.foreground.isPolicyFixed,
302                         group.background.isPolicyFixed, allowedAlwaysState,
303                         allowedForegroundState, askState, deniedState,
304                         deniedForegroundState)) {
305                     showAdminSupportLiveData.value = admin
306                     val detailId = getDetailResIdForFixedByPolicyPermissionGroup(group,
307                         admin != null)
308                     if (detailId != 0) {
309                         detailResIdLiveData.value = detailId to null
310                     }
311                 } else if (Utils.areGroupPermissionsIndividuallyControlled(app, permGroupName)) {
312                     val detailId = getIndividualPermissionDetailResId(group)
313                     detailResIdLiveData.value = detailId.first to detailId.second
314                 }
315             } else {
316                 // Allow / Deny case
317                 allowedState.isShown = true
318 
319                 allowedState.isChecked = group.foreground.isGranted && !group.foreground.isOneTime
320                 askState.isChecked = !group.foreground.isGranted && group.isOneTime
321                 askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime
322                 askOneTimeState.isShown = askOneTimeState.isChecked
323                 deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime
324 
325                 if (group.foreground.isPolicyFixed || group.foreground.isSystemFixed) {
326                     allowedState.isEnabled = false
327                     askState.isEnabled = false
328                     deniedState.isEnabled = false
329                     showAdminSupportLiveData.value = admin
330                     val detailId = getDetailResIdForFixedByPolicyPermissionGroup(group,
331                         admin != null)
332                     if (detailId != 0) {
333                         detailResIdLiveData.value = detailId to null
334                     }
335                 }
336                 if (isForegroundGroupSpecialCase(permGroupName)) {
337                     allowedForegroundState.isShown = true
338                     allowedState.isShown = false
339                     allowedForegroundState.isChecked = allowedState.isChecked
340                     allowedForegroundState.isEnabled = allowedState.isEnabled
341                 }
342             }
343             if (group.packageInfo.targetSdkVersion < Build.VERSION_CODES.M) {
344                 // Pre-M app's can't ask for runtime permissions
345                 askState.isShown = false
346                 deniedState.isChecked = askState.isChecked || deniedState.isChecked
347                 deniedForegroundState.isChecked = askState.isChecked ||
348                     deniedForegroundState.isChecked
349             }
350 
351             val storageState = fullStorageStateLiveData.value
352             if (isStorageAndLessThanT && storageState?.isLegacy != true) {
353                 val allowedAllFilesState = allowedAlwaysState
354                 val allowedMediaOnlyState = allowedForegroundState
355                 if (storageState != null) {
356                         // Set up the tri state permission for storage
357                         allowedAllFilesState.isEnabled = allowedState.isEnabled
358                         allowedAllFilesState.isShown = true
359                         if (storageState.isGranted) {
360                             allowedAllFilesState.isChecked = true
361                             deniedState.isChecked = false
362                         }
363                 } else {
364                     allowedAllFilesState.isEnabled = false
365                     allowedAllFilesState.isShown = false
366                 }
367                 allowedMediaOnlyState.isShown = true
368                 allowedMediaOnlyState.isEnabled = allowedState.isEnabled
369                 allowedMediaOnlyState.isChecked = allowedState.isChecked &&
370                     storageState?.isGranted != true
371                 allowedState.isChecked = false
372                 allowedState.isShown = false
373             }
374 
375             if (shouldShowLocationAccuracy == null) {
376                 shouldShowLocationAccuracy = group.permGroupName == LOCATION &&
377                         group.permissions.containsKey(ACCESS_FINE_LOCATION) &&
378                         isLocationAccuracyEnabled()
379             }
380             val locationAccuracyState = ButtonState(isFineLocationChecked(group),
381                     true, false, null)
382             if (shouldShowLocationAccuracy == true && !deniedState.isChecked) {
383                 locationAccuracyState.isShown = true
384             }
385             if (group.foreground.isSystemFixed || group.foreground.isPolicyFixed) {
386                 locationAccuracyState.isEnabled = false
387             }
388 
389             value = mapOf(
390                 ALLOW to allowedState, ALLOW_ALWAYS to allowedAlwaysState,
391                 ALLOW_FOREGROUND to allowedForegroundState, ASK_ONCE to askOneTimeState,
392                 ASK to askState, DENY to deniedState, DENY_FOREGROUND to deniedForegroundState,
393                 LOCATION_ACCURACY to locationAccuracyState)
394         }
395     }
396 
397     private fun isFineLocationChecked(group: LightAppPermGroup): Boolean {
398         if (shouldShowLocationAccuracy == true) {
399             val coarseLocation = group.permissions[ACCESS_COARSE_LOCATION]!!
400             val fineLocation = group.permissions[ACCESS_FINE_LOCATION]!!
401             // Steps to decide location accuracy toggle state
402             // 1. If none of the FINE and COARSE isSelectedLocationAccuracy flags is set,
403             //    then use default precision from device config.
404             // 2. Otherwise return if FINE isSelectedLocationAccuracy is set to true.
405             return if ((!fineLocation.isSelectedLocationAccuracy &&
406                             !coarseLocation.isSelectedLocationAccuracy)) {
407                 getDefaultPrecision()
408             } else {
409                 fineLocation.isSelectedLocationAccuracy
410             }
411         }
412         return false
413     }
414 
415     // TODO evanseverson: Actually change mic/camera to be a foreground only permission
416     private fun isForegroundGroupSpecialCase(permissionGroupName: String): Boolean {
417         return permissionGroupName.equals(Manifest.permission_group.CAMERA) ||
418                 permissionGroupName.equals(Manifest.permission_group.MICROPHONE)
419     }
420 
421     /**
422      * Modifies the radio buttons to reflect the current policy fixing state
423      *
424      * @return if anything was changed
425      */
426     private fun applyFixToForegroundBackground(
427         group: LightAppPermGroup,
428         isForegroundFixed: Boolean,
429         isBackgroundFixed: Boolean,
430         allowedAlwaysState: ButtonState,
431         allowedForegroundState: ButtonState,
432         askState: ButtonState,
433         deniedState: ButtonState,
434         deniedForegroundState: ButtonState
435     ): Boolean {
436         if (isBackgroundFixed && isForegroundFixed) {
437             // Background and foreground are both policy fixed. Disable everything
438             allowedAlwaysState.isEnabled = false
439             allowedForegroundState.isEnabled = false
440             askState.isEnabled = false
441             deniedState.isEnabled = false
442 
443             if (askState.isChecked) {
444                 askState.isChecked = false
445                 deniedState.isChecked = true
446             }
447         } else if (isBackgroundFixed && !isForegroundFixed) {
448             if (group.background.isGranted) {
449                 // Background policy fixed as granted, foreground flexible. Granting
450                 // foreground implies background comes with it in this case.
451                 // Only allow user to grant background or deny (which only toggles fg)
452                 allowedForegroundState.isEnabled = false
453                 askState.isEnabled = false
454                 deniedState.isShown = false
455                 deniedForegroundState.isShown = true
456                 deniedForegroundState.isChecked = deniedState.isChecked
457 
458                 if (askState.isChecked) {
459                     askState.isChecked = false
460                     deniedState.isChecked = true
461                 }
462             } else {
463                 // Background policy fixed as not granted, foreground flexible
464                 allowedAlwaysState.isEnabled = false
465             }
466         } else if (!isBackgroundFixed && isForegroundFixed) {
467             if (group.foreground.isGranted) {
468                 // Foreground is fixed as granted, background flexible.
469                 // Allow switching between foreground and background. No denying
470                 allowedForegroundState.isEnabled = allowedAlwaysState.isShown
471                 askState.isEnabled = false
472                 deniedState.isEnabled = false
473             } else {
474                 // Foreground is fixed denied. Background irrelevant
475                 allowedAlwaysState.isEnabled = false
476                 allowedForegroundState.isEnabled = false
477                 askState.isEnabled = false
478                 deniedState.isEnabled = false
479 
480                 if (askState.isChecked) {
481                     askState.isChecked = false
482                     deniedState.isChecked = true
483                 }
484             }
485         } else {
486             return false
487         }
488         return true
489     }
490 
491     /**
492      * Navigate to either the App Permission Groups screen, or the Permission Apps Screen.
493      * @param fragment The current fragment
494      * @param action The action to be taken
495      * @param args The arguments to pass to the fragment
496      */
497     fun showBottomLinkPage(fragment: Fragment, action: String, args: Bundle) {
498         var actionId = R.id.app_to_perm_groups
499         if (action == Intent.ACTION_MANAGE_PERMISSION_APPS) {
500             actionId = R.id.app_to_perm_apps
501         }
502 
503         fragment.findNavController().navigateSafe(actionId, args)
504     }
505 
506     /**
507      * Request to grant/revoke permissions group.
508      *
509      * Does <u>not</u> handle:
510      *
511      *  * Individually granted permissions
512      *  * Permission groups with background permissions
513      *
514      * <u>Does</u> handle:
515      *
516      *  * Default grant permissions
517      *
518      * @param setOneTime Whether or not to set this permission as one time
519      * @param fragment The fragment calling this method
520      * @param defaultDeny The system which will show the default deny dialog. Usually the same as
521      * the fragment.
522      * @param changeRequest Which permission group (foreground/background/both) should be changed
523      * @param buttonClicked button which was pressed to initiate the change, one of
524      *                      AppPermissionFragmentActionReported.button_pressed constants
525      *
526      * @return The dialogue to show, if applicable, or if the request was processed.
527      */
528     fun requestChange(
529         setOneTime: Boolean,
530         fragment: Fragment,
531         defaultDeny: ConfirmDialogShowingFragment,
532         changeRequest: ChangeRequest,
533         buttonClicked: Int
534     ) {
535         val context = fragment.context ?: return
536         val group = lightAppPermGroup ?: return
537         val wasForegroundGranted = group.foreground.isGranted
538         val wasBackgroundGranted = group.background.isGranted
539 
540         if (LocationUtils.isLocationGroupAndProvider(context, permGroupName, packageName)) {
541             val packageLabel = KotlinUtils.getPackageLabel(app, packageName, user)
542             LocationUtils.showLocationDialog(context, packageLabel)
543         }
544 
545         if (changeRequest == ChangeRequest.GRANT_FINE_LOCATION) {
546             if (!group.isOneTime) {
547                 KotlinUtils.grantForegroundRuntimePermissions(app, group)
548             }
549             KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, group, true)
550             return
551         }
552 
553         if (changeRequest == ChangeRequest.REVOKE_FINE_LOCATION) {
554             if (!group.isOneTime) {
555                 KotlinUtils.revokeForegroundRuntimePermissions(app, group,
556                     filterPermissions = listOf(ACCESS_FINE_LOCATION))
557             }
558             KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, group, false)
559             return
560         }
561 
562         val shouldGrantForeground = changeRequest andValue ChangeRequest.GRANT_FOREGROUND != 0
563         val shouldGrantBackground = changeRequest andValue ChangeRequest.GRANT_BACKGROUND != 0
564         val shouldRevokeForeground = changeRequest andValue ChangeRequest.REVOKE_FOREGROUND != 0
565         val shouldRevokeBackground = changeRequest andValue ChangeRequest.REVOKE_BACKGROUND != 0
566         var showDefaultDenyDialog = false
567         var showGrantedByDefaultWarning = false
568         var showCDMWarning = false
569 
570         if (shouldRevokeForeground && wasForegroundGranted) {
571             showDefaultDenyDialog = (group.foreground.isGrantedByDefault ||
572                     !group.supportsRuntimePerms ||
573                     group.hasInstallToRuntimeSplit)
574             showGrantedByDefaultWarning = showGrantedByDefaultWarning ||
575                     group.foreground.isGrantedByDefault
576             showCDMWarning = showCDMWarning || group.foreground.isGrantedByRole
577         }
578 
579         if (shouldRevokeBackground && wasBackgroundGranted) {
580             showDefaultDenyDialog = showDefaultDenyDialog ||
581                     group.background.isGrantedByDefault ||
582                     !group.supportsRuntimePerms ||
583                     group.hasInstallToRuntimeSplit
584             showGrantedByDefaultWarning = showGrantedByDefaultWarning ||
585                     group.background.isGrantedByDefault
586             showCDMWarning = showCDMWarning || group.background.isGrantedByRole
587         }
588 
589         if (showCDMWarning) {
590             // Refine showCDMWarning to only trigger for apps holding a device profile role
591             val heldRoles = context.getSystemService(android.app.role.RoleManager::class.java)
592                     .getHeldRolesFromController(packageName)
593             val heldProfiles = heldRoles.filter { it.startsWith(DEVICE_PROFILE_ROLE_PREFIX) }
594             showCDMWarning = showCDMWarning && heldProfiles.isNotEmpty()
595         }
596 
597         if (expandsToStorageSupergroup(group)) {
598             if (group.permGroupName == Manifest.permission_group.STORAGE) {
599                 showDefaultDenyDialog = false
600             } else if (changeRequest == ChangeRequest.GRANT_FOREGROUND) {
601                 showMediaConfirmDialog(setOneTime, defaultDeny,
602                     ChangeRequest.GRANT_STORAGE_SUPERGROUP, buttonClicked, group.permGroupName,
603                     group.packageInfo.targetSdkVersion)
604                 return
605             } else if (changeRequest == ChangeRequest.REVOKE_BOTH) {
606                 showMediaConfirmDialog(setOneTime, defaultDeny,
607                     ChangeRequest.REVOKE_STORAGE_SUPERGROUP, buttonClicked, group.permGroupName,
608                     group.packageInfo.targetSdkVersion)
609                 return
610             } else {
611                 showDefaultDenyDialog = false
612             }
613         }
614 
615         if (showDefaultDenyDialog && !hasConfirmedRevoke && showGrantedByDefaultWarning) {
616             defaultDeny.showConfirmDialog(changeRequest, R.string.system_warning, buttonClicked,
617                 setOneTime)
618             return
619         }
620 
621         if (showDefaultDenyDialog && !hasConfirmedRevoke) {
622             defaultDeny.showConfirmDialog(changeRequest, R.string.old_sdk_deny_warning,
623                     buttonClicked, setOneTime)
624             return
625         }
626 
627         if (showCDMWarning) {
628             defaultDeny.showConfirmDialog(changeRequest,
629                     R.string.cdm_profile_revoke_warning, buttonClicked, setOneTime)
630             return
631         }
632 
633         val groupsToUpdate = expandToSupergroup(group)
634         for (group in groupsToUpdate) {
635             var newGroup = group
636             val oldGroup = group
637 
638             if (shouldRevokeBackground && group.hasBackgroundGroup &&
639                     (wasBackgroundGranted || group.background.isUserFixed ||
640                             group.isOneTime != setOneTime)) {
641                 newGroup = KotlinUtils
642                         .revokeBackgroundRuntimePermissions(app, newGroup, oneTime = setOneTime)
643 
644                 // only log if we have actually denied permissions, not if we switch from
645                 // "ask every time" to denied
646                 if (wasBackgroundGranted) {
647                     SafetyNetLogger.logPermissionToggled(newGroup, true)
648                 }
649             }
650 
651             if (shouldRevokeForeground && (wasForegroundGranted || group.isOneTime != setOneTime)) {
652                 newGroup = KotlinUtils
653                         .revokeForegroundRuntimePermissions(app, newGroup, false, setOneTime)
654 
655                 // only log if we have actually denied permissions, not if we switch from
656                 // "ask every time" to denied
657                 if (wasForegroundGranted) {
658                     SafetyNetLogger.logPermissionToggled(newGroup)
659                 }
660             }
661 
662             if (shouldGrantForeground) {
663                 if (shouldShowLocationAccuracy == true && !isFineLocationChecked(newGroup)) {
664                     newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup,
665                             filterPermissions = listOf(ACCESS_COARSE_LOCATION))
666                 } else {
667                     newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup)
668                 }
669 
670                 if (!wasForegroundGranted) {
671                     SafetyNetLogger.logPermissionToggled(newGroup)
672                 }
673             }
674 
675             if (shouldGrantBackground && group.hasBackgroundGroup) {
676                 newGroup = KotlinUtils.grantBackgroundRuntimePermissions(app, newGroup)
677 
678                 if (!wasBackgroundGranted) {
679                     SafetyNetLogger.logPermissionToggled(newGroup, true)
680                 }
681             }
682 
683             logPermissionChanges(oldGroup, newGroup, buttonClicked)
684 
685             fullStorageStateLiveData.value?.let {
686                 FullStoragePermissionAppsLiveData.recalculate()
687             }
688         }
689     }
690 
691     private fun expandsToStorageSupergroup(group: LightAppPermGroup): Boolean {
692         return group.packageInfo.targetSdkVersion <= Build.VERSION_CODES.S_V2 &&
693             group.permGroupName in Utils.STORAGE_SUPERGROUP_PERMISSIONS
694     }
695 
696     private fun expandToSupergroup(group: LightAppPermGroup): List<LightAppPermGroup> {
697         val mediaSupergroup = Utils.STORAGE_SUPERGROUP_PERMISSIONS
698                 .mapNotNull { mediaStorageSupergroupPermGroups[it] }
699         return if (expandsToStorageSupergroup(group)) {
700             mediaSupergroup
701         } else {
702             listOf(group)
703         }
704     }
705 
706     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
707     private fun showMediaConfirmDialog(
708         setOneTime: Boolean,
709         confirmDialog: ConfirmDialogShowingFragment,
710         changeRequest: ChangeRequest,
711         buttonClicked: Int,
712         groupName: String,
713         targetSdk: Int
714     ) {
715 
716         val aural = groupName == Manifest.permission_group.READ_MEDIA_AURAL
717         val visual = groupName == Manifest.permission_group.READ_MEDIA_VISUAL
718         val allow = changeRequest === ChangeRequest.GRANT_STORAGE_SUPERGROUP
719         val deny = changeRequest === ChangeRequest.REVOKE_STORAGE_SUPERGROUP
720 
721         val (iconId, titleId, messageId) = when {
722             targetSdk < Build.VERSION_CODES.Q && aural && allow ->
723                 Triple(
724                     R.drawable.perm_group_storage,
725                     R.string.media_confirm_dialog_title_a_to_p_aural_allow,
726                     R.string.media_confirm_dialog_message_a_to_p_aural_allow)
727             targetSdk < Build.VERSION_CODES.Q && aural && deny ->
728                 Triple(
729                     R.drawable.perm_group_storage,
730                     R.string.media_confirm_dialog_title_a_to_p_aural_deny,
731                     R.string.media_confirm_dialog_message_a_to_p_aural_deny)
732             targetSdk < Build.VERSION_CODES.Q && visual && allow ->
733                 Triple(
734                     R.drawable.perm_group_storage,
735                     R.string.media_confirm_dialog_title_a_to_p_visual_allow,
736                     R.string.media_confirm_dialog_message_a_to_p_visual_allow)
737             targetSdk < Build.VERSION_CODES.Q && visual && deny ->
738                 Triple(
739                     R.drawable.perm_group_storage,
740                     R.string.media_confirm_dialog_title_a_to_p_visual_deny,
741                     R.string.media_confirm_dialog_message_a_to_p_visual_deny)
742             targetSdk <= Build.VERSION_CODES.S_V2 && aural && allow ->
743                 Triple(
744                     R.drawable.perm_group_visual,
745                     R.string.media_confirm_dialog_title_q_to_s_aural_allow,
746                     R.string.media_confirm_dialog_message_q_to_s_aural_allow)
747             targetSdk <= Build.VERSION_CODES.S_V2 && aural && deny ->
748                 Triple(
749                     R.drawable.perm_group_visual,
750                     R.string.media_confirm_dialog_title_q_to_s_aural_deny,
751                     R.string.media_confirm_dialog_message_q_to_s_aural_deny)
752             targetSdk <= Build.VERSION_CODES.S_V2 && visual && allow ->
753                 Triple(
754                     R.drawable.perm_group_aural,
755                     R.string.media_confirm_dialog_title_q_to_s_visual_allow,
756                     R.string.media_confirm_dialog_message_q_to_s_visual_allow)
757             targetSdk <= Build.VERSION_CODES.S_V2 && visual && deny ->
758                 Triple(
759                     R.drawable.perm_group_aural,
760                     R.string.media_confirm_dialog_title_q_to_s_visual_deny,
761                     R.string.media_confirm_dialog_message_q_to_s_visual_deny)
762             else ->
763                 Triple(0, 0, 0)
764         }
765 
766         if (iconId == 0 || titleId == 0 || messageId == 0) {
767             throw UnsupportedOperationException()
768         }
769 
770         confirmDialog.showAdvancedConfirmDialog(
771             AdvancedConfirmDialogArgs(
772                 iconId = iconId,
773                 titleId = titleId,
774                 messageId = messageId,
775                 negativeButtonTextId = R.string.media_confirm_dialog_negative_button,
776                 positiveButtonTextId = R.string.media_confirm_dialog_positive_button,
777                 changeRequest =
778                     if (allow) ChangeRequest.GRANT_STORAGE_SUPERGROUP_CONFIRMED
779                     else ChangeRequest.REVOKE_STORAGE_SUPERGROUP_CONFIRMED,
780                 setOneTime = setOneTime,
781                 buttonClicked = buttonClicked
782             )
783         )
784     }
785 
786     /**
787      * Once the user has confirmed that he/she wants to revoke a permission that was granted by
788      * default, actually revoke the permissions.
789      *
790      * @param changeRequest whether to change foreground, background, or both.
791      * @param buttonPressed button pressed to initiate the change, one of
792      *                      AppPermissionFragmentActionReported.button_pressed constants
793      * @param oneTime whether the change should show that the permission was selected as one-time
794      *
795      */
796     fun onDenyAnyWay(changeRequest: ChangeRequest, buttonPressed: Int, oneTime: Boolean) {
797         val group = lightAppPermGroup ?: return
798 
799         val groupsToUpdate = expandToSupergroup(group)
800         for (group in groupsToUpdate) {
801             val wasForegroundGranted = group.foreground.isGranted
802             val wasBackgroundGranted = group.background.isGranted
803             var hasDefaultPermissions = false
804 
805             var newGroup = group
806             val oldGroup = group
807 
808             if (changeRequest andValue ChangeRequest.REVOKE_BACKGROUND != 0 &&
809                     group.hasBackgroundGroup) {
810                 newGroup =
811                     KotlinUtils.revokeBackgroundRuntimePermissions(app, newGroup, false, oneTime)
812 
813                 if (wasBackgroundGranted) {
814                     SafetyNetLogger.logPermissionToggled(newGroup)
815                 }
816                 hasDefaultPermissions = hasDefaultPermissions ||
817                         group.background.isGrantedByDefault
818             }
819 
820             if (changeRequest andValue ChangeRequest.REVOKE_FOREGROUND != 0) {
821                 newGroup =
822                     KotlinUtils.revokeForegroundRuntimePermissions(app, newGroup, false, oneTime)
823                 if (wasForegroundGranted) {
824                     SafetyNetLogger.logPermissionToggled(newGroup)
825                 }
826                 hasDefaultPermissions = group.foreground.isGrantedByDefault
827             }
828             logPermissionChanges(oldGroup, newGroup, buttonPressed)
829 
830             if (hasDefaultPermissions || !group.supportsRuntimePerms) {
831                 hasConfirmedRevoke = true
832             }
833 
834             fullStorageStateLiveData.value?.let {
835                 FullStoragePermissionAppsLiveData.recalculate()
836             }
837         }
838     }
839 
840     /**
841      * Set the All Files access for this app
842      *
843      * @param granted Whether to grant or revoke access
844      */
845     fun setAllFilesAccess(granted: Boolean) {
846         val aom = app.getSystemService(AppOpsManager::class.java)!!
847         val uid = lightAppPermGroup?.packageInfo?.uid ?: return
848         val mode = if (granted) {
849             MODE_ALLOWED
850         } else {
851             MODE_ERRORED
852         }
853         val fullStorageGrant = fullStorageStateLiveData.value?.isGranted
854         if (fullStorageGrant != null && fullStorageGrant != granted) {
855             aom.setUidMode(OPSTR_MANAGE_EXTERNAL_STORAGE, uid, mode)
856             FullStoragePermissionAppsLiveData.recalculate()
857         }
858     }
859 
860     /**
861      * Show the All App Permissions screen with the proper filter group, package name, and user.
862      *
863      * @param fragment The current fragment we wish to transition from
864      */
865     fun showAllPermissions(fragment: Fragment, args: Bundle) {
866         fragment.findNavController().navigateSafe(R.id.app_to_all_perms, args)
867     }
868 
869     private fun getIndividualPermissionDetailResId(group: LightAppPermGroup): Pair<Int, Int> {
870         return when (val numRevoked =
871             group.permissions.filter { !it.value.isGrantedIncludingAppOp }.size) {
872             0 -> R.string.permission_revoked_none to numRevoked
873             group.permissions.size -> R.string.permission_revoked_all to numRevoked
874             else -> R.string.permission_revoked_count to numRevoked
875         }
876     }
877 
878     /**
879      * Get the detail string id of a permission group if it is at least partially fixed by policy.
880      */
881     private fun getDetailResIdForFixedByPolicyPermissionGroup(
882         group: LightAppPermGroup,
883         hasAdmin: Boolean
884     ): Int {
885         val isForegroundPolicyDenied = group.foreground.isPolicyFixed && !group.foreground.isGranted
886         val isPolicyFullyFixedWithGrantedOrNoBkg = group.isPolicyFullyFixed &&
887             (group.background.isGranted || !group.hasBackgroundGroup)
888         if (group.foreground.isSystemFixed || group.background.isSystemFixed) {
889             return R.string.permission_summary_enabled_system_fixed
890         } else if (hasAdmin) {
891             // Permission is fully controlled by policy and cannot be switched
892             if (isForegroundPolicyDenied) {
893                 return R.string.disabled_by_admin
894             } else if (isPolicyFullyFixedWithGrantedOrNoBkg) {
895                 return R.string.enabled_by_admin
896             } else if (group.isPolicyFullyFixed) {
897                 return R.string.permission_summary_enabled_by_admin_foreground_only
898             }
899 
900             // Part of the permission group can still be switched
901             if (group.background.isPolicyFixed && group.background.isGranted) {
902                 return R.string.permission_summary_enabled_by_admin_background_only
903             } else if (group.background.isPolicyFixed) {
904                 return R.string.permission_summary_disabled_by_admin_background_only
905             } else if (group.foreground.isPolicyFixed) {
906                 return R.string.permission_summary_enabled_by_admin_foreground_only
907             }
908         } else {
909             // Permission is fully controlled by policy and cannot be switched
910             if ((isForegroundPolicyDenied) || isPolicyFullyFixedWithGrantedOrNoBkg) {
911                 // Permission is fully controlled by policy and cannot be switched
912                 // State will be displayed by switch, so no need to add text for that
913                 return R.string.permission_summary_enforced_by_policy
914             } else if (group.isPolicyFullyFixed) {
915                 return R.string.permission_summary_enabled_by_policy_foreground_only
916             }
917 
918             // Part of the permission group can still be switched
919             if (group.background.isPolicyFixed && group.background.isGranted) {
920                 return R.string.permission_summary_enabled_by_policy_background_only
921             } else if (group.background.isPolicyFixed) {
922                 return R.string.permission_summary_disabled_by_policy_background_only
923             } else if (group.foreground.isPolicyFixed) {
924                 return R.string.permission_summary_enabled_by_policy_foreground_only
925             }
926         }
927         return 0
928     }
929 
930     @SuppressLint("NewApi")
931     private fun logPermissionChanges(
932         oldGroup: LightAppPermGroup,
933         newGroup: LightAppPermGroup,
934         buttonPressed: Int
935     ) {
936         val changeId = Random().nextLong()
937 
938         for ((permName, permission) in oldGroup.permissions) {
939             val newPermission = newGroup.permissions[permName] ?: continue
940 
941             if (permission.isGrantedIncludingAppOp != newPermission.isGrantedIncludingAppOp ||
942                 permission.flags != newPermission.flags) {
943                 logAppPermissionFragmentActionReported(changeId, newPermission, buttonPressed)
944                 PermissionDecisionStorageImpl.recordPermissionDecision(app.applicationContext,
945                     packageName, permGroupName, newPermission.isGrantedIncludingAppOp)
946             }
947         }
948     }
949 
950     private fun logAppPermissionFragmentActionReported(
951         changeId: Long,
952         permission: LightPermission,
953         buttonPressed: Int
954     ) {
955         val uid = KotlinUtils.getPackageUid(app, packageName, user) ?: return
956         PermissionControllerStatsLog.write(APP_PERMISSION_FRAGMENT_ACTION_REPORTED, sessionId,
957             changeId, uid, packageName, permission.permInfo.name,
958             permission.isGrantedIncludingAppOp, permission.flags, buttonPressed)
959         Log.v(LOG_TAG, "Permission changed via UI with sessionId=$sessionId changeId=" +
960             "$changeId uid=$uid packageName=$packageName permission=" + permission.permInfo.name +
961             " isGranted=" + permission.isGrantedIncludingAppOp + " permissionFlags=" +
962             permission.flags + " buttonPressed=$buttonPressed")
963     }
964 
965     /**
966      * Logs information about this AppPermissionGroup and view session
967      */
968     fun logAppPermissionFragmentViewed() {
969         val uid = KotlinUtils.getPackageUid(app, packageName, user) ?: return
970         PermissionControllerStatsLog.write(APP_PERMISSION_FRAGMENT_VIEWED, sessionId,
971             uid, packageName, permGroupName)
972         Log.v(LOG_TAG, "AppPermission fragment viewed with sessionId=$sessionId uid=" +
973             "$uid packageName=$packageName" +
974             "permGroupName=$permGroupName")
975     }
976 }
977 
978 /**
979  * Factory for an AppPermissionViewModel
980  *
981  * @param app The current application
982  * @param packageName The name of the package this ViewModel represents
983  * @param permGroupName The name of the permission group this ViewModel represents
984  * @param user The user of the package
985  * @param sessionId A session ID used in logs to identify this particular session
986  */
987 class AppPermissionViewModelFactory(
988     private val app: Application,
989     private val packageName: String,
990     private val permGroupName: String,
991     private val user: UserHandle,
992     private val sessionId: Long
993 ) : ViewModelProvider.Factory {
createnull994     override fun <T : ViewModel> create(modelClass: Class<T>): T {
995         @Suppress("UNCHECKED_CAST")
996         return AppPermissionViewModel(app, packageName, permGroupName, user, sessionId) as T
997     }
998 }
999