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