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