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 
17 package com.android.permissioncontroller.permission.ui.model
18 
19 import android.app.Application
20 import android.content.Context
21 import android.content.pm.PackageInfo
22 import android.os.Bundle
23 import androidx.fragment.app.Fragment
24 import androidx.lifecycle.ViewModel
25 import androidx.lifecycle.ViewModelProvider
26 import androidx.navigation.NavController
27 import androidx.navigation.fragment.NavHostFragment
28 import com.android.permissioncontroller.R
29 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
30 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
31 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
32 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
33 import com.android.permissioncontroller.permission.data.get
34 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
35 import com.android.permissioncontroller.permission.utils.PermissionMapping
36 import com.android.permissioncontroller.permission.utils.Utils
37 import com.android.permissioncontroller.permission.utils.navigateSafe
38 import com.android.settingslib.RestrictedLockUtils
39 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
40 import java.util.stream.Collectors
41 
42 /** View model for legacy {@link ReviewPermissionsFragment}. */
43 class ReviewPermissionsViewModel(val app: Application, val packageInfo: PackageInfo) : ViewModel() {
44 
45     private val mUser = android.os.Process.myUserHandle()
46 
47     /** Holds permission groups for a package or an empty map in case no user review is required. */
48     val permissionGroupsLiveData =
49         object : SmartUpdateMediatorLiveData<Map<String, LightAppPermGroup>>() {
50             val packagePermsLiveData = PackagePermissionsLiveData[packageInfo.packageName, mUser]
51 
52             init {
53                 addSource(packagePermsLiveData) { update() }
54             }
55 
56             val permissionGroups = mutableMapOf<String, LightAppPermGroupLiveData>()
57 
58             override fun onUpdate() {
59                 val permissionGroupsMap = packagePermsLiveData.value ?: return
60                 val filteredGroups =
61                     permissionGroupsMap.keys
62                         .stream()
63                         .filter { it -> !it.equals(NON_RUNTIME_NORMAL_PERMS) }
64                         .collect(Collectors.toList())
65 
66                 val getPermGroupLiveData = { permGroupName: String ->
67                     LightAppPermGroupLiveData[packageInfo.packageName, permGroupName, mUser]
68                 }
69                 setSourcesToDifference(filteredGroups, permissionGroups, getPermGroupLiveData)
70                 if (
71                     permissionGroups.values.all { it.isInitialized } &&
72                         permissionGroups.values.all { !it.isStale }
73                 ) {
74                     val permGroups: List<LightAppPermGroup?> =
75                         permissionGroups.values.map { it.value }
76                     val reviewGroups =
77                         permGroups
78                             .filterNotNull()
79                             .filter {
80                                 shouldShowPermission(it) &&
81                                     Utils.OS_PKG == it.permGroupInfo.packageName
82                             }
83                             .associateBy { it.permGroupName }
84                     value =
85                         if (reviewGroups.any { it.value.isReviewRequired }) reviewGroups
86                         else emptyMap()
87                 }
88             }
89         }
90 
91     fun isInitialized(): Boolean {
92         return permissionGroupsLiveData.isInitialized
93     }
94 
95     private fun shouldShowPermission(group: LightAppPermGroup): Boolean {
96         if (!(group.foreground.isGrantable || group.background.isGrantable)) {
97             return false
98         }
99         val isPlatformPermission = group.packageName == Utils.OS_PKG
100         // Show legacy permissions only if the user chose that.
101         return !(isPlatformPermission &&
102             !PermissionMapping.isPlatformPermissionGroup(group.permGroupName))
103     }
104 
105     fun isPackageUpdated(): Boolean {
106         val permGroupsMap: Map<String, LightAppPermGroup> = permissionGroupsLiveData.value!!
107         return permGroupsMap.any { !it.value.isReviewRequired }
108     }
109 
110     /**
111      * Update the summary of a permission group that has background permission. This does not apply
112      * to permission groups that are fixed by policy
113      */
114     fun getSummaryForPermGroupWithBackgroundPermission(state: PermissionTarget): PermissionSummary {
115         if (state != PermissionTarget.PERMISSION_NONE) {
116             if (
117                 state.and(PermissionTarget.PERMISSION_BACKGROUND) !=
118                     PermissionTarget.PERMISSION_NONE.value
119             ) {
120                 return SummaryMessage.ACCESS_ALWAYS.toPermSummary()
121             } else {
122                 return SummaryMessage.ACCESS_ONLY_FOREGROUND.toPermSummary()
123             }
124         } else {
125             return SummaryMessage.ACCESS_NEVER.toPermSummary()
126         }
127     }
128 
129     fun getSummaryForIndividuallyControlledPermGroup(
130         permGroup: LightAppPermGroup
131     ): PermissionSummary {
132         var revokedCount = 0
133         val lightPerms = permGroup.allPermissions.values.toList()
134         val permissionCount = lightPerms.size
135         for (i in 0 until permissionCount) {
136             if (!lightPerms[i].isGrantedIncludingAppOp) {
137                 revokedCount++
138             }
139         }
140         return when (revokedCount) {
141             0 -> {
142                 SummaryMessage.REVOKED_NONE.toPermSummary()
143             }
144             permissionCount -> {
145                 SummaryMessage.REVOKED_ALL.toPermSummary()
146             }
147             else -> {
148                 PermissionSummary(SummaryMessage.REVOKED_COUNT, false, revokedCount)
149             }
150         }
151     }
152 
153     /** Show all individual permissions in this group in a new fragment. */
154     fun showAllPermissions(fragment: Fragment, args: Bundle) {
155         val navController: NavController = NavHostFragment.findNavController(fragment)
156         navController.navigateSafe(R.id.app_to_all_perms, args)
157     }
158 
159     enum class SummaryMessage {
160         NO_SUMMARY,
161         DISABLED_BY_ADMIN,
162         ENABLED_BY_ADMIN,
163         ENABLED_SYSTEM_FIXED,
164         ENFORCED_BY_POLICY,
165         ENABLED_BY_ADMIN_FOREGROUND_ONLY,
166         ENABLED_BY_POLICY_FOREGROUND_ONLY,
167         ENABLED_BY_ADMIN_BACKGROUND_ONLY,
168         ENABLED_BY_POLICY_BACKGROUND_ONLY,
169         DISABLED_BY_ADMIN_BACKGROUND_ONLY,
170         DISABLED_BY_POLICY_BACKGROUND_ONLY,
171         REVOKED_NONE,
172         REVOKED_ALL,
173         REVOKED_COUNT,
174         ACCESS_ALWAYS,
175         ACCESS_ONLY_FOREGROUND,
176         ACCESS_NEVER;
177 
178         fun toPermSummary(): PermissionSummary {
179             return PermissionSummary(this, false)
180         }
181 
182         fun toPermSummary(isEnterprise: Boolean): PermissionSummary {
183             return PermissionSummary(this, isEnterprise)
184         }
185     }
186 
187     data class PermissionSummary(
188         val msg: SummaryMessage,
189         val isEnterprise: Boolean = false,
190         val revokeCount: Int = 0
191     )
192 
193     fun getSummaryForFixedByPolicyPermissionGroup(
194         mState: PermissionTarget,
195         permGroup: LightAppPermGroup,
196         context: Context
197     ): PermissionSummary {
198         val admin = getAdmin(context, permGroup)
199         val hasAdmin = admin != null
200         if (permGroup.isSystemFixed) {
201             // Permission is fully controlled by the system and cannot be switched
202             return SummaryMessage.ENABLED_SYSTEM_FIXED.toPermSummary()
203         } else if (isForegroundDisabledByPolicy(permGroup)) {
204             // Permission is fully controlled by policy and cannot be switched
205             return if (hasAdmin) {
206                 SummaryMessage.DISABLED_BY_ADMIN.toPermSummary()
207             } else {
208                 // Disabled state will be displayed by switch, so no need to add text for that
209                 SummaryMessage.ENFORCED_BY_POLICY.toPermSummary()
210             }
211         } else if (permGroup.isPolicyFullyFixed) {
212             // Permission is fully controlled by policy and cannot be switched
213             if (!permGroup.hasBackgroundGroup) {
214                 return if (hasAdmin) {
215                     SummaryMessage.ENABLED_BY_ADMIN.toPermSummary()
216                 } else {
217                     // Enabled state will be displayed by switch, so no need to add text for that
218                     SummaryMessage.ENFORCED_BY_POLICY.toPermSummary()
219                 }
220             } else {
221                 if (
222                     mState.and(PermissionTarget.PERMISSION_BACKGROUND) !=
223                         PermissionTarget.PERMISSION_NONE.value
224                 ) {
225                     return if (hasAdmin) {
226                         SummaryMessage.ENABLED_BY_ADMIN.toPermSummary()
227                     } else {
228                         // Enabled state will be displayed by switch, so no need to add text for
229                         // that
230                         SummaryMessage.ENFORCED_BY_POLICY.toPermSummary()
231                     }
232                 } else {
233                     return if (hasAdmin) {
234                         SummaryMessage.ENABLED_BY_ADMIN_FOREGROUND_ONLY.toPermSummary()
235                     } else {
236                         SummaryMessage.ENABLED_BY_POLICY_BACKGROUND_ONLY.toPermSummary()
237                     }
238                 }
239             }
240         } else {
241             // Part of the permission group can still be switched
242             if (permGroup.background.isPolicyFixed) {
243                 return if (
244                     mState.and(PermissionTarget.PERMISSION_BACKGROUND) !=
245                         PermissionTarget.PERMISSION_NONE.value
246                 ) {
247                     if (hasAdmin) {
248                         SummaryMessage.ENABLED_BY_ADMIN_BACKGROUND_ONLY.toPermSummary(true)
249                     } else {
250                         SummaryMessage.ENABLED_BY_POLICY_BACKGROUND_ONLY.toPermSummary()
251                     }
252                 } else {
253                     if (hasAdmin) {
254                         SummaryMessage.DISABLED_BY_ADMIN_BACKGROUND_ONLY.toPermSummary(true)
255                     } else {
256                         SummaryMessage.DISABLED_BY_POLICY_BACKGROUND_ONLY.toPermSummary()
257                     }
258                 }
259             } else if (permGroup.foreground.isPolicyFixed) {
260                 return if (hasAdmin) {
261                     SummaryMessage.ENABLED_BY_ADMIN_FOREGROUND_ONLY.toPermSummary(true)
262                 } else {
263                     SummaryMessage.ENABLED_BY_POLICY_FOREGROUND_ONLY.toPermSummary()
264                 }
265             }
266         }
267         return SummaryMessage.NO_SUMMARY.toPermSummary()
268     }
269 
270     /**
271      * Is the foreground part of this group disabled. If the foreground is disabled, there is no
272      * need to possible grant background access.
273      *
274      * @return `true` iff the permissions of this group are fixed
275      */
276     private fun isForegroundDisabledByPolicy(mGroup: LightAppPermGroup): Boolean {
277         return mGroup.foreground.isPolicyFixed && !mGroup.isGranted
278     }
279 
280     /** Whether policy is system fixed or fully fixed or foreground disabled */
281     fun isFixedOrForegroundDisabled(mGroup: LightAppPermGroup): Boolean {
282         return mGroup.isSystemFixed ||
283             mGroup.isPolicyFullyFixed ||
284             isForegroundDisabledByPolicy(mGroup)
285     }
286 
287     /**
288      * Get the app that acts as admin for this profile.
289      *
290      * @return The admin or `null` if there is no admin.
291      */
292     fun getAdmin(context: Context, mGroup: LightAppPermGroup): EnforcedAdmin? {
293         return RestrictedLockUtils.getProfileOrDeviceOwner(context, mGroup.userHandle)
294     }
295 
296     enum class PermissionTarget(val value: Int) {
297         PERMISSION_NONE(0),
298         PERMISSION_FOREGROUND(1),
299         PERMISSION_BACKGROUND(2),
300         PERMISSION_BOTH(3);
301 
302         infix fun and(other: PermissionTarget): Int {
303             return value and other.value
304         }
305 
306         infix fun and(other: Int): Int {
307             return value and other
308         }
309 
310         infix fun or(other: PermissionTarget): Int {
311             return value or other.value
312         }
313 
314         companion object {
315             fun fromInt(value: Int) = values().first { it.value == value }
316         }
317     }
318 }
319 
320 class ReviewPermissionViewModelFactory(
321     private val app: Application,
322     private val packageInfo: PackageInfo
323 ) : ViewModelProvider.Factory {
createnull324     override fun <T : ViewModel> create(modelClass: Class<T>): T {
325         @Suppress("UNCHECKED_CAST")
326         return ReviewPermissionsViewModel(app, packageInfo = packageInfo) as T
327     }
328 }
329