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.app.AppOpsManager
21 import android.app.AppOpsManager.MODE_ALLOWED
22 import android.app.AppOpsManager.MODE_ERRORED
23 import android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE
24 import android.app.Application
25 import android.content.Intent
26 import android.os.Build
27 import android.os.Bundle
28 import android.os.UserHandle
29 import android.util.Log
30 import androidx.annotation.StringRes
31 import androidx.fragment.app.Fragment
32 import androidx.lifecycle.MutableLiveData
33 import androidx.lifecycle.ViewModel
34 import androidx.lifecycle.ViewModelProvider
35 import androidx.navigation.fragment.findNavController
36 import com.android.permissioncontroller.PermissionControllerStatsLog
37 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED
38 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_VIEWED
39 import com.android.permissioncontroller.R
40 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
41 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState
42 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
43 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
44 import com.android.permissioncontroller.permission.data.get
45 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
46 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
47 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW
48 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_ALWAYS
49 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND
50 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ASK
51 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ASK_ONCE
52 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY
53 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY_FOREGROUND
54 import com.android.permissioncontroller.permission.utils.KotlinUtils
55 import com.android.permissioncontroller.permission.utils.LocationUtils
56 import com.android.permissioncontroller.permission.utils.SafetyNetLogger
57 import com.android.permissioncontroller.permission.utils.Utils
58 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.ASSISTANT
59 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.CARRIER_SERVICE
60 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.SOUND_TRIGGER
61 import com.android.permissioncontroller.permission.utils.Utils.ForegroundCapableType.VOICE_INTERACTION
62 import com.android.permissioncontroller.permission.utils.navigateSafe
63 import com.android.settingslib.RestrictedLockUtils
64 import java.util.Random
65 import kotlin.collections.component1
66 import kotlin.collections.component2
67 
68 /**
69  * ViewModel for the AppPermissionFragment. Determines button state and detail text strings, logs
70  * permission change information, and makes permission changes.
71  *
72  * @param app The current application
73  * @param packageName The name of the package this ViewModel represents
74  * @param permGroupName The name of the permission group this ViewModel represents
75  * @param user The user of the package
76  * @param sessionId A session ID used in logs to identify this particular session
77  * @param couldPackageHaveFgCapabilities Whether the package could access a foreground perm while in
78  * the background
79  */
80 class AppPermissionViewModel(
81     private val app: Application,
82     private val packageName: String,
83     private val permGroupName: String,
84     private val user: UserHandle,
85     private val sessionId: Long,
86     private val foregroundCapableType: Utils.ForegroundCapableType
87 ) : ViewModel() {
88 
89     companion object {
90         private val LOG_TAG = AppPermissionViewModel::class.java.simpleName
91     }
92 
93     interface ConfirmDialogShowingFragment {
94         fun showConfirmDialog(
95             changeRequest: ChangeRequest,
96             @StringRes messageId: Int,
97             buttonPressed: Int,
98             oneTime: Boolean
99         )
100     }
101 
102     enum class ChangeRequest(val value: Int) {
103         GRANT_FOREGROUND(1),
104         REVOKE_FOREGROUND(2),
105         GRANT_BACKGROUND(4),
106         REVOKE_BACKGROUND(8),
107         GRANT_BOTH(GRANT_FOREGROUND.value or GRANT_BACKGROUND.value),
108         REVOKE_BOTH(REVOKE_FOREGROUND.value or REVOKE_BACKGROUND.value),
109         GRANT_FOREGROUND_ONLY(GRANT_FOREGROUND.value or REVOKE_BACKGROUND.value),
110         GRANT_All_FILE_ACCESS(16);
111 
112         infix fun andValue(other: ChangeRequest): Int {
113             return value and other.value
114         }
115     }
116 
117     enum class ButtonType(val type: Int) {
118         ALLOW(0),
119         ALLOW_ALWAYS(1),
120         ALLOW_FOREGROUND(2),
121         ASK_ONCE(3),
122         ASK(4),
123         DENY(5),
124         DENY_FOREGROUND(6);
125     }
126 
127     private val isStorage = permGroupName == Manifest.permission_group.STORAGE
128     private var hasConfirmedRevoke = false
129     private var lightAppPermGroup: LightAppPermGroup? = null
130 
131     /**
132      * A livedata which determines which detail string, if any, should be shown
133      */
134     val detailResIdLiveData = MutableLiveData<Pair<Int, Int?>>()
135     /**
136      * A livedata which stores the device admin, if there is one
137      */
138     val showAdminSupportLiveData = MutableLiveData<RestrictedLockUtils.EnforcedAdmin>()
139 
140     /**
141      * A livedata which determines which detail string, if any, should be shown
142      */
143     val fullStorageStateLiveData = object : SmartUpdateMediatorLiveData<FullStoragePackageState>() {
144         init {
145             if (isStorage) {
146                 addSource(FullStoragePermissionAppsLiveData) {
147                     updateIfActive()
148                 }
149             } else {
150                 value = null
151             }
152         }
153         override fun onUpdate() {
154             for (state in FullStoragePermissionAppsLiveData.value ?: return) {
155                 if (state.packageName == packageName && state.user == user) {
156                     value = state
157                     return
158                 }
159             }
160             value = null
161             return
162         }
163     }
164 
165     data class ButtonState(
166         var isChecked: Boolean,
167         var isEnabled: Boolean,
168         var isShown: Boolean,
169         var customRequest: ChangeRequest?
170     ) {
171         constructor() : this(false, true, false, null)
172     }
173 
174     /**
175      * A livedata which computes the state of the radio buttons
176      */
177     val buttonStateLiveData = object
178         : SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<ButtonType, ButtonState>>() {
179 
180         private val appPermGroupLiveData = LightAppPermGroupLiveData[packageName, permGroupName,
181             user]
182 
183         init {
184             addSource(appPermGroupLiveData) { appPermGroup ->
185                 lightAppPermGroup = appPermGroup
186                 if (appPermGroupLiveData.isInitialized && appPermGroup == null) {
187                     value = null
188                 } else if (appPermGroup != null) {
189                     if (isStorage && !fullStorageStateLiveData.isInitialized) {
190                         return@addSource
191                     }
192                     if (value == null) {
193                         logAppPermissionFragmentViewed()
194                     }
195                     updateIfActive()
196                 }
197             }
198 
199             if (isStorage) {
200                 addSource(fullStorageStateLiveData) {
201                     updateIfActive()
202                 }
203             }
204         }
205 
206         override fun onUpdate() {
207             val group = appPermGroupLiveData.value ?: return
208 
209             val admin = RestrictedLockUtils.getProfileOrDeviceOwner(app, user)
210 
211             val couldPackageHaveFgCapabilities =
212                     foregroundCapableType != Utils.ForegroundCapableType.NONE
213 
214             val allowedState = ButtonState()
215             val allowedAlwaysState = ButtonState()
216             val allowedForegroundState = ButtonState()
217             val askOneTimeState = ButtonState()
218             val askState = ButtonState()
219             val deniedState = ButtonState()
220             val deniedForegroundState = ButtonState() // when bg is fixed as granted and fg is flex
221 
222             askState.isShown = Utils.supportsOneTimeGrant(permGroupName) &&
223                     !(group.foreground.isGranted && group.isOneTime)
224             deniedState.isShown = true
225 
226             if (group.hasPermWithBackgroundMode) {
227                 // Background / Foreground / Deny case
228                 allowedForegroundState.isShown = true
229                 if (group.hasBackgroundGroup) {
230                     allowedAlwaysState.isShown = true
231                 }
232 
233                 allowedAlwaysState.isChecked = group.background.isGranted &&
234                     group.foreground.isGranted
235                 allowedForegroundState.isChecked = group.foreground.isGranted &&
236                     !group.background.isGranted && !group.isOneTime
237                 askState.isChecked = !group.foreground.isGranted && group.isOneTime
238                 askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime
239                 askOneTimeState.isShown = askOneTimeState.isChecked
240                 deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime
241                 var detailId = 0
242                 if (applyFixToForegroundBackground(group, group.foreground.isSystemFixed,
243                         group.background.isSystemFixed, allowedAlwaysState,
244                         allowedForegroundState, askState, deniedState,
245                         deniedForegroundState) ||
246                     applyFixToForegroundBackground(group, group.foreground.isPolicyFixed,
247                         group.background.isPolicyFixed, allowedAlwaysState,
248                         allowedForegroundState, askState, deniedState,
249                         deniedForegroundState)) {
250                     showAdminSupportLiveData.value = admin
251                     detailId = getDetailResIdForFixedByPolicyPermissionGroup(group,
252                         admin != null)
253                     if (detailId != 0) {
254                         detailResIdLiveData.value = detailId to null
255                     }
256                 } else if (Utils.areGroupPermissionsIndividuallyControlled(app, permGroupName)) {
257                     val detailPair = getIndividualPermissionDetailResId(group)
258                     detailId = detailPair.first
259                     detailResIdLiveData.value = detailId to detailPair.second
260                 }
261                 if (couldPackageHaveFgCapabilities) {
262                     // Correct the UI in case the app can access bg location with only fg perm
263                     allowedAlwaysState.isShown = true
264                     allowedAlwaysState.isChecked =
265                             allowedAlwaysState.isChecked || allowedForegroundState.isChecked
266                     // Should be enabled && is denied enabled for the user to be able to switch to.
267                     allowedAlwaysState.isEnabled =
268                             ((allowedAlwaysState.isEnabled && allowedAlwaysState.isShown) ||
269                                     allowedForegroundState.isEnabled) &&
270                                     ((deniedState.isEnabled && deniedState.isShown) ||
271                                             (deniedForegroundState.isEnabled &&
272                                                     deniedForegroundState.isShown))
273                     allowedForegroundState.isChecked = false
274                     allowedForegroundState.isEnabled = false
275                     deniedState.isChecked = deniedState.isChecked || askState.isChecked
276                     deniedForegroundState.isChecked = deniedState.isChecked
277                     askState.isEnabled = false
278 
279                     if (detailId == 0) {
280                         detailId = getForegroundCapableDetailResId(foregroundCapableType)
281                         if (detailId != 0) {
282                             detailResIdLiveData.value = detailId to null
283                         }
284                     }
285                 }
286             } else {
287                 // Allow / Deny case
288                 allowedState.isShown = true
289 
290                 allowedState.isChecked = group.foreground.isGranted
291                 askState.isChecked = !group.foreground.isGranted && group.isOneTime
292                 askOneTimeState.isChecked = group.foreground.isGranted && group.isOneTime
293                 askOneTimeState.isShown = askOneTimeState.isChecked
294                 deniedState.isChecked = !group.foreground.isGranted && !group.isOneTime
295 
296                 var detailId = 0
297                 if (group.foreground.isPolicyFixed || group.foreground.isSystemFixed) {
298                     allowedState.isEnabled = false
299                     askState.isEnabled = false
300                     deniedState.isEnabled = false
301                     showAdminSupportLiveData.value = admin
302                     val detailId = getDetailResIdForFixedByPolicyPermissionGroup(group,
303                         admin != null)
304                     if (detailId != 0) {
305                         detailResIdLiveData.value = detailId to null
306                     }
307                 }
308                 if (isForegroundGroupSpecialCase(permGroupName)) {
309                     allowedForegroundState.isShown = true
310                     allowedState.isShown = false
311                     allowedForegroundState.isChecked = allowedState.isChecked
312                     allowedForegroundState.isEnabled = allowedState.isEnabled
313                     if (couldPackageHaveFgCapabilities || (Utils.isEmergencyApp(app, packageName) &&
314                                     isMicrophone(permGroupName))) {
315                         allowedAlwaysState.isShown = true
316                         allowedAlwaysState.isChecked = allowedForegroundState.isChecked
317                         allowedAlwaysState.isEnabled = allowedForegroundState.isEnabled
318                         allowedForegroundState.isChecked = false
319                         allowedForegroundState.isEnabled = false
320                         deniedState.isChecked = deniedState.isChecked || askState.isChecked
321                         askState.isEnabled = false
322 
323                         if (detailId == 0) {
324                             detailId = getForegroundCapableDetailResId(foregroundCapableType)
325                             if (detailId != 0) {
326                                 detailResIdLiveData.value = detailId to null
327                             }
328                         }
329                     }
330                 }
331             }
332             if (group.packageInfo.targetSdkVersion < Build.VERSION_CODES.M) {
333                 // Pre-M app's can't ask for runtime permissions
334                 askState.isShown = false
335                 deniedState.isChecked = askState.isChecked || deniedState.isChecked
336                 deniedForegroundState.isChecked = askState.isChecked ||
337                     deniedForegroundState.isChecked
338             }
339 
340             val storageState = fullStorageStateLiveData.value
341             if (isStorage && storageState?.isLegacy != true) {
342                 val allowedAllFilesState = allowedAlwaysState
343                 val allowedMediaOnlyState = allowedForegroundState
344                 if (storageState != null) {
345                         // Set up the tri state permission for storage
346                         allowedAllFilesState.isEnabled = allowedState.isEnabled
347                         allowedAllFilesState.isShown = true
348                         if (storageState.isGranted) {
349                             allowedAllFilesState.isChecked = true
350                             deniedState.isChecked = false
351                         }
352                 } else {
353                     allowedAllFilesState.isEnabled = false
354                     allowedAllFilesState.isShown = false
355                 }
356                 allowedMediaOnlyState.isShown = true
357                 allowedMediaOnlyState.isEnabled = allowedState.isEnabled
358                 allowedMediaOnlyState.isChecked = allowedState.isChecked &&
359                     storageState?.isGranted != true
360                 allowedState.isChecked = false
361                 allowedState.isShown = false
362             }
363 
364             value = mapOf(ALLOW to allowedState, ALLOW_ALWAYS to allowedAlwaysState,
365                 ALLOW_FOREGROUND to allowedForegroundState, ASK_ONCE to askOneTimeState,
366                 ASK to askState, DENY to deniedState, DENY_FOREGROUND to deniedForegroundState)
367         }
368     }
369 
370     // TODO evanseverson: Actually change mic/camera to be a foreground only permission
371     private fun isForegroundGroupSpecialCase(permissionGroupName: String): Boolean {
372         return permissionGroupName.equals(Manifest.permission_group.CAMERA) ||
373                 permissionGroupName.equals(Manifest.permission_group.MICROPHONE)
374     }
375     private fun isMicrophone(permissionGroupName: String): Boolean {
376         return permissionGroupName.equals(Manifest.permission_group.MICROPHONE)
377     }
378 
379     /**
380      * Modifies the radio buttons to reflect the current policy fixing state
381      *
382      * @return if anything was changed
383      */
384     private fun applyFixToForegroundBackground(
385         group: LightAppPermGroup,
386         isForegroundFixed: Boolean,
387         isBackgroundFixed: Boolean,
388         allowedAlwaysState: ButtonState,
389         allowedForegroundState: ButtonState,
390         askState: ButtonState,
391         deniedState: ButtonState,
392         deniedForegroundState: ButtonState
393     ): Boolean {
394         if (isBackgroundFixed && isForegroundFixed) {
395             // Background and foreground are both policy fixed. Disable everything
396             allowedAlwaysState.isEnabled = false
397             allowedForegroundState.isEnabled = false
398             askState.isEnabled = false
399             deniedState.isEnabled = false
400 
401             if (askState.isChecked) {
402                 askState.isChecked = false
403                 deniedState.isChecked = true
404             }
405         } else if (isBackgroundFixed && !isForegroundFixed) {
406             if (group.background.isGranted) {
407                 // Background policy fixed as granted, foreground flexible. Granting
408                 // foreground implies background comes with it in this case.
409                 // Only allow user to grant background or deny (which only toggles fg)
410                 allowedForegroundState.isEnabled = false
411                 askState.isEnabled = false
412                 deniedState.isShown = false
413                 deniedForegroundState.isShown = true
414                 deniedForegroundState.isChecked = deniedState.isChecked
415 
416                 if (askState.isChecked) {
417                     askState.isChecked = false
418                     deniedState.isChecked = true
419                 }
420             } else {
421                 // Background policy fixed as not granted, foreground flexible
422                 allowedAlwaysState.isEnabled = false
423             }
424         } else if (!isBackgroundFixed && isForegroundFixed) {
425             if (group.foreground.isGranted) {
426                 // Foreground is fixed as granted, background flexible.
427                 // Allow switching between foreground and background. No denying
428                 allowedForegroundState.isEnabled = allowedAlwaysState.isShown
429                 askState.isEnabled = false
430                 deniedState.isEnabled = false
431             } else {
432                 // Foreground is fixed denied. Background irrelevant
433                 allowedAlwaysState.isEnabled = false
434                 allowedForegroundState.isEnabled = false
435                 askState.isEnabled = false
436                 deniedState.isEnabled = false
437 
438                 if (askState.isChecked) {
439                     askState.isChecked = false
440                     deniedState.isChecked = true
441                 }
442             }
443         } else {
444             return false
445         }
446         return true
447     }
448 
449     /**
450      * Navigate to either the App Permission Groups screen, or the Permission Apps Screen.
451      * @param fragment The current fragment
452      * @param action The action to be taken
453      * @param args The arguments to pass to the fragment
454      */
455     fun showBottomLinkPage(fragment: Fragment, action: String, args: Bundle) {
456         var actionId = R.id.app_to_perm_groups
457         if (action == Intent.ACTION_MANAGE_PERMISSION_APPS) {
458             actionId = R.id.app_to_perm_apps
459         }
460 
461         fragment.findNavController().navigateSafe(actionId, args)
462     }
463 
464     /**
465      * Request to grant/revoke permissions group.
466      *
467      * Does <u>not</u> handle:
468      *
469      *  * Individually granted permissions
470      *  * Permission groups with background permissions
471      *
472      * <u>Does</u> handle:
473      *
474      *  * Default grant permissions
475      *
476      * @param setOneTime Whether or not to set this permission as one time
477      * @param fragment The fragment calling this method
478      * @param defaultDeny The system which will show the default deny dialog. Usually the same as
479      * the fragment.
480      * @param changeRequest Which permission group (foreground/background/both) should be changed
481      * @param buttonClicked button which was pressed to initiate the change, one of
482      *                      AppPermissionFragmentActionReported.button_pressed constants
483      *
484      * @return The dialogue to show, if applicable, or if the request was processed.
485      */
486     fun requestChange(
487         setOneTime: Boolean,
488         fragment: Fragment,
489         defaultDeny: ConfirmDialogShowingFragment,
490         changeRequest: ChangeRequest,
491         buttonClicked: Int
492     ) {
493         val context = fragment.context ?: return
494         val group = lightAppPermGroup ?: return
495         val wasForegroundGranted = group.foreground.isGranted
496         val wasBackgroundGranted = group.background.isGranted
497 
498         if (LocationUtils.isLocationGroupAndProvider(context, permGroupName, packageName)) {
499             val packageLabel = KotlinUtils.getPackageLabel(app, packageName, user)
500             LocationUtils.showLocationDialog(context, packageLabel)
501         }
502 
503         val shouldGrantForeground = !group.isForegroundFixed &&
504                 changeRequest andValue ChangeRequest.GRANT_FOREGROUND != 0
505         val shouldGrantBackground = !group.isBackgroundFixed &&
506                 changeRequest andValue ChangeRequest.GRANT_BACKGROUND != 0
507         val shouldRevokeForeground = !group.isForegroundFixed &&
508                 changeRequest andValue ChangeRequest.REVOKE_FOREGROUND != 0
509         val shouldRevokeBackground = !group.isBackgroundFixed &&
510                 changeRequest andValue ChangeRequest.REVOKE_BACKGROUND != 0
511         var showDefaultDenyDialog = false
512         var showGrantedByDefaultWarning = false
513 
514         if (shouldRevokeForeground && wasForegroundGranted) {
515             showDefaultDenyDialog = (group.foreground.isGrantedByDefault ||
516                     !group.supportsRuntimePerms ||
517                     group.hasInstallToRuntimeSplit)
518             showGrantedByDefaultWarning = showGrantedByDefaultWarning ||
519                     group.foreground.isGrantedByDefault
520         }
521 
522         if (shouldRevokeBackground && wasBackgroundGranted) {
523             showDefaultDenyDialog = showDefaultDenyDialog ||
524                     group.background.isGrantedByDefault ||
525                     !group.supportsRuntimePerms ||
526                     group.hasInstallToRuntimeSplit
527             showGrantedByDefaultWarning = showGrantedByDefaultWarning ||
528                     group.background.isGrantedByDefault
529         }
530 
531         if (showDefaultDenyDialog && !hasConfirmedRevoke && showGrantedByDefaultWarning) {
532             defaultDeny.showConfirmDialog(changeRequest, R.string.system_warning, buttonClicked,
533                 setOneTime)
534             return
535         }
536 
537         if (showDefaultDenyDialog && !hasConfirmedRevoke) {
538             defaultDeny.showConfirmDialog(changeRequest, R.string.old_sdk_deny_warning,
539                     buttonClicked, setOneTime)
540             return
541         }
542 
543         var newGroup = group
544         val oldGroup = group
545 
546         if (shouldRevokeBackground && group.hasBackgroundGroup &&
547                 (wasBackgroundGranted || group.background.isUserFixed ||
548                         group.isOneTime != setOneTime)) {
549             newGroup = KotlinUtils
550                     .revokeBackgroundRuntimePermissions(app, newGroup)
551 
552             // only log if we have actually denied permissions, not if we switch from
553             // "ask every time" to denied
554             if (wasBackgroundGranted) {
555                 SafetyNetLogger.logPermissionToggled(newGroup, true)
556             }
557         }
558 
559         if (shouldRevokeForeground && (wasForegroundGranted || group.isOneTime != setOneTime)) {
560             newGroup = KotlinUtils
561                     .revokeForegroundRuntimePermissions(app, newGroup, false, setOneTime)
562 
563             // only log if we have actually denied permissions, not if we switch from
564             // "ask every time" to denied
565             if (wasForegroundGranted) {
566                 SafetyNetLogger.logPermissionToggled(newGroup)
567             }
568         }
569 
570         if (shouldGrantForeground) {
571             newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup)
572 
573             if (!wasForegroundGranted) {
574                 SafetyNetLogger.logPermissionToggled(newGroup)
575             }
576         }
577 
578         if (shouldGrantBackground && group.hasBackgroundGroup) {
579             newGroup = KotlinUtils.grantBackgroundRuntimePermissions(app, newGroup)
580 
581             if (!wasBackgroundGranted) {
582                 SafetyNetLogger.logPermissionToggled(newGroup, true)
583             }
584         }
585 
586         logPermissionChanges(oldGroup, newGroup, buttonClicked)
587 
588         fullStorageStateLiveData.value?.let {
589             FullStoragePermissionAppsLiveData.recalculate()
590         }
591     }
592 
593     /**
594      * Once the user has confirmed that he/she wants to revoke a permission that was granted by
595      * default, actually revoke the permissions.
596      *
597      * @param changeRequest whether to change foreground, background, or both.
598      * @param buttonPressed button pressed to initiate the change, one of
599      *                      AppPermissionFragmentActionReported.button_pressed constants
600      * @param oneTime whether the change should show that the permission was selected as one-time
601      *
602      */
603     fun onDenyAnyWay(changeRequest: ChangeRequest, buttonPressed: Int, oneTime: Boolean) {
604         val group = lightAppPermGroup ?: return
605         val wasForegroundGranted = group.foreground.isGranted
606         val wasBackgroundGranted = group.background.isGranted
607         var hasDefaultPermissions = false
608 
609         var newGroup = group
610         val oldGroup = group
611 
612         if (changeRequest andValue ChangeRequest.REVOKE_BACKGROUND != 0 &&
613             group.hasBackgroundGroup) {
614             newGroup = KotlinUtils.revokeBackgroundRuntimePermissions(app, newGroup, false, oneTime)
615 
616             if (wasBackgroundGranted) {
617                 SafetyNetLogger.logPermissionToggled(newGroup)
618             }
619             hasDefaultPermissions = hasDefaultPermissions ||
620                 group.background.isGrantedByDefault
621         }
622 
623         if (changeRequest andValue ChangeRequest.REVOKE_FOREGROUND != 0) {
624             newGroup = KotlinUtils.revokeForegroundRuntimePermissions(app, newGroup, false, oneTime)
625             if (wasForegroundGranted) {
626                 SafetyNetLogger.logPermissionToggled(newGroup)
627             }
628             hasDefaultPermissions = group.foreground.isGrantedByDefault
629         }
630         logPermissionChanges(oldGroup, newGroup, buttonPressed)
631 
632         if (hasDefaultPermissions || !group.supportsRuntimePerms) {
633             hasConfirmedRevoke = true
634         }
635 
636         fullStorageStateLiveData.value?.let {
637             FullStoragePermissionAppsLiveData.recalculate()
638         }
639     }
640 
641     /**
642      * Set the All Files access for this app
643      *
644      * @param granted Whether to grant or revoke access
645      */
646     fun setAllFilesAccess(granted: Boolean) {
647         val aom = app.getSystemService(AppOpsManager::class.java)!!
648         val uid = lightAppPermGroup?.packageInfo?.uid ?: return
649         val mode = if (granted) {
650             MODE_ALLOWED
651         } else {
652             MODE_ERRORED
653         }
654         val fullStorageGrant = fullStorageStateLiveData.value?.isGranted
655         if (fullStorageGrant != null && fullStorageGrant != granted) {
656             aom.setUidMode(OPSTR_MANAGE_EXTERNAL_STORAGE, uid, mode)
657             FullStoragePermissionAppsLiveData.recalculate()
658         }
659     }
660 
661     /**
662      * Show the All App Permissions screen with the proper filter group, package name, and user.
663      *
664      * @param fragment The current fragment we wish to transition from
665      */
666     fun showAllPermissions(fragment: Fragment, args: Bundle) {
667         fragment.findNavController().navigateSafe(R.id.app_to_all_perms, args)
668     }
669 
670     private fun getIndividualPermissionDetailResId(group: LightAppPermGroup): Pair<Int, Int> {
671         return when (val numRevoked =
672             group.permissions.filter { !it.value.isGrantedIncludingAppOp }.size) {
673             0 -> R.string.permission_revoked_none to numRevoked
674             group.permissions.size -> R.string.permission_revoked_all to numRevoked
675             else -> R.string.permission_revoked_count to numRevoked
676         }
677     }
678 
679     /**
680      * Get the detail string id of a permission group if it is at least partially fixed by policy.
681      */
682     private fun getDetailResIdForFixedByPolicyPermissionGroup(
683         group: LightAppPermGroup,
684         hasAdmin: Boolean
685     ): Int {
686         val isForegroundPolicyDenied = group.foreground.isPolicyFixed && !group.foreground.isGranted
687         val isPolicyFullyFixedWithGrantedOrNoBkg = group.isPolicyFullyFixed &&
688             (group.background.isGranted || !group.hasBackgroundGroup)
689         if (group.foreground.isSystemFixed || group.background.isSystemFixed) {
690             return R.string.permission_summary_enabled_system_fixed
691         } else if (hasAdmin) {
692             // Permission is fully controlled by policy and cannot be switched
693             if (isForegroundPolicyDenied) {
694                 return R.string.disabled_by_admin
695             } else if (isPolicyFullyFixedWithGrantedOrNoBkg) {
696                 return R.string.enabled_by_admin
697             } else if (group.isPolicyFullyFixed) {
698                 return R.string.permission_summary_enabled_by_admin_foreground_only
699             }
700 
701             // Part of the permission group can still be switched
702             if (group.background.isPolicyFixed && group.background.isGranted) {
703                 return R.string.permission_summary_enabled_by_admin_background_only
704             } else if (group.background.isPolicyFixed) {
705                 return R.string.permission_summary_disabled_by_admin_background_only
706             } else if (group.foreground.isPolicyFixed) {
707                 return R.string.permission_summary_enabled_by_admin_foreground_only
708             }
709         } else {
710             // Permission is fully controlled by policy and cannot be switched
711             if ((isForegroundPolicyDenied) || isPolicyFullyFixedWithGrantedOrNoBkg) {
712                 // Permission is fully controlled by policy and cannot be switched
713                 // State will be displayed by switch, so no need to add text for that
714                 return R.string.permission_summary_enforced_by_policy
715             } else if (group.isPolicyFullyFixed) {
716                 return R.string.permission_summary_enabled_by_policy_foreground_only
717             }
718 
719             // Part of the permission group can still be switched
720             if (group.background.isPolicyFixed && group.background.isGranted) {
721                 return R.string.permission_summary_enabled_by_policy_background_only
722             } else if (group.background.isPolicyFixed) {
723                 return R.string.permission_summary_disabled_by_policy_background_only
724             } else if (group.foreground.isPolicyFixed) {
725                 return R.string.permission_summary_enabled_by_policy_foreground_only
726             }
727         }
728         return 0
729     }
730 
731     private fun getForegroundCapableDetailResId(type: Utils.ForegroundCapableType): Int {
732         when (type) {
733             SOUND_TRIGGER -> return R.string.fg_capabilities_sound_trigger
734             ASSISTANT -> return R.string.fg_capabilities_assistant
735             VOICE_INTERACTION -> return R.string.fg_capabilities_voice_interaction
736             CARRIER_SERVICE -> return R.string.fg_capabilities_carrier
737         }
738         return 0
739     }
740 
741     private fun logPermissionChanges(
742         oldGroup: LightAppPermGroup,
743         newGroup: LightAppPermGroup,
744         buttonPressed: Int
745     ) {
746         val changeId = Random().nextLong()
747 
748         for ((permName, permission) in oldGroup.permissions) {
749             val newPermission = newGroup.permissions[permName] ?: continue
750 
751             if (permission.isGrantedIncludingAppOp != newPermission.isGrantedIncludingAppOp ||
752                 permission.flags != newPermission.flags) {
753                 logAppPermissionFragmentActionReported(changeId, newPermission, buttonPressed)
754             }
755         }
756     }
757 
758     private fun logAppPermissionFragmentActionReported(
759         changeId: Long,
760         permission: LightPermission,
761         buttonPressed: Int
762     ) {
763         val uid = KotlinUtils.getPackageUid(app, packageName, user) ?: return
764         PermissionControllerStatsLog.write(APP_PERMISSION_FRAGMENT_ACTION_REPORTED, sessionId,
765             changeId, uid, packageName, permission.permInfo.name,
766             permission.isGrantedIncludingAppOp, permission.flags, buttonPressed)
767         Log.v(LOG_TAG, "Permission changed via UI with sessionId=$sessionId changeId=" +
768             "$changeId uid=$uid packageName=$packageName permission=" + permission.permInfo.name +
769             " isGranted=" + permission.isGrantedIncludingAppOp + " permissionFlags=" +
770             permission.flags + " buttonPressed=$buttonPressed")
771     }
772 
773     /**
774      * Logs information about this AppPermissionGroup and view session
775      */
776     fun logAppPermissionFragmentViewed() {
777         val uid = KotlinUtils.getPackageUid(app, packageName, user) ?: return
778         PermissionControllerStatsLog.write(APP_PERMISSION_FRAGMENT_VIEWED, sessionId,
779             uid, packageName, permGroupName)
780         Log.v(LOG_TAG, "AppPermission fragment viewed with sessionId=$sessionId uid=" +
781             "$uid packageName=$packageName" +
782             "permGroupName=$permGroupName")
783     }
784 }
785 
786 /**
787  * Factory for an AppPermissionViewModel
788  *
789  * @param app The current application
790  * @param packageName The name of the package this ViewModel represents
791  * @param permGroupName The name of the permission group this ViewModel represents
792  * @param user The user of the package
793  * @param sessionId A session ID used in logs to identify this particular session
794  */
795 class AppPermissionViewModelFactory(
796     private val app: Application,
797     private val packageName: String,
798     private val permGroupName: String,
799     private val user: UserHandle,
800     private val sessionId: Long,
801     private val foregroundCapableType: Utils.ForegroundCapableType
802 ) : ViewModelProvider.Factory {
createnull803     override fun <T : ViewModel> create(modelClass: Class<T>): T {
804         @Suppress("UNCHECKED_CAST")
805         return AppPermissionViewModel(app, packageName, permGroupName, user, sessionId,
806                 foregroundCapableType) as T
807     }
808 }