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