• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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