1 /* <lambda>null2 * Copyright (C) 2019 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.data 18 19 import android.app.Application 20 import android.app.role.RoleManager 21 import android.os.Handler 22 import android.os.Looper 23 import android.os.UserHandle 24 import android.os.UserManager 25 import androidx.lifecycle.LiveData 26 import com.android.permissioncontroller.PermissionControllerApplication 27 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo 28 import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo 29 import com.android.permissioncontroller.permission.utils.Utils 30 31 /** 32 * A LiveData which tracks all app permission groups for a set of permission groups, either platform 33 * or custom, as well as the UI information related to each app permission group, and the permission 34 * group as a whole. 35 * 36 * @param app The current application 37 */ 38 class PermGroupsPackagesUiInfoLiveData( 39 private val app: Application, 40 private val groupNamesLiveData: LiveData<List<String>> 41 ) : 42 SmartUpdateMediatorLiveData< 43 @kotlin.jvm.JvmSuppressWildcards Map<String, PermGroupPackagesUiInfo?> 44 >() { 45 private val SYSTEM_SHELL = "android.app.role.SYSTEM_SHELL" 46 47 private val STAGGER_LOAD_TIME_MS = 50L 48 49 // Optimization: This LiveData relies on a large number of other ones. Enough that they can 50 // cause loading issues when they all become active at once. If this value is true, then it will 51 // slowly load data from all source LiveDatas, spacing loads them STAGGER_LOAD_TIME_MS apart 52 var loadStaggered: Boolean = false 53 54 // If we are returning from a particular permission group page, then that particular group is 55 // the one most likely to change. If so, then it will be prioritized in the load order. 56 var firstLoadGroup: String? = null 57 58 private val handler: Handler = Handler(Looper.getMainLooper()) 59 60 /** Map<permission group name, PermGroupUiLiveDatas> */ 61 private val permGroupPackagesLiveDatas = 62 mutableMapOf<String, SinglePermGroupPackagesUiInfoLiveData>() 63 private val allPackageData = mutableMapOf<String, PermGroupPackagesUiInfo?>() 64 65 private lateinit var groupNames: List<String> 66 private val userManager = 67 Utils.getSystemServiceSafe(app.applicationContext, UserManager::class.java) 68 69 init { 70 addSource(groupNamesLiveData) { 71 groupNames = it 72 update() 73 getPermGroupPackageLiveDatas() 74 } 75 } 76 77 private fun getPermGroupPackageLiveDatas() { 78 val getLiveData = { groupName: String -> SinglePermGroupPackagesUiInfoLiveData[groupName] } 79 setSourcesToDifference(groupNames, permGroupPackagesLiveDatas, getLiveData) 80 } 81 82 private fun isGranted(grantState: AppPermGroupUiInfo.PermGrantState): Boolean { 83 return grantState != AppPermGroupUiInfo.PermGrantState.PERMS_DENIED && 84 grantState != AppPermGroupUiInfo.PermGrantState.PERMS_ASK 85 } 86 87 private fun createPermGroupPackageUiInfo( 88 groupName: String, 89 appPermGroups: Map<Pair<String, UserHandle>, AppPermGroupUiInfo> 90 ): PermGroupPackagesUiInfo { 91 var nonSystem = 0 92 var grantedNonSystem = 0 93 var userInteractedNonSystem = 0 94 var grantedSystem = 0 95 var userInteractedSystem = 0 96 var firstGrantedSystemPackageName: String? = null 97 val showInSettingsByUsers = HashMap<UserHandle, Boolean>() 98 99 for ((packageUserPair, appPermGroup) in appPermGroups) { 100 if (!appPermGroup.shouldShow) { 101 continue 102 } 103 104 if (!showInSettingsByUsers.containsKey(packageUserPair.second)) { 105 showInSettingsByUsers[packageUserPair.second] = 106 Utils.shouldShowInSettings(packageUserPair.second, userManager) 107 } 108 109 if (showInSettingsByUsers[packageUserPair.second] == false) { 110 continue 111 } 112 113 if (appPermGroup.isSystem) { 114 if (isGranted(appPermGroup.permGrantState)) { 115 if (grantedSystem == 0) { 116 firstGrantedSystemPackageName = packageUserPair.first 117 } 118 grantedSystem++ 119 userInteractedSystem++ 120 } else if (appPermGroup.isUserSet) { 121 userInteractedSystem++ 122 } 123 } else { 124 nonSystem++ 125 126 if (isGranted(appPermGroup.permGrantState)) { 127 grantedNonSystem++ 128 userInteractedNonSystem++ 129 } else if (appPermGroup.isUserSet) { 130 userInteractedNonSystem++ 131 } 132 } 133 } 134 val onlyShellGranted = 135 grantedNonSystem == 0 && 136 grantedSystem == 1 && 137 isPackageShell(firstGrantedSystemPackageName) 138 return PermGroupPackagesUiInfo( 139 groupName, 140 nonSystem, 141 grantedNonSystem, 142 userInteractedNonSystem, 143 grantedSystem, 144 userInteractedSystem, 145 onlyShellGranted 146 ) 147 } 148 149 private fun isPackageShell(packageName: String?): Boolean { 150 if (packageName == null) { 151 return false 152 } 153 154 // This method is only called at most once per permission group, so no need to cache value 155 val roleManager = 156 Utils.getSystemServiceSafe( 157 PermissionControllerApplication.get(), 158 RoleManager::class.java 159 ) 160 return roleManager.getRoleHolders(SYSTEM_SHELL).contains(packageName) 161 } 162 163 override fun onUpdate() { 164 /** 165 * Only update when either- We have a list of groups, and none have loaded their data, or 166 * All packages have loaded their data 167 */ 168 val haveAllLiveDatas = groupNames.all { permGroupPackagesLiveDatas.contains(it) } 169 val allInitialized = permGroupPackagesLiveDatas.all { it.value.isInitialized } 170 for (groupName in groupNames) { 171 allPackageData[groupName] = 172 if (haveAllLiveDatas && allInitialized) { 173 permGroupPackagesLiveDatas[groupName]?.value?.let { uiInfo -> 174 createPermGroupPackageUiInfo(groupName, uiInfo) 175 } 176 } else { 177 null 178 } 179 } 180 value = allPackageData.toMap() 181 } 182 183 // Schedule a staggered loading of individual permission group livedatas 184 private fun scheduleStaggeredGroupLoad() { 185 if (groupNamesLiveData.value != null) { 186 if (firstLoadGroup in groupNames) { 187 addLiveDataDelayed(firstLoadGroup!!, 0) 188 } 189 for ((idx, groupName) in groupNames.withIndex()) { 190 if (groupName != firstLoadGroup) { 191 addLiveDataDelayed(groupName, idx * STAGGER_LOAD_TIME_MS) 192 } 193 } 194 } 195 } 196 197 private fun addLiveDataDelayed(groupName: String, delayTimeMs: Long) { 198 val liveData = SinglePermGroupPackagesUiInfoLiveData[groupName] 199 permGroupPackagesLiveDatas[groupName] = liveData 200 handler.postDelayed({ addSource(liveData) { update() } }, delayTimeMs) 201 } 202 203 override fun onActive() { 204 super.onActive() 205 if (loadStaggered && permGroupPackagesLiveDatas.isEmpty()) { 206 scheduleStaggeredGroupLoad() 207 } 208 } 209 210 override fun onInactive() { 211 super.onInactive() 212 if (loadStaggered) { 213 permGroupPackagesLiveDatas.forEach { (_, liveData) -> removeSource(liveData) } 214 permGroupPackagesLiveDatas.clear() 215 } 216 } 217 } 218