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