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.utils
18 
19 import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
20 import android.Manifest.permission.ACCESS_FINE_LOCATION
21 import android.app.ActivityManager
22 import android.app.AppOpsManager
23 import android.app.AppOpsManager.MODE_ALLOWED
24 import android.app.AppOpsManager.MODE_FOREGROUND
25 import android.app.AppOpsManager.MODE_IGNORED
26 import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
27 import android.app.AppOpsManager.permissionToOp
28 import android.app.Application
29 import android.content.Context
30 import android.content.Intent
31 import android.content.Intent.ACTION_MAIN
32 import android.content.Intent.CATEGORY_INFO
33 import android.content.Intent.CATEGORY_LAUNCHER
34 import android.content.pm.PackageManager
35 import android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED
36 import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME
37 import android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
38 import android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
39 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
40 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
41 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
42 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
43 import android.content.pm.PermissionGroupInfo
44 import android.content.pm.PermissionInfo
45 import android.graphics.drawable.Drawable
46 import android.os.Build
47 import android.os.Bundle
48 import android.os.UserHandle
49 import android.text.TextUtils
50 import androidx.lifecycle.LiveData
51 import androidx.lifecycle.Observer
52 import androidx.navigation.NavController
53 import androidx.preference.Preference
54 import androidx.preference.PreferenceGroup
55 import com.android.permissioncontroller.R
56 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
57 import com.android.permissioncontroller.permission.data.get
58 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
59 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
60 import com.android.permissioncontroller.permission.model.livedatatypes.PermState
61 import com.android.permissioncontroller.permission.service.LocationAccessCheck
62 import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader
63 import kotlinx.coroutines.CoroutineScope
64 import kotlinx.coroutines.Dispatchers
65 import kotlinx.coroutines.GlobalScope
66 import kotlinx.coroutines.async
67 import kotlinx.coroutines.launch
68 import java.util.concurrent.atomic.AtomicReference
69 import kotlin.coroutines.Continuation
70 import kotlin.coroutines.CoroutineContext
71 import kotlin.coroutines.resume
72 import kotlin.coroutines.suspendCoroutine
73 
74 /**
75  * A set of util functions designed to work with kotlin, though they can work with java, as well.
76  */
77 object KotlinUtils {
78 
79     private const val PERMISSION_CONTROLLER_CHANGED_FLAG_MASK = FLAG_PERMISSION_USER_SET or
80         FLAG_PERMISSION_USER_FIXED or
81         FLAG_PERMISSION_ONE_TIME or
82         FLAG_PERMISSION_REVOKED_COMPAT or
83         FLAG_PERMISSION_ONE_TIME or
84         FLAG_PERMISSION_REVIEW_REQUIRED or
85         FLAG_PERMISSION_AUTO_REVOKED
86 
87     private const val KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"
88 
89     /**
90      * Given a Map, and a List, determines which elements are in the list, but not the map, and
91      * vice versa. Used primarily for determining which liveDatas are already being watched, and
92      * which need to be removed or added
93      *
94      * @param oldValues A map of key type K, with any value type
95      * @param newValues A list of type K
96      *
97      * @return A pair, where the first value is all items in the list, but not the map, and the
98      * second is all keys in the map, but not the list
99      */
100     fun <K> getMapAndListDifferences(
101         newValues: Collection<K>,
102         oldValues: Map<K, *>
103     ): Pair<Set<K>, Set<K>> {
104         val mapHas = oldValues.keys.toMutableSet()
105         val listHas = newValues.toMutableSet()
106         for (newVal in newValues) {
107             if (oldValues.containsKey(newVal)) {
108                 mapHas.remove(newVal)
109                 listHas.remove(newVal)
110             }
111         }
112         return listHas to mapHas
113     }
114 
115     /**
116      * Sort a given PreferenceGroup by the given comparison function.
117      *
118      * @param compare The function comparing two preferences, which will be used to sort
119      * @param hasHeader Whether the group contains a LargeHeaderPreference, which will be kept at
120      * the top of the list
121      */
122     fun sortPreferenceGroup(
123         group: PreferenceGroup,
124         compare: (lhs: Preference, rhs: Preference) -> Int,
125         hasHeader: Boolean
126     ) {
127         val preferences = mutableListOf<Preference>()
128         for (i in 0 until group.preferenceCount) {
129             preferences.add(group.getPreference(i))
130         }
131 
132         if (hasHeader) {
133             preferences.sortWith(Comparator { lhs, rhs ->
134                 if (lhs is SettingsWithLargeHeader.LargeHeaderPreference) {
135                     -1
136                 } else if (rhs is SettingsWithLargeHeader.LargeHeaderPreference) {
137                     1
138                 } else {
139                     compare(lhs, rhs)
140                 }
141             })
142         } else {
143             preferences.sortWith(Comparator(compare))
144         }
145 
146         for (i in 0 until preferences.size) {
147             preferences[i].order = i
148         }
149     }
150 
151     /**
152      * Gets a permission group's icon from the system.
153      *
154      * @param context The context from which to get the icon
155      * @param groupName The name of the permission group whose icon we want
156      *
157      * @return The permission group's icon, the ic_perm_device_info icon if the group has no icon,
158      * or the group does not exist
159      */
160     fun getPermGroupIcon(context: Context, groupName: String): Drawable? {
161         val groupInfo = Utils.getGroupInfo(groupName, context)
162         var icon: Drawable? = null
163         if (groupInfo != null && groupInfo.icon != 0) {
164             icon = Utils.loadDrawable(context.packageManager, groupInfo.packageName,
165                 groupInfo.icon)
166         }
167 
168         if (icon == null) {
169             icon = context.getDrawable(R.drawable.ic_perm_device_info)
170         }
171 
172         return Utils.applyTint(context, icon, android.R.attr.colorControlNormal)
173     }
174 
175     /**
176      * Gets a permission group's label from the system.
177      *
178      * @param context The context from which to get the label
179      * @param groupName The name of the permission group whose label we want
180      *
181      * @return The permission group's label, or the group name, if the group is invalid
182      */
183     fun getPermGroupLabel(context: Context, groupName: String): CharSequence {
184         val groupInfo = Utils.getGroupInfo(groupName, context) ?: return groupName
185         return groupInfo.loadSafeLabel(context.packageManager, 0f,
186             TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM)
187     }
188 
189     /**
190      * Gets a permission group's description from the system.
191      *
192      * @param context The context from which to get the description
193      * @param groupName The name of the permission group whose description we want
194      *
195      * @return The permission group's description, or an empty string, if the group is invalid, or
196      * its description does not exist
197      */
198     fun getPermGroupDescription(context: Context, groupName: String): CharSequence {
199         val groupInfo = Utils.getGroupInfo(groupName, context)
200         var description: CharSequence = ""
201 
202         if (groupInfo is PermissionGroupInfo) {
203             description = groupInfo.loadDescription(context.packageManager) ?: groupName
204         } else if (groupInfo is PermissionInfo) {
205             description = groupInfo.loadDescription(context.packageManager) ?: groupName
206         }
207         return description
208     }
209 
210     /**
211      * Gets a permission's label from the system.
212      * @param context The context from which to get the label
213      * @param permName The name of the permission whose label we want
214      *
215      * @return The permission's label, or the permission name, if the permission is invalid
216      */
217     fun getPermInfoLabel(context: Context, permName: String): CharSequence {
218         return try {
219             context.packageManager.getPermissionInfo(permName, 0).loadSafeLabel(
220                 context.packageManager, 20000.toFloat(), TextUtils.SAFE_STRING_FLAG_TRIM)
221         } catch (e: PackageManager.NameNotFoundException) {
222             permName
223         }
224     }
225 
226     /**
227      * Gets a permission's icon from the system.
228      * @param context The context from which to get the icon
229      * @param permName The name of the permission whose icon we want
230      *
231      * @return The permission's icon, or the permission's group icon if the icon isn't set, or
232      * the ic_perm_device_info icon if the permission is invalid.
233      */
234     fun getPermInfoIcon(context: Context, permName: String): Drawable? {
235         return try {
236             val permInfo = context.packageManager.getPermissionInfo(permName, 0)
237             var icon: Drawable? = null
238             if (permInfo.icon != 0) {
239                 icon = Utils.applyTint(context, permInfo.loadUnbadgedIcon(context.packageManager),
240                     android.R.attr.colorControlNormal)
241             }
242 
243             if (icon == null) {
244                 val groupName = Utils.getGroupOfPermission(permInfo) ?: permInfo.name
245                 icon = getPermGroupIcon(context, groupName)
246             }
247 
248             icon
249         } catch (e: PackageManager.NameNotFoundException) {
250             Utils.applyTint(context, context.getDrawable(R.drawable.ic_perm_device_info),
251                 android.R.attr.colorControlNormal)
252         }
253     }
254 
255     /**
256      * Gets a permission's description from the system.
257      *
258      * @param context The context from which to get the description
259      * @param permName The name of the permission whose description we want
260      *
261      * @return The permission's description, or an empty string, if the group is invalid, or
262      * its description does not exist
263      */
264     fun getPermInfoDescription(context: Context, permName: String): CharSequence {
265         return try {
266             val permInfo = context.packageManager.getPermissionInfo(permName, 0)
267             permInfo.loadDescription(context.packageManager) ?: ""
268         } catch (e: PackageManager.NameNotFoundException) {
269             ""
270         }
271     }
272 
273     /**
274      * Gets a package's badged icon from the system.
275      *
276      * @param app The current application
277      * @param packageName The name of the package whose icon we want
278      * @param user The user for whom we want the package icon
279      *
280      * @return The package's icon, or null, if the package does not exist
281      */
282     @JvmOverloads
283     fun getBadgedPackageIcon(
284         app: Application,
285         packageName: String,
286         user: UserHandle
287     ): Drawable? {
288         return try {
289             val userContext = Utils.getUserContext(app, user)
290             val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
291             Utils.getBadgedIcon(app, appInfo)
292         } catch (e: PackageManager.NameNotFoundException) {
293             null
294         }
295     }
296 
297     /**
298      * Gets a package's badged label from the system.
299      *
300      * @param app The current application
301      * @param packageName The name of the package whose label we want
302      * @param user The user for whom we want the package label
303      *
304      * @return The package's label
305      */
306     fun getPackageLabel(app: Application, packageName: String, user: UserHandle): String {
307         return try {
308             val userContext = Utils.getUserContext(app, user)
309             val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
310             Utils.getFullAppLabel(appInfo, app)
311         } catch (e: PackageManager.NameNotFoundException) {
312             packageName
313         }
314     }
315 
316     /**
317      * Gets a package's uid, using a cached liveData value, if the liveData is currently being
318      * observed (and thus has an up-to-date value).
319      *
320      * @param app The current application
321      * @param packageName The name of the package whose uid we want
322      * @param user The user we want the package uid for
323      *
324      * @return The package's UID, or null if the package or user is invalid
325      */
326     fun getPackageUid(app: Application, packageName: String, user: UserHandle): Int? {
327         val liveData = LightPackageInfoLiveData[packageName, user]
328         val liveDataUid = liveData.value?.uid
329         return if (liveDataUid != null && liveData.hasActiveObservers()) liveDataUid else {
330             val userContext = Utils.getUserContext(app, user)
331             try {
332                 val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
333                 appInfo.uid
334             } catch (e: PackageManager.NameNotFoundException) {
335                 null
336             }
337         }
338     }
339 
340     /**
341      * Determines if an app is R or above, or if it is Q-, and has auto revoke enabled
342      *
343      * @param app The currenct application
344      * @param packageName The package name to check
345      * @param user The user whose package we want to check
346      *
347      * @return true if the package is R+ (and not a work profile) or has auto revoke enabled
348      */
349     fun isROrAutoRevokeEnabled(app: Application, packageName: String, user: UserHandle): Boolean {
350         val userContext = Utils.getUserContext(app, user)
351         val liveDataValue = LightPackageInfoLiveData[packageName, user].value
352         val (targetSdk, uid) = if (liveDataValue != null) {
353             liveDataValue.targetSdkVersion to liveDataValue.uid
354         } else {
355             val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
356             appInfo.targetSdkVersion to appInfo.uid
357         }
358 
359         if (targetSdk <= Build.VERSION_CODES.Q) {
360             val opsManager = app.getSystemService(AppOpsManager::class.java)!!
361             return opsManager.unsafeCheckOpNoThrow(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid,
362                 packageName) == MODE_ALLOWED
363         }
364         return true
365     }
366 
367     /**
368      * Grant all foreground runtime permissions of a LightAppPermGroup
369      *
370      * <p>This also automatically grants all app ops for permissions that have app ops.
371      *
372      * @param app The current application
373      * @param group The group whose permissions should be granted
374      * @param filterPermissions If not specified, all permissions of the group will be granted.
375      *                          Otherwise only permissions in {@code filterPermissions} will be
376      *                          granted.
377      *
378      * @return a new LightAppPermGroup, reflecting the new state
379      */
380     @JvmOverloads
381     fun grantForegroundRuntimePermissions(
382         app: Application,
383         group: LightAppPermGroup,
384         filterPermissions: List<String> = group.permissions.keys.toList()
385     ): LightAppPermGroup {
386         return grantRuntimePermissions(app, group, false, filterPermissions)
387     }
388 
389     /**
390      * Grant all background runtime permissions of a LightAppPermGroup
391      *
392      * <p>This also automatically grants all app ops for permissions that have app ops.
393      *
394      * @param app The current application
395      * @param group The group whose permissions should be granted
396      * @param filterPermissions If not specified, all permissions of the group will be granted.
397      *                          Otherwise only permissions in {@code filterPermissions} will be
398      *                          granted.
399      *
400      * @return a new LightAppPermGroup, reflecting the new state
401      */
402     @JvmOverloads
403     fun grantBackgroundRuntimePermissions(
404         app: Application,
405         group: LightAppPermGroup,
406         filterPermissions: List<String> = group.permissions.keys.toList()
407     ): LightAppPermGroup {
408         return grantRuntimePermissions(app, group, true, filterPermissions)
409     }
410 
411     private fun grantRuntimePermissions(
412         app: Application,
413         group: LightAppPermGroup,
414         grantBackground: Boolean,
415         filterPermissions: List<String> = group.permissions.keys.toList()
416     ): LightAppPermGroup {
417         val newPerms = group.permissions.toMutableMap()
418         var shouldKillForAnyPermission = false
419         for (permName in filterPermissions) {
420             val perm = group.permissions[permName] ?: continue
421             val isBackgroundPerm = permName in group.backgroundPermNames
422             if (isBackgroundPerm == grantBackground) {
423                 val (newPerm, shouldKill) = grantRuntimePermission(app, perm, group)
424                 newPerms[newPerm.name] = newPerm
425                 shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
426             }
427         }
428 
429         if (shouldKillForAnyPermission) {
430             (app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
431                 group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
432         }
433         return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
434             group.hasInstallToRuntimeSplit, group.specialLocationGrant)
435     }
436 
437     /**
438      * Grants a single runtime permission
439      *
440      * @param app The current application
441      * @param perm The permission which should be granted.
442      * @param group An optional app permission group in which to look for background or foreground
443      * permissions
444      *
445      * @return a LightPermission and boolean pair <permission with updated state (or the original
446      * state, if it wasn't changed), should kill app>
447      */
448     private fun grantRuntimePermission(
449         app: Application,
450         perm: LightPermission,
451         group: LightAppPermGroup
452     ): Pair<LightPermission, Boolean> {
453         val user = UserHandle.getUserHandleForUid(group.packageInfo.uid)
454         val supportsRuntime = group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.M
455         val isGrantingAllowed = (!group.packageInfo.isInstantApp || perm.isInstantPerm) &&
456             (supportsRuntime || !perm.isRuntimeOnly)
457         // Do not touch permissions fixed by the system, or permissions that cannot be granted
458         if (!isGrantingAllowed || perm.isSystemFixed) {
459             return perm to false
460         }
461 
462         var newFlags = perm.flags
463         var isGranted = perm.isGrantedIncludingAppOp
464         var shouldKill = false
465 
466         // Grant the permission if needed.
467         if (!perm.isGrantedIncludingAppOp) {
468             val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission
469 
470             if (supportsRuntime) {
471                 app.packageManager.grantRuntimePermission(group.packageInfo.packageName, perm.name,
472                     user)
473                 isGranted = true
474             } else if (affectsAppOp) {
475                 // Legacy apps do not know that they have to retry access to a
476                 // resource due to changes in runtime permissions (app ops in this
477                 // case). Therefore, we restart them on app op change, so they
478                 // can pick up the change.
479                 shouldKill = true
480                 isGranted = true
481             }
482             newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
483 
484             // If this permission affects an app op, ensure the permission app op is enabled
485             // before the permission grant.
486             if (affectsAppOp) {
487                 allowAppOp(app, perm, group)
488             }
489         }
490 
491         // Granting a permission explicitly means the user already
492         // reviewed it so clear the review flag on every grant.
493         newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
494 
495         // Update the permission flags
496         // Now the apps can ask for the permission as the user
497         // no longer has it fixed in a denied state.
498         newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
499         newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
500         newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
501         newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
502 
503         // If we newly grant background access to the fine location, double-guess the user some
504         // time later if this was really the right choice.
505         if (!perm.isGrantedIncludingAppOp && isGranted) {
506             var triggerLocationAccessCheck = false
507             if (perm.name == ACCESS_FINE_LOCATION) {
508                 val bgPerm = group.permissions[perm.backgroundPermission]
509                 triggerLocationAccessCheck = bgPerm?.isGrantedIncludingAppOp == true
510             } else if (perm.name == ACCESS_BACKGROUND_LOCATION) {
511                 val fgPerm = group.permissions[ACCESS_FINE_LOCATION]
512                 triggerLocationAccessCheck = fgPerm?.isGrantedIncludingAppOp == true
513             }
514             if (triggerLocationAccessCheck) {
515                 // trigger location access check
516                 LocationAccessCheck(app, null).checkLocationAccessSoon()
517             }
518         }
519 
520         if (perm.flags != newFlags) {
521             app.packageManager.updatePermissionFlags(perm.name, group.packageInfo.packageName,
522                 PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, newFlags, user)
523         }
524 
525         val newState = PermState(newFlags, isGranted)
526         return LightPermission(perm.pkgInfo, perm.permInfo, newState,
527             perm.foregroundPerms) to shouldKill
528     }
529 
530     /**
531      * Revoke all foreground runtime permissions of a LightAppPermGroup
532      *
533      * <p>This also disallows all app ops for permissions that have app ops.
534      *
535      * @param app The current application
536      * @param group The group whose permissions should be revoked
537      * @param userFixed If the user requested that they do not want to be asked again
538      * @param oneTime If the permission should be mark as one-time
539      * @param filterPermissions If not specified, all permissions of the group will be revoked.
540      *                          Otherwise only permissions in {@code filterPermissions} will be
541      *                          revoked.
542      *
543      * @return a LightAppPermGroup representing the new state
544      */
545     @JvmOverloads
546     fun revokeForegroundRuntimePermissions(
547         app: Application,
548         group: LightAppPermGroup,
549         userFixed: Boolean = false,
550         oneTime: Boolean = false,
551         filterPermissions: List<String> = group.permissions.keys.toList()
552     ): LightAppPermGroup {
553         return revokeRuntimePermissions(app, group, false, userFixed, oneTime, filterPermissions)
554     }
555 
556     /**
557      * Revoke all background runtime permissions of a LightAppPermGroup
558      *
559      * <p>This also disallows all app ops for permissions that have app ops.
560      *
561      * @param app The current application
562      * @param group The group whose permissions should be revoked
563      * @param userFixed If the user requested that they do not want to be asked again
564      * @param filterPermissions If not specified, all permissions of the group will be revoked.
565      *                          Otherwise only permissions in {@code filterPermissions} will be
566      *                          revoked.
567      *
568      * @return a LightAppPermGroup representing the new state
569      */
570     @JvmOverloads
571     fun revokeBackgroundRuntimePermissions(
572         app: Application,
573         group: LightAppPermGroup,
574         userFixed: Boolean = false,
575         oneTime: Boolean = false,
576         filterPermissions: List<String> = group.permissions.keys.toList()
577     ): LightAppPermGroup {
578         return revokeRuntimePermissions(app, group, true, userFixed, oneTime, filterPermissions)
579     }
580 
581     private fun revokeRuntimePermissions(
582         app: Application,
583         group: LightAppPermGroup,
584         revokeBackground: Boolean,
585         userFixed: Boolean,
586         oneTime: Boolean,
587         filterPermissions: List<String>
588     ): LightAppPermGroup {
589         val newPerms = group.permissions.toMutableMap()
590         var shouldKillForAnyPermission = false
591         for (permName in filterPermissions) {
592             val perm = group.permissions[permName] ?: continue
593             val isBackgroundPerm = permName in group.backgroundPermNames
594             if (isBackgroundPerm == revokeBackground) {
595                 val (newPerm, shouldKill) =
596                     revokeRuntimePermission(app, perm, userFixed, oneTime, group)
597                 newPerms[newPerm.name] = newPerm
598                 shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
599             }
600         }
601 
602         if (shouldKillForAnyPermission) {
603             (app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
604                 group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
605         }
606         return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
607             group.hasInstallToRuntimeSplit, group.specialLocationGrant)
608     }
609 
610     /**
611      * Revokes a single runtime permission.
612      *
613      * @param app The current application
614      * @param perm The permission which should be revoked.
615      * @param userFixed If the user requested that they do not want to be asked again
616      * @param group An optional app permission group in which to look for background or foreground
617      * permissions
618      *
619      * @return a LightPermission and boolean pair <permission with updated state (or the original
620      * state, if it wasn't changed), should kill app>
621      */
622     private fun revokeRuntimePermission(
623         app: Application,
624         perm: LightPermission,
625         userFixed: Boolean,
626         oneTime: Boolean,
627         group: LightAppPermGroup
628     ): Pair<LightPermission, Boolean> {
629         // Do not touch permissions fixed by the system.
630         if (perm.isSystemFixed) {
631             return perm to false
632         }
633 
634         val supportsRuntime = group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.M
635         val user = UserHandle.getUserHandleForUid(group.packageInfo.uid)
636         var newFlags = perm.flags
637         var isGranted = perm.isGrantedIncludingAppOp
638         var shouldKill = false
639 
640         val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission
641 
642         if (perm.isGrantedIncludingAppOp) {
643             if (supportsRuntime) {
644                 // Revoke the permission if needed.
645                 app.packageManager.revokeRuntimePermission(group.packageInfo.packageName,
646                     perm.name, user)
647                 isGranted = false
648             } else if (affectsAppOp) {
649                 // If the permission has no corresponding app op, then it is a
650                 // third-party one and we do not offer toggling of such permissions.
651 
652                 // Disabling an app op may put the app in a situation in which it
653                 // has a handle to state it shouldn't have, so we have to kill the
654                 // app. This matches the revoke runtime permission behavior.
655                 shouldKill = true
656                 newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
657                 isGranted = false
658             }
659 
660             if (affectsAppOp) {
661                 disallowAppOp(app, perm, group)
662             }
663         }
664 
665         // Update the permission flags.
666         // Take a note that the user fixed the permission, if applicable.
667         newFlags = if (userFixed) newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
668         else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
669         newFlags = if (oneTime) newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_SET)
670         else newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
671         newFlags = if (oneTime) newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
672         else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
673         newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
674 
675         if (perm.flags != newFlags) {
676             app.packageManager.updatePermissionFlags(perm.name, group.packageInfo.packageName,
677                 PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, newFlags, user)
678         }
679 
680         val newState = PermState(newFlags, isGranted)
681         return LightPermission(perm.pkgInfo, perm.permInfo, newState,
682             perm.foregroundPerms) to shouldKill
683     }
684 
685     private fun Int.setFlag(flagToSet: Int): Int {
686         return this or flagToSet
687     }
688 
689     private fun Int.clearFlag(flagToSet: Int): Int {
690         return this and flagToSet.inv()
691     }
692 
693     /**
694      * Allow the app op for a permission/uid.
695      *
696      * <p>There are three cases:
697      * <dl>
698      * <dt>The permission is not split into foreground/background</dt>
699      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
700      * <dt>The permission is a foreground permission:</dt>
701      * <dd><dl><dt>The background permission permission is granted</dt>
702      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
703      * <dt>The background permission permission is <u>not</u> granted</dt>
704      * <dd>The app op matching the permission will be set to
705      * {@link AppOpsManager#MODE_FOREGROUND}</dd>
706      * </dl></dd>
707      * <dt>The permission is a background permission:</dt>
708      * <dd>All granted foreground permissions for this background permission will be set to
709      * {@link AppOpsManager#MODE_ALLOWED}</dd>
710      * </dl>
711      *
712      * @param app The current application
713      * @param perm The LightPermission whose app op should be allowed
714      * @param group The LightAppPermGroup which will be looked in for foreground or
715      * background LightPermission objects
716      *
717      * @return {@code true} iff app-op was changed
718      */
719     private fun allowAppOp(
720         app: Application,
721         perm: LightPermission,
722         group: LightAppPermGroup
723     ): Boolean {
724         val packageName = group.packageInfo.packageName
725         val uid = group.packageInfo.uid
726         val appOpsManager = app.getSystemService(AppOpsManager::class.java) as AppOpsManager
727         var wasChanged = false
728 
729         if (perm.isBackgroundPermission && perm.foregroundPerms != null) {
730             for (foregroundPermName in perm.foregroundPerms) {
731                 val fgPerm = group.permissions[foregroundPermName]
732                 val appOpName = permissionToOp(foregroundPermName) ?: continue
733 
734                 if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) {
735                     wasChanged = wasChanged || setOpMode(appOpName, uid, packageName, MODE_ALLOWED,
736                         appOpsManager)
737                 }
738             }
739         } else {
740             val appOpName = permissionToOp(perm.name) ?: return false
741             if (perm.backgroundPermission != null) {
742                 wasChanged = if (group.permissions.containsKey(perm.backgroundPermission)) {
743                     val bgPerm = group.permissions[perm.backgroundPermission]
744                     val mode = if (bgPerm != null && bgPerm.isGrantedIncludingAppOp) MODE_ALLOWED
745                     else MODE_FOREGROUND
746 
747                     setOpMode(appOpName, uid, packageName, mode, appOpsManager)
748                 } else {
749                     // The app requested a permission that has a background permission but it did
750                     // not request the background permission, hence it can never get background
751                     // access
752                     setOpMode(appOpName, uid, packageName, MODE_FOREGROUND, appOpsManager)
753                 }
754             } else {
755                 wasChanged = setOpMode(appOpName, uid, packageName, MODE_ALLOWED, appOpsManager)
756             }
757         }
758         return wasChanged
759     }
760 
761     /**
762      * Disallow the app op for a permission/uid.
763      *
764      * <p>There are three cases:
765      * <dl>
766      * <dt>The permission is not split into foreground/background</dt>
767      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
768      * <dt>The permission is a foreground permission:</dt>
769      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
770      * <dt>The permission is a background permission:</dt>
771      * <dd>All granted foreground permissions for this background permission will be set to
772      * {@link AppOpsManager#MODE_FOREGROUND}</dd>
773      * </dl>
774      *
775      * @param app The current application
776      * @param perm The LightPermission whose app op should be allowed
777      * @param group The LightAppPermGroup which will be looked in for foreground or
778      * background LightPermission objects
779      *
780      * @return {@code true} iff app-op was changed
781      */
782     private fun disallowAppOp(
783         app: Application,
784         perm: LightPermission,
785         group: LightAppPermGroup
786     ): Boolean {
787         val packageName = group.packageInfo.packageName
788         val uid = group.packageInfo.uid
789         val appOpsManager = app.getSystemService(AppOpsManager::class.java) as AppOpsManager
790         var wasChanged = false
791 
792         if (perm.isBackgroundPermission && perm.foregroundPerms != null) {
793             for (foregroundPermName in perm.foregroundPerms) {
794                 val fgPerm = group.permissions[foregroundPermName]
795                 if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) {
796                     val appOpName = permissionToOp(foregroundPermName) ?: return false
797                     wasChanged = wasChanged || setOpMode(appOpName, uid, packageName,
798                         MODE_FOREGROUND, appOpsManager)
799                 }
800             }
801         } else {
802             val appOpName = permissionToOp(perm.name) ?: return false
803             wasChanged = setOpMode(appOpName, uid, packageName, MODE_IGNORED, appOpsManager)
804         }
805         return wasChanged
806     }
807 
808     /**
809      * Set mode of an app-op if needed.
810      *
811      * @param op The op to set
812      * @param uid The uid the app-op belongs to
813      * @param packageName The package the app-op belongs to
814      * @param mode The new mode
815      * @param manager The app ops manager to use to change the app op
816      *
817      * @return {@code true} iff app-op was changed
818      */
819     private fun setOpMode(
820         op: String,
821         uid: Int,
822         packageName: String,
823         mode: Int,
824         manager: AppOpsManager
825     ): Boolean {
826         val currentMode = manager.unsafeCheckOpRaw(op, uid, packageName)
827         if (currentMode == mode) {
828             return false
829         }
830         manager.setUidMode(op, uid, mode)
831         return true
832     }
833 
834     /**
835      * Determine if a given package has a launch intent. Will function correctly even if called
836      * before user is unlocked.
837      *
838      * @param context: The context from which to retrieve the package
839      * @param packageName: The package name to check
840      *
841      * @return whether or not the given package has a launch intent
842      */
843     fun packageHasLaunchIntent(context: Context, packageName: String): Boolean {
844         val intentToResolve = Intent(ACTION_MAIN)
845         intentToResolve.addCategory(CATEGORY_INFO)
846         intentToResolve.setPackage(packageName)
847         var resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
848             MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
849 
850         if (resolveInfos == null || resolveInfos.size <= 0) {
851             intentToResolve.removeCategory(CATEGORY_INFO)
852             intentToResolve.addCategory(CATEGORY_LAUNCHER)
853             intentToResolve.setPackage(packageName)
854             resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
855                 MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
856         }
857         return resolveInfos != null && resolveInfos.size > 0
858     }
859 }
860 
861 /**
862  * Get the [value][LiveData.getValue], suspending until [isInitialized] if not yet so
863  */
getInitializedValuenull864 suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
865     observe: LD.(Observer<T>) -> Unit = { observeForever(it) },
<lambda>null866     isInitialized: LD.() -> Boolean = { value != null }
867 ): T {
868     return if (isInitialized()) {
869         value as T
870     } else {
continuationnull871         suspendCoroutine { continuation: Continuation<T> ->
872             val observer = AtomicReference<Observer<T>>()
873             observer.set(Observer { newValue ->
874                 if (isInitialized()) {
875                     GlobalScope.launch(Dispatchers.Main) {
876                         observer.getAndSet(null)?.let { observerSnapshot ->
877                             removeObserver(observerSnapshot)
878                             continuation.resume(newValue)
879                         }
880                     }
881                 }
882             })
883 
884             GlobalScope.launch(Dispatchers.Main) {
885                 observe(observer.get())
886             }
887         }
888     }
889 }
890 
891 /**
892  * A parallel equivalent of [map]
893  *
894  * Starts the given suspending function for each item in the collection without waiting for
895  * previous ones to complete, then suspends until all the started operations finish.
896  */
mapInParallelnull897 suspend inline fun <T, R> Iterable<T>.mapInParallel(
898     context: CoroutineContext,
899     scope: CoroutineScope = GlobalScope,
900     crossinline transform: suspend CoroutineScope.(T) -> R
901 ): List<R> = map { scope.async(context) { transform(it) } }.map { it.await() }
902 
903 /**
904  * A parallel equivalent of [forEach]
905  *
906  * See [mapInParallel]
907  */
forEachInParallelnull908 suspend inline fun <T> Iterable<T>.forEachInParallel(
909     context: CoroutineContext,
910     scope: CoroutineScope = GlobalScope,
911     crossinline action: suspend CoroutineScope.(T) -> Unit
912 ) {
913     mapInParallel(context, scope) { action(it) }
914 }
915 
916 /**
917  * Check that we haven't already started transitioning to a given destination. If we haven't,
918  * start navigating to that destination.
919  *
920  * @param destResId The ID of the desired destination
921  * @param args The optional bundle of args to be passed to the destination
922  */
navigateSafenull923 fun NavController.navigateSafe(destResId: Int, args: Bundle? = null) {
924     val navAction = currentDestination?.getAction(destResId) ?: graph.getAction(destResId)
925     navAction?.let { action ->
926         if (currentDestination?.id != action.destinationId) {
927             navigate(destResId, args)
928         }
929     }
930 }