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