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 }