1 /* 2 * 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 */ 1036 suspend fun <T, LD : LiveData<T>> LD.getInitializedValue( 1037 observe: LD.(Observer<T>) -> Unit = { observeForever(it) }, 1038 isInitialized: LD.() -> Boolean = { value != null } 1039 ): T { 1040 return if (isInitialized()) { 1041 value as T 1042 } else { 1043 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 */ 1069 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 */ 1080 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 */ 1095 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 }