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