• 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.service
18 
19 import android.content.pm.PackageManager
20 import android.os.Process
21 import android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED
22 import android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM
23 import androidx.core.util.Consumer
24 import androidx.lifecycle.Lifecycle
25 import androidx.lifecycle.LiveData
26 import androidx.lifecycle.Observer
27 import com.android.permissioncontroller.DumpableLog
28 import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto
29 import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
30 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
31 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
32 import com.android.permissioncontroller.permission.data.UserPackageInfosLiveData
33 import com.android.permissioncontroller.permission.data.get
34 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo
35 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
36 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
37 import com.android.permissioncontroller.permission.utils.IPC
38 import com.android.permissioncontroller.permission.utils.Utils
39 import kotlinx.coroutines.Dispatchers.IO
40 import kotlinx.coroutines.Dispatchers.Main
41 import kotlinx.coroutines.GlobalScope
42 import kotlinx.coroutines.async
43 import kotlinx.coroutines.launch
44 import kotlinx.coroutines.withTimeout
45 import java.util.function.IntConsumer
46 
47 /**
48  * A model for the PermissionControllerServiceImpl. Handles the data gathering for some methods of
49  * ServiceImpl, and supports retrieving data from LiveDatas.
50  */
51 class PermissionControllerServiceModel(private val service: PermissionControllerServiceImpl) {
52 
53     private val observedLiveDatas = mutableListOf<LiveData<*>>()
54 
55     /**
56      * *Must* be used instead of LiveData.observe, in order to allow the lifecycle state to
57      * be set to "started" correctly. If the liveData was inactive, create a no op observer, which
58      * will survive until the service goes inactive. Will remove the provided observer after one
59      * update (one non-stale update, in the case of a SmartUpdateMediatorLiveData).
60      *
61      * @param liveData The livedata we wish to observe
62      * @param onChangedFun The function we wish to be called upon livedata updates
63      * @param <T> The type of the livedata and observer
64      */
65     fun <T> observeAndCheckForLifecycleState(
66         liveData: LiveData<T>,
67         onChangedFun: (t: T?) -> Unit
68     ) {
69         GlobalScope.launch(Main.immediate) {
70 
71             if (service.lifecycle.currentState != Lifecycle.State.STARTED) {
72                 service.setLifecycleToStarted()
73             }
74 
75             if (!liveData.hasActiveObservers()) {
76                 observedLiveDatas.add(liveData)
77                 liveData.observe(service, Observer { })
78             }
79 
80             var updated = false
81             val observer = object : Observer<T> {
82                 override fun onChanged(data: T) {
83                     if (updated) {
84                         return
85                     }
86                     if ((liveData is SmartUpdateMediatorLiveData<T> && !liveData.isStale) ||
87                         liveData !is SmartUpdateMediatorLiveData<T>) {
88                         onChangedFun(data)
89                         liveData.removeObserver(this)
90                         updated = true
91                     }
92                 }
93             }
94 
95             if (liveData is SmartUpdateMediatorLiveData<T>) {
96                 liveData.observeStale(service, observer)
97             } else {
98                 liveData.observe(service, observer)
99             }
100         }
101     }
102 
103     /**
104      * Stop observing all currently observed liveDatas
105      */
106     fun removeObservers() {
107         GlobalScope.launch(Main.immediate) {
108             for (liveData in observedLiveDatas) {
109                 liveData.removeObservers(service)
110             }
111 
112             observedLiveDatas.clear()
113         }
114     }
115 
116     /**
117      * Counts the number of apps that have at least one of a provided list of permissions, subject
118      * to the options specified in flags. This data is gathered from a series of LiveData objects.
119      *
120      * @param permissionNames The list of permission names whose apps we want to count
121      * @param flags Flags specifying if we want to count system apps, and count only granted apps
122      * @param callback The callback our result will be returned to
123      */
124     fun onCountPermissionAppsLiveData(
125         permissionNames: List<String>,
126         flags: Int,
127         callback: IntConsumer
128     ) {
129         val packageInfosLiveData = UserPackageInfosLiveData[Process.myUserHandle()]
130         observeAndCheckForLifecycleState(packageInfosLiveData) { packageInfos ->
131             onPackagesLoadedForCountPermissionApps(permissionNames, flags, callback,
132                 packageInfos)
133         }
134     }
135 
136     /**
137      * Called upon receiving a list of packages which we want to filter by a list of permissions
138      * and flags. Observes the AppPermGroupUiInfoLiveData for every app, and, upon receiving a
139      * non-stale update, adds it to the count if it matches the permission list and flags. Will
140      * only use the first non-stale update, so if an app is updated after this update, but before
141      * execution is complete, the changes will not be reflected until the method is called again.
142      *
143      * @param permissionNames The list of permission names whose apps we want to count
144      * @param flags Flags specifying if we want to count system apps, and count only granted apps
145      * @param callback The callback our result will be returned to
146      * @param packageInfos The list of LightPackageInfos we want to filter and count
147      */
148     private fun onPackagesLoadedForCountPermissionApps(
149         permissionNames: List<String>,
150         flags: Int,
151         callback: IntConsumer,
152         packageInfos: List<LightPackageInfo>?
153     ) {
154         if (packageInfos == null) {
155             callback.accept(0)
156             return
157         }
158 
159         val countSystem = flags and COUNT_WHEN_SYSTEM != 0
160         val countOnlyGranted = flags and COUNT_ONLY_WHEN_GRANTED != 0
161 
162         // Store the group of all installed, runtime permissions in permissionNames
163         val permToGroup = mutableMapOf<String, String?>()
164         for (permName in permissionNames) {
165             val permInfo = try {
166                 service.packageManager.getPermissionInfo(permName, 0)
167             } catch (e: PackageManager.NameNotFoundException) {
168                 continue
169             }
170 
171             if (Utils.isPermissionDangerousInstalledNotRemoved(permInfo)) {
172                 permToGroup[permName] = Utils.getGroupOfPermission(permInfo)
173             }
174         }
175 
176         val uiLiveDatasPerPackage = mutableListOf<MutableSet<AppPermGroupUiInfoLiveData>>()
177         var numLiveDatas = 0
178         for ((packageName, _, requestedPermissions) in packageInfos) {
179             val packageUiLiveDatas = mutableSetOf<AppPermGroupUiInfoLiveData>()
180             for (permName in permToGroup.keys) {
181                 if (requestedPermissions.contains(permName)) {
182                     packageUiLiveDatas.add(AppPermGroupUiInfoLiveData[packageName,
183                         permToGroup[permName]!!, Process.myUserHandle()])
184                 }
185             }
186             if (packageUiLiveDatas.isNotEmpty()) {
187                 uiLiveDatasPerPackage.add(packageUiLiveDatas)
188                 numLiveDatas += packageUiLiveDatas.size
189             }
190         }
191 
192         if (numLiveDatas == 0) {
193             callback.accept(0)
194         }
195 
196         var packagesWithPermission = 0
197         var numPermAppsChecked = 0
198 
199         for (packageUiInfoLiveDatas in uiLiveDatasPerPackage) {
200             var packageAdded = false
201             // We don't need to check for new packages in between the updates of the ui info live
202             // datas, because this method is used primarily for UI, and there is inherent delay
203             // when calling this method, due to binder calls, so some staleness is acceptable
204             for (packageUiInfoLiveData in packageUiInfoLiveDatas) {
205                 observeAndCheckForLifecycleState(packageUiInfoLiveData) { uiInfo ->
206                     numPermAppsChecked++
207 
208                     if (uiInfo != null && uiInfo.shouldShow && (!uiInfo.isSystem || countSystem)) {
209                         val granted = uiInfo.permGrantState != PermGrantState.PERMS_DENIED &&
210                             uiInfo.permGrantState != PermGrantState.PERMS_ASK
211                         if (granted || !countOnlyGranted && !packageAdded) {
212                             // The permission might not be granted, but some permissions of the
213                             // group are granted. In this case the permission is granted silently
214                             // when the app asks for it.
215                             // Hence this is as-good-as-granted and we count it.
216                             packageAdded = true
217                             packagesWithPermission++
218                         }
219                     }
220 
221                     if (numPermAppsChecked == numLiveDatas) {
222                         callback.accept(packagesWithPermission)
223                     }
224                 }
225             }
226         }
227     }
228 
229     /**
230      * Gets a list of the runtime permission groups which a package requests, and the UI information
231      * about those groups. Will only use the first non-stale data for each group, so if an app is
232      * updated after this update, but before execution is complete, the changes will not be
233      * reflected until the method is called again.
234      *
235      * @param packageName The package whose permission information we want
236      * @param callback The callback which will accept the list of <group name, group UI info> pairs
237      */
238     fun onGetAppPermissions(
239         packageName: String,
240         callback: Consumer<List<Pair<String, AppPermGroupUiInfo>>>
241     ) {
242         val packageGroupsLiveData = PackagePermissionsLiveData[packageName,
243             Process.myUserHandle()]
244         observeAndCheckForLifecycleState(packageGroupsLiveData) { groups ->
245             val groupNames = groups?.keys?.toMutableList() ?: mutableListOf()
246             groupNames.remove(PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS)
247             val uiInfos = mutableListOf<Pair<String, AppPermGroupUiInfo>>()
248             if (groupNames.isEmpty()) {
249                 callback.accept(uiInfos)
250             }
251             var numLiveDatasUpdated = 0
252 
253             for (groupName in groupNames) {
254                 // We don't need to check for new packages in between the updates of the ui info
255                 // live datas, because this method is used primarily for UI, and there is inherent
256                 // delay when calling this method, due to binder calls, so some staleness is
257                 // acceptable
258                 val uiInfoLiveData = AppPermGroupUiInfoLiveData[packageName, groupName,
259                     Process.myUserHandle()]
260                 observeAndCheckForLifecycleState(uiInfoLiveData) { uiInfo ->
261                     numLiveDatasUpdated++
262 
263                     uiInfo?.let {
264                         if (uiInfo.shouldShow) {
265                             uiInfos.add(groupName to uiInfo)
266                         }
267                     }
268 
269                     if (numLiveDatasUpdated == groupNames.size) {
270                         callback.accept(uiInfos)
271                     }
272                 }
273             }
274         }
275     }
276 
277     /**
278      * Dump state of the permission controller service
279      *
280      * @return the dump state as a proto
281      */
282     suspend fun onDump(): PermissionControllerDumpProto {
283         // Timeout is less than the timeout used by dumping (10 s)
284         return withTimeout(9000) {
285             val autoRevokeDump = GlobalScope.async(IPC) { dumpAutoRevokePermissions(service) }
286             val dumpedLogs = GlobalScope.async(IO) { DumpableLog.get() }
287 
288             PermissionControllerDumpProto.newBuilder()
289                     .setAutoRevoke(autoRevokeDump.await())
290                     .addAllLogs(dumpedLogs.await())
291                     .build()
292         }
293     }
294 }
295