1 /*
2  * 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.UserHandle
22 import androidx.lifecycle.LiveData
23 import com.android.permissioncontroller.PermissionControllerApplication
24 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo
25 import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo
26 import com.android.permissioncontroller.permission.utils.Utils
27 
28 /**
29  * A LiveData which tracks all app permission groups for a set of permission groups, either platform
30  * or custom, as well as the UI information related to each app permission group, and the permission
31  * group as a whole.
32  *
33  * @param app The current application
34  */
35 class PermGroupsPackagesUiInfoLiveData(
36     private val app: Application,
37     groupNamesLiveData: LiveData<List<String>>
38 ) : SmartUpdateMediatorLiveData<
39     @kotlin.jvm.JvmSuppressWildcards Map<String, PermGroupPackagesUiInfo?>>() {
40     private val SYSTEM_SHELL = "android.app.role.SYSTEM_SHELL"
41 
42     /**
43      * Map<permission group name, PermGroupUiLiveDatas>
44      */
45     private val permGroupPackagesLiveDatas = mutableMapOf<String,
46         SinglePermGroupPackagesUiInfoLiveData>()
47     private val allPackageData = mutableMapOf<String, PermGroupPackagesUiInfo?>()
48 
49     private lateinit var groupNames: List<String>
50 
51     init {
52         addSource(groupNamesLiveData) {
53             groupNames = it ?: emptyList()
54             update()
55             getPermGroupPackageLiveDatas()
56         }
57     }
58 
59     private fun getPermGroupPackageLiveDatas() {
60         val getLiveData = { groupName: String -> SinglePermGroupPackagesUiInfoLiveData[groupName] }
61         setSourcesToDifference(groupNames, permGroupPackagesLiveDatas, getLiveData)
62     }
63 
64     private fun isGranted(grantState: AppPermGroupUiInfo.PermGrantState): Boolean {
65         return grantState != AppPermGroupUiInfo.PermGrantState.PERMS_DENIED &&
66                 grantState != AppPermGroupUiInfo.PermGrantState.PERMS_ASK
67     }
68 
69     private fun createPermGroupPackageUiInfo(
70         groupName: String,
71         appPermGroups: Map<Pair<String, UserHandle>, AppPermGroupUiInfo>
72     ): PermGroupPackagesUiInfo {
73         var nonSystem = 0
74         var grantedNonSystem = 0
75         var userInteractedNonSystem = 0
76         var grantedSystem = 0
77         var userInteractedSystem = 0
78         var firstGrantedSystemPackageName: String? = null
79 
80         for ((packageUserPair, appPermGroup) in appPermGroups) {
81             if (!appPermGroup.shouldShow) {
82                 continue
83             }
84 
85             if (appPermGroup.isSystem) {
86                 if (isGranted(appPermGroup.permGrantState)) {
87                     if (grantedSystem == 0) {
88                         firstGrantedSystemPackageName = packageUserPair.first
89                     }
90                     grantedSystem++
91                     userInteractedSystem++
92                 } else if (appPermGroup.isUserSet) {
93                     userInteractedSystem++
94                 }
95             } else {
96                 nonSystem++
97 
98                 if (isGranted(appPermGroup.permGrantState)) {
99                     grantedNonSystem++
100                     userInteractedNonSystem++
101                 } else if (appPermGroup.isUserSet) {
102                     userInteractedNonSystem++
103                 }
104             }
105         }
106         val onlyShellGranted = grantedNonSystem == 0 && grantedSystem == 1 &&
107                 isPackageShell(firstGrantedSystemPackageName)
108         return PermGroupPackagesUiInfo(groupName, nonSystem, grantedNonSystem,
109                 userInteractedNonSystem, grantedSystem, userInteractedSystem, onlyShellGranted)
110     }
111 
112     private fun isPackageShell(packageName: String?): Boolean {
113         if (packageName == null) {
114             return false
115         }
116 
117         // This method is only called at most once per permission group, so no need to cache value
118         val roleManager = Utils.getSystemServiceSafe(PermissionControllerApplication.get(),
119             RoleManager::class.java)
120         return roleManager.getRoleHolders(SYSTEM_SHELL).contains(packageName)
121     }
122 
123     override fun onUpdate() {
124         /**
125          * Only update when either-
126          * We have a list of groups, and none have loaded their data, or
127          * All packages have loaded their data
128          */
129         val haveAllLiveDatas = groupNames.all { permGroupPackagesLiveDatas.contains(it) }
130         val allInitialized = permGroupPackagesLiveDatas.all { it.value.isInitialized }
131         for (groupName in groupNames) {
132             allPackageData[groupName] = if (haveAllLiveDatas && allInitialized) {
133                 permGroupPackagesLiveDatas[groupName]?.value?.let { uiInfo ->
134                     createPermGroupPackageUiInfo(groupName, uiInfo)
135                 }
136             } else {
137                 null
138             }
139         }
140         value = allPackageData.toMap()
141     }
142 }
143