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