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