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