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 @file:Suppress("DEPRECATION")
17
18 package com.android.permissioncontroller.permission.utils
19
20 import android.Manifest
21 import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
22 import android.Manifest.permission.ACCESS_FINE_LOCATION
23 import android.Manifest.permission.BACKUP
24 import android.Manifest.permission.POST_NOTIFICATIONS
25 import android.Manifest.permission.READ_MEDIA_IMAGES
26 import android.Manifest.permission.READ_MEDIA_VIDEO
27 import android.Manifest.permission_group.NOTIFICATIONS
28 import android.app.ActivityManager
29 import android.app.AppOpsManager
30 import android.app.AppOpsManager.MODE_ALLOWED
31 import android.app.AppOpsManager.MODE_FOREGROUND
32 import android.app.AppOpsManager.MODE_IGNORED
33 import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
34 import android.app.AppOpsManager.permissionToOp
35 import android.app.Application
36 import android.content.Context
37 import android.content.Intent
38 import android.content.Intent.ACTION_MAIN
39 import android.content.Intent.CATEGORY_INFO
40 import android.content.Intent.CATEGORY_LAUNCHER
41 import android.content.pm.PackageManager
42 import android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED
43 import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME
44 import android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
45 import android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
46 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
47 import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
48 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
49 import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
50 import android.content.pm.PermissionGroupInfo
51 import android.content.pm.PermissionInfo
52 import android.content.pm.ResolveInfo
53 import android.content.res.Resources
54 import android.graphics.Bitmap
55 import android.graphics.Canvas
56 import android.graphics.drawable.Drawable
57 import android.graphics.drawable.Icon
58 import android.health.connect.HealthConnectManager
59 import android.os.Build
60 import android.os.Bundle
61 import android.os.UserHandle
62 import android.permission.PermissionManager
63 import android.provider.DeviceConfig
64 import android.provider.Settings
65 import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
66 import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
67 import android.text.Html
68 import android.text.TextUtils
69 import android.util.Log
70 import androidx.annotation.ChecksSdkIntAtLeast
71 import androidx.lifecycle.LiveData
72 import androidx.lifecycle.Observer
73 import androidx.navigation.NavController
74 import androidx.preference.Preference
75 import androidx.preference.PreferenceGroup
76 import com.android.modules.utils.build.SdkLevel
77 import com.android.permissioncontroller.DeviceUtils
78 import com.android.permissioncontroller.PermissionControllerApplication
79 import com.android.permissioncontroller.R
80 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
81 import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
82 import com.android.permissioncontroller.permission.data.get
83 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
84 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
85 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
86 import com.android.permissioncontroller.permission.model.livedatatypes.PermState
87 import com.android.permissioncontroller.permission.service.LocationAccessCheck
88 import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader
89 import com.android.safetycenter.resources.SafetyCenterResourcesContext
90 import java.time.Duration
91 import java.util.concurrent.atomic.AtomicReference
92 import kotlin.coroutines.Continuation
93 import kotlin.coroutines.CoroutineContext
94 import kotlin.coroutines.resume
95 import kotlin.coroutines.suspendCoroutine
96 import kotlinx.coroutines.CoroutineScope
97 import kotlinx.coroutines.Dispatchers
98 import kotlinx.coroutines.GlobalScope
99 import kotlinx.coroutines.async
100 import kotlinx.coroutines.launch
101
102 /**
103 * A set of util functions designed to work with kotlin, though they can work with java, as well.
104 */
105 object KotlinUtils {
106
107 private const val LOG_TAG = "PermissionController Utils"
108
109 private const val PERMISSION_CONTROLLER_CHANGED_FLAG_MASK = FLAG_PERMISSION_USER_SET or
110 FLAG_PERMISSION_USER_FIXED or
111 FLAG_PERMISSION_ONE_TIME or
112 FLAG_PERMISSION_REVOKED_COMPAT or
113 FLAG_PERMISSION_ONE_TIME or
114 FLAG_PERMISSION_REVIEW_REQUIRED or
115 FLAG_PERMISSION_AUTO_REVOKED
116
117 private const val KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"
118 private const val SAFETY_PROTECTION_RESOURCES_ENABLED = "safety_protection_enabled"
119
120 /**
121 * Importance level to define the threshold for whether a package is in a state which resets the
122 * timer on its one-time permission session
123 */
124 private val ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER =
125 ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
126
127 /**
128 * Importance level to define the threshold for whether a package is in a state which keeps its
129 * one-time permission session alive after the timer ends
130 */
131 private val ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
132 ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
133
134 /** Whether to show the Permissions Hub. */
135 private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"
136
137 /** Whether to show the mic and camera icons. */
138 private const val PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"
139
140 /** Whether to show the location indicators. */
141 private const val PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled"
142
143 /** Whether location accuracy feature is enabled */
144 private const val PROPERTY_LOCATION_ACCURACY_ENABLED = "location_accuracy_enabled"
145
146 /** Whether to show 7-day toggle in privacy hub. */
147 private const val PRIVACY_DASHBOARD_7_DAY_TOGGLE = "privacy_dashboard_7_day_toggle"
148
149 /** Default location precision */
150 private const val PROPERTY_LOCATION_PRECISION = "location_precision"
151
152 /** Whether to show the photo picker option in permission prompts. */
153 private const val PROPERTY_PHOTO_PICKER_PROMPT_ENABLED = "photo_picker_prompt_enabled"
154
155 /**
156 * The minimum amount of time to wait, after scheduling the safety label changes job, before
157 * the job actually runs for the first time.
158 */
159 private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_DELAY_MILLIS =
160 "safety_label_changes_job_delay_millis"
161
162 /** How often the safety label changes job service will run its job. */
163 private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS =
164 "safety_label_changes_job_interval_millis"
165
166 /** Whether the safety label changes job should only be run when the device is idle. */
167 private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_RUN_WHEN_IDLE =
168 "safety_label_changes_job_run_when_idle"
169
170 /** Whether the kill switch is set for [SafetyLabelChangesJobService]. */
171 private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_SERVICE_KILL_SWITCH =
172 "safety_label_changes_job_service_kill_switch"
173
174 /**
175 * Whether the Permissions Hub 2 flag is enabled
176 *
177 * @return whether the flag is enabled
178 */
179 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
180 fun isPermissionsHub2FlagEnabled(): Boolean {
181 return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
182 PROPERTY_PERMISSIONS_HUB_2_ENABLED, false)
183 }
184 /**
185 * Whether to show the Permissions Dashboard
186 *
187 * @return whether to show the Permissions Dashboard.
188 */
189 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
190 fun shouldShowPermissionsDashboard(): Boolean {
191 return isPermissionsHub2FlagEnabled()
192 }
193
194 /**
195 * Whether the Camera and Mic Icons are enabled by flag.
196 *
197 * @return whether the Camera and Mic Icons are enabled.
198 */
199 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
200 fun isCameraMicIconsFlagEnabled(): Boolean {
201 return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
202 PROPERTY_CAMERA_MIC_ICONS_ENABLED, true)
203 }
204
205 /**
206 * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the icons
207 * specifically, are enabled.
208 *
209 * @return whether to show the icons.
210 */
211 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
212 fun shouldShowCameraMicIndicators(): Boolean {
213 return isCameraMicIconsFlagEnabled() || isPermissionsHub2FlagEnabled()
214 }
215
216 /**
217 * Whether the location indicators are enabled by flag.
218 *
219 * @return whether the location indicators are enabled by flag.
220 */
221 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
222 fun isLocationIndicatorsFlagEnabled(): Boolean {
223 return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
224 PROPERTY_LOCATION_INDICATORS_ENABLED, false)
225 }
226
227 /**
228 * Whether to show the location indicators. The location indicators are enable if the
229 * permission hub, or location indicator specifically are enabled.
230 */
231 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
232 fun shouldShowLocationIndicators(): Boolean {
233 return isLocationIndicatorsFlagEnabled() || isPermissionsHub2FlagEnabled()
234 }
235
236 /**
237 * Whether the location accuracy feature is enabled
238 */
239 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
240 fun isLocationAccuracyEnabled(): Boolean {
241 return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
242 PROPERTY_LOCATION_ACCURACY_ENABLED, true)
243 }
244
245 /**
246 * Default state of location precision
247 * true: default is FINE.
248 * false: default is COARSE.
249 */
250 fun getDefaultPrecision(): Boolean {
251 return !SdkLevel.isAtLeastS() || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
252 PROPERTY_LOCATION_PRECISION, true)
253 }
254
255 /**
256 * Whether we should enable the 7-day toggle in privacy dashboard
257 *
258 * @return whether the flag is enabled
259 */
260 @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
261 fun is7DayToggleEnabled(): Boolean {
262 return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
263 PRIVACY_DASHBOARD_7_DAY_TOGGLE, false)
264 }
265
266 /**
267 * Whether the Photo Picker Prompt is enabled
268 *
269 * @return `true` iff the Location Access Check is enabled.
270 */
271 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
272 fun isPhotoPickerPromptEnabled(): Boolean {
273 val app = PermissionControllerApplication.get()
274 return SdkLevel.isAtLeastU() && !DeviceUtils.isAuto(app) &&
275 !DeviceUtils.isTelevision(app) && !DeviceUtils.isWear(app) &&
276 DeviceConfig.getBoolean(
277 DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PHOTO_PICKER_PROMPT_ENABLED, true)
278 }
279
280 /*
281 * Whether we should enable the permission rationale in permission settings and grant dialog
282 *
283 * @return whether the flag is enabled
284 */
285 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
286 fun isPermissionRationaleEnabled(): Boolean {
287 return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
288 PERMISSION_RATIONALE_ENABLED, true)
289 }
290
291 /**
292 * Whether we should enable the safety label change notifications and data sharing updates UI.
293 */
294 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
295 fun isSafetyLabelChangeNotificationsEnabled(context: Context): Boolean {
296 return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
297 SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true) &&
298 !DeviceUtils.isAuto(context) &&
299 !DeviceUtils.isTelevision(context) &&
300 !DeviceUtils.isWear(context)
301 }
302
303 /**
304 * Whether the kill switch is set for [SafetyLabelChangesJobService]. If {@code true}, the
305 * service is effectively disabled and will not run or schedule any jobs.
306 */
307 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
308 fun safetyLabelChangesJobServiceKillSwitch(): Boolean {
309 return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
310 PROPERTY_SAFETY_LABEL_CHANGES_JOB_SERVICE_KILL_SWITCH, false)
311 }
312
313 /** How often the safety label changes job will run. */
314 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
315 fun getSafetyLabelChangesJobIntervalMillis(): Long {
316 return DeviceConfig.getLong(
317 DeviceConfig.NAMESPACE_PRIVACY,
318 PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS,
319 Duration.ofDays(30).toMillis())
320 }
321
322 /** Whether the safety label changes job should only be run when the device is idle. */
323 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
324 fun runSafetyLabelChangesJobOnlyWhenDeviceIdle(): Boolean {
325 return DeviceConfig.getBoolean(
326 DeviceConfig.NAMESPACE_PRIVACY,
327 PROPERTY_SAFETY_LABEL_CHANGES_JOB_RUN_WHEN_IDLE,
328 true)
329 }
330
331 /**
332 * Given a Map, and a List, determines which elements are in the list, but not the map, and
333 * vice versa. Used primarily for determining which liveDatas are already being watched, and
334 * which need to be removed or added
335 *
336 * @param oldValues A map of key type K, with any value type
337 * @param newValues A list of type K
338 *
339 * @return A pair, where the first value is all items in the list, but not the map, and the
340 * second is all keys in the map, but not the list
341 */
342 fun <K> getMapAndListDifferences(
343 newValues: Collection<K>,
344 oldValues: Map<K, *>
345 ): Pair<Set<K>, Set<K>> {
346 val mapHas = oldValues.keys.toMutableSet()
347 val listHas = newValues.toMutableSet()
348 for (newVal in newValues) {
349 if (oldValues.containsKey(newVal)) {
350 mapHas.remove(newVal)
351 listHas.remove(newVal)
352 }
353 }
354 return listHas to mapHas
355 }
356
357 /**
358 * Sort a given PreferenceGroup by the given comparison function.
359 *
360 * @param compare The function comparing two preferences, which will be used to sort
361 * @param hasHeader Whether the group contains a LargeHeaderPreference, which will be kept at
362 * the top of the list
363 */
364 fun sortPreferenceGroup(
365 group: PreferenceGroup,
366 compare: (lhs: Preference, rhs: Preference) -> Int,
367 hasHeader: Boolean
368 ) {
369 val preferences = mutableListOf<Preference>()
370 for (i in 0 until group.preferenceCount) {
371 preferences.add(group.getPreference(i))
372 }
373
374 if (hasHeader) {
375 preferences.sortWith(Comparator { lhs, rhs ->
376 if (lhs is SettingsWithLargeHeader.LargeHeaderPreference) {
377 -1
378 } else if (rhs is SettingsWithLargeHeader.LargeHeaderPreference) {
379 1
380 } else {
381 compare(lhs, rhs)
382 }
383 })
384 } else {
385 preferences.sortWith(Comparator(compare))
386 }
387
388 for (i in 0 until preferences.size) {
389 preferences[i].order = i
390 }
391 }
392
393 /**
394 * Gets a permission group's icon from the system.
395 *
396 * @param context The context from which to get the icon
397 * @param groupName The name of the permission group whose icon we want
398 *
399 * @return The permission group's icon, the ic_perm_device_info icon if the group has no icon,
400 * or the group does not exist
401 */
402 @JvmOverloads
403 fun getPermGroupIcon(context: Context, groupName: String, tint: Int? = null): Drawable? {
404 val groupInfo = Utils.getGroupInfo(groupName, context)
405 var icon: Drawable? = null
406 if (groupInfo != null && groupInfo.icon != 0) {
407 icon = Utils.loadDrawable(context.packageManager, groupInfo.packageName,
408 groupInfo.icon)
409 }
410
411 if (icon == null) {
412 icon = context.getDrawable(R.drawable.ic_perm_device_info)
413 }
414
415 if (tint == null) {
416 return Utils.applyTint(context, icon, android.R.attr.colorControlNormal)
417 }
418
419 icon?.setTint(tint)
420 return icon
421 }
422
423 /**
424 * Gets a permission group's label from the system.
425 *
426 * @param context The context from which to get the label
427 * @param groupName The name of the permission group whose label we want
428 *
429 * @return The permission group's label, or the group name, if the group is invalid
430 */
431 fun getPermGroupLabel(context: Context, groupName: String): CharSequence {
432 val groupInfo = Utils.getGroupInfo(groupName, context) ?: return groupName
433 return groupInfo.loadSafeLabel(context.packageManager, 0f,
434 TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM)
435 }
436
437 /**
438 * Gets a permission group's description from the system.
439 *
440 * @param context The context from which to get the description
441 * @param groupName The name of the permission group whose description we want
442 *
443 * @return The permission group's description, or an empty string, if the group is invalid, or
444 * its description does not exist
445 */
446 fun getPermGroupDescription(context: Context, groupName: String): CharSequence {
447 val groupInfo = Utils.getGroupInfo(groupName, context)
448 var description: CharSequence = ""
449
450 if (groupInfo is PermissionGroupInfo) {
451 description = groupInfo.loadDescription(context.packageManager) ?: groupName
452 } else if (groupInfo is PermissionInfo) {
453 description = groupInfo.loadDescription(context.packageManager) ?: groupName
454 }
455 return description
456 }
457
458 /**
459 * Gets a permission's label from the system.
460 * @param context The context from which to get the label
461 * @param permName The name of the permission whose label we want
462 *
463 * @return The permission's label, or the permission name, if the permission is invalid
464 */
465 fun getPermInfoLabel(context: Context, permName: String): CharSequence {
466 return try {
467 context.packageManager.getPermissionInfo(permName, 0).loadSafeLabel(
468 context.packageManager, 20000.toFloat(), TextUtils.SAFE_STRING_FLAG_TRIM)
469 } catch (e: PackageManager.NameNotFoundException) {
470 permName
471 }
472 }
473
474 /**
475 * Gets a permission's icon from the system.
476 * @param context The context from which to get the icon
477 * @param permName The name of the permission whose icon we want
478 *
479 * @return The permission's icon, or the permission's group icon if the icon isn't set, or
480 * the ic_perm_device_info icon if the permission is invalid.
481 */
482 fun getPermInfoIcon(context: Context, permName: String): Drawable? {
483 return try {
484 val permInfo = context.packageManager.getPermissionInfo(permName, 0)
485 var icon: Drawable? = null
486 if (permInfo.icon != 0) {
487 icon = Utils.applyTint(context, permInfo.loadUnbadgedIcon(context.packageManager),
488 android.R.attr.colorControlNormal)
489 }
490
491 if (icon == null) {
492 val groupName = PermissionMapping.getGroupOfPermission(permInfo) ?: permInfo.name
493 icon = getPermGroupIcon(context, groupName)
494 }
495
496 icon
497 } catch (e: PackageManager.NameNotFoundException) {
498 Utils.applyTint(context, context.getDrawable(R.drawable.ic_perm_device_info),
499 android.R.attr.colorControlNormal)
500 }
501 }
502
503 /**
504 * Gets a permission's description from the system.
505 *
506 * @param context The context from which to get the description
507 * @param permName The name of the permission whose description we want
508 *
509 * @return The permission's description, or an empty string, if the group is invalid, or
510 * its description does not exist
511 */
512 fun getPermInfoDescription(context: Context, permName: String): CharSequence {
513 return try {
514 val permInfo = context.packageManager.getPermissionInfo(permName, 0)
515 permInfo.loadDescription(context.packageManager) ?: ""
516 } catch (e: PackageManager.NameNotFoundException) {
517 ""
518 }
519 }
520
521 /**
522 * Gets a package's badged icon from the system.
523 *
524 * @param app The current application
525 * @param packageName The name of the package whose icon we want
526 * @param user The user for whom we want the package icon
527 *
528 * @return The package's icon, or null, if the package does not exist
529 */
530 fun getBadgedPackageIcon(
531 app: Application,
532 packageName: String,
533 user: UserHandle
534 ): Drawable? {
535 return try {
536 val userContext = Utils.getUserContext(app, user)
537 val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
538 Utils.getBadgedIcon(app, appInfo)
539 } catch (e: PackageManager.NameNotFoundException) {
540 null
541 }
542 }
543
544 /**
545 * Gets a package's badged label from the system.
546 *
547 * @param app The current application
548 * @param packageName The name of the package whose label we want
549 * @param user The user for whom we want the package label
550 *
551 * @return The package's label
552 */
553 fun getPackageLabel(app: Application, packageName: String, user: UserHandle): String {
554 return try {
555 val userContext = Utils.getUserContext(app, user)
556 val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
557 Utils.getFullAppLabel(appInfo, app)
558 } catch (e: PackageManager.NameNotFoundException) {
559 packageName
560 }
561 }
562
563 fun convertToBitmap(pkgIcon: Drawable): Bitmap {
564 val pkgIconBmp = Bitmap.createBitmap(pkgIcon.intrinsicWidth, pkgIcon.intrinsicHeight,
565 Bitmap.Config.ARGB_8888)
566 // Draw the icon so it can be displayed.
567 val canvas = Canvas(pkgIconBmp)
568 pkgIcon.setBounds(0, 0, pkgIcon.intrinsicWidth, pkgIcon.intrinsicHeight)
569 pkgIcon.draw(canvas)
570 return pkgIconBmp
571 }
572
573 /**
574 * Gets a package's uid, using a cached liveData value, if the liveData is currently being
575 * observed (and thus has an up-to-date value).
576 *
577 * @param app The current application
578 * @param packageName The name of the package whose uid we want
579 * @param user The user we want the package uid for
580 *
581 * @return The package's UID, or null if the package or user is invalid
582 */
583 fun getPackageUid(app: Application, packageName: String, user: UserHandle): Int? {
584 val liveData = LightPackageInfoLiveData[packageName, user]
585 val liveDataUid = liveData.value?.uid
586 return if (liveDataUid != null && liveData.hasActiveObservers()) liveDataUid else {
587 val userContext = Utils.getUserContext(app, user)
588 try {
589 val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
590 appInfo.uid
591 } catch (e: PackageManager.NameNotFoundException) {
592 null
593 }
594 }
595 }
596
597 /**
598 * Return a specific MIME type, if a set of permissions is associated with one
599 */
600 fun getMimeTypeForPermissions(permissions: List<String>): String? {
601 if (permissions.contains(READ_MEDIA_IMAGES) && !permissions.contains(READ_MEDIA_VIDEO)) {
602 return "image/*"
603 }
604 if (permissions.contains(READ_MEDIA_VIDEO) && !permissions.contains(READ_MEDIA_IMAGES)) {
605 return "video/*"
606 }
607
608 return null
609 }
610
611 /**
612 * Determines if an app is R or above, or if it is Q-, and has auto revoke enabled
613 *
614 * @param app The currenct application
615 * @param packageName The package name to check
616 * @param user The user whose package we want to check
617 *
618 * @return true if the package is R+ (and not a work profile) or has auto revoke enabled
619 */
620 fun isROrAutoRevokeEnabled(app: Application, packageName: String, user: UserHandle): Boolean {
621 val userContext = Utils.getUserContext(app, user)
622 val liveDataValue = LightPackageInfoLiveData[packageName, user].value
623 val (targetSdk, uid) = if (liveDataValue != null) {
624 liveDataValue.targetSdkVersion to liveDataValue.uid
625 } else {
626 val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
627 appInfo.targetSdkVersion to appInfo.uid
628 }
629
630 if (targetSdk <= Build.VERSION_CODES.Q) {
631 val opsManager = app.getSystemService(AppOpsManager::class.java)!!
632 return opsManager.unsafeCheckOpNoThrow(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid,
633 packageName) == MODE_ALLOWED
634 }
635 return true
636 }
637
638 /**
639 * Determine if the given permission should be treated as split from a
640 * non-runtime permission for an application targeting the given SDK level.
641 */
642 private fun isPermissionSplitFromNonRuntime(
643 app: Application,
644 permName: String,
645 targetSdk: Int
646 ): Boolean {
647 val permissionManager = app.getSystemService(PermissionManager::class.java) ?: return false
648 val splitPerms = permissionManager.splitPermissions
649 val size = splitPerms.size
650 for (i in 0 until size) {
651 val splitPerm = splitPerms[i]
652 if (targetSdk < splitPerm.targetSdk && splitPerm.newPermissions.contains(permName)) {
653 val perm = app.packageManager.getPermissionInfo(splitPerm.splitPermission, 0)
654 return perm != null && perm.protection != PermissionInfo.PROTECTION_DANGEROUS
655 }
656 }
657 return false
658 }
659
660 /**
661 * Set a list of flags for a set of permissions of a LightAppPermGroup
662 *
663 * @param app: The current application
664 * @param group: The LightAppPermGroup whose permission flags we wish to set
665 * @param flags: Pairs of <FlagInt, ShouldSetFlag>
666 * @param filterPermissions: A list of permissions to filter by. Only the filtered permissions
667 * will be set
668 *
669 * @return A new LightAppPermGroup with the flags set.
670 */
671 fun setGroupFlags(
672 app: Application,
673 group: LightAppPermGroup,
674 vararg flags: Pair<Int, Boolean>,
675 filterPermissions: List<String> = group.permissions.keys.toList()
676 ): LightAppPermGroup {
677 var flagMask = 0
678 var flagsToSet = 0
679 for ((flag, shouldSet) in flags) {
680 flagMask = flagMask or flag
681 if (shouldSet) {
682 flagsToSet = flagsToSet or flag
683 }
684 }
685
686 val newPerms = mutableMapOf<String, LightPermission>()
687 for ((permName, perm) in group.permissions) {
688 if (permName !in filterPermissions) {
689 continue
690 }
691 // Check if flags need to be updated
692 if (flagMask and (perm.flags xor flagsToSet) != 0) {
693 app.packageManager.updatePermissionFlags(permName, group.packageName,
694 group.userHandle, *flags)
695 }
696 newPerms[permName] = LightPermission(group.packageInfo, perm.permInfo,
697 perm.isGrantedIncludingAppOp, perm.flags or flagsToSet, perm.foregroundPerms)
698 }
699 return LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
700 group.hasInstallToRuntimeSplit, group.specialLocationGrant)
701 }
702
703 /**
704 * Grant all foreground runtime permissions of a LightAppPermGroup
705 *
706 * <p>This also automatically grants all app ops for permissions that have app ops.
707 *
708 * @param app The current application
709 * @param group The group whose permissions should be granted
710 * @param filterPermissions If not specified, all permissions of the group will be granted.
711 * Otherwise only permissions in {@code filterPermissions} will be
712 * granted.
713 *
714 * @return a new LightAppPermGroup, reflecting the new state
715 */
716 @JvmOverloads
717 fun grantForegroundRuntimePermissions(
718 app: Application,
719 group: LightAppPermGroup,
720 filterPermissions: List<String> = group.permissions.keys.toList(),
721 isOneTime: Boolean = false,
722 userFixed: Boolean = false,
723 withoutAppOps: Boolean = false,
724 ): LightAppPermGroup {
725 return grantRuntimePermissions(app, group, false, isOneTime, userFixed,
726 withoutAppOps, filterPermissions)
727 }
728
729 /**
730 * Grant all background runtime permissions of a LightAppPermGroup
731 *
732 * <p>This also automatically grants all app ops for permissions that have app ops.
733 *
734 * @param app The current application
735 * @param group The group whose permissions should be granted
736 * @param filterPermissions If not specified, all permissions of the group will be granted.
737 * Otherwise only permissions in {@code filterPermissions} will be
738 * granted.
739 *
740 * @return a new LightAppPermGroup, reflecting the new state
741 */
742 @JvmOverloads
743 fun grantBackgroundRuntimePermissions(
744 app: Application,
745 group: LightAppPermGroup,
746 filterPermissions: List<String> = group.permissions.keys.toList()
747 ): LightAppPermGroup {
748 return grantRuntimePermissions(app, group, true, false, false, false,
749 filterPermissions)
750 }
751
752 private fun grantRuntimePermissions(
753 app: Application,
754 group: LightAppPermGroup,
755 grantBackground: Boolean,
756 isOneTime: Boolean = false,
757 userFixed: Boolean = false,
758 withoutAppOps: Boolean = false,
759 filterPermissions: List<String> = group.permissions.keys.toList(),
760 ): LightAppPermGroup {
761 val newPerms = group.permissions.toMutableMap()
762 var shouldKillForAnyPermission = false
763 for (permName in filterPermissions) {
764 val perm = group.permissions[permName] ?: continue
765 val isBackgroundPerm = permName in group.backgroundPermNames
766 if (isBackgroundPerm == grantBackground) {
767 val (newPerm, shouldKill) = grantRuntimePermission(app, perm, group, isOneTime,
768 userFixed, withoutAppOps)
769 newPerms[newPerm.name] = newPerm
770 shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
771 }
772 }
773 if (!newPerms.isEmpty()) {
774 val user = UserHandle.getUserHandleForUid(group.packageInfo.uid)
775 for (groupPerm in group.allPermissions.values) {
776 var permFlags = groupPerm.flags
777 permFlags = permFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
778 if (groupPerm.flags != permFlags) {
779 app.packageManager.updatePermissionFlags(groupPerm.name,
780 group.packageInfo.packageName, PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
781 permFlags, user)
782 }
783 }
784 }
785
786 if (shouldKillForAnyPermission) {
787 (app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
788 group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
789 }
790 val newGroup = LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
791 group.hasInstallToRuntimeSplit, group.specialLocationGrant)
792 // If any permission in the group is one time granted, start one time permission session.
793 if (newGroup.permissions.any { it.value.isOneTime && it.value.isGrantedIncludingAppOp }) {
794 if (SdkLevel.isAtLeastT()) {
795 app.getSystemService(PermissionManager::class.java)!!.startOneTimePermissionSession(
796 group.packageName, Utils.getOneTimePermissionsTimeout(),
797 Utils.getOneTimePermissionsKilledDelay(false),
798 ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
799 ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE)
800 } else {
801 app.getSystemService(PermissionManager::class.java)!!.startOneTimePermissionSession(
802 group.packageName, Utils.getOneTimePermissionsTimeout(),
803 ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
804 ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE)
805 }
806 }
807 return newGroup
808 }
809
810 /**
811 * Grants a single runtime permission
812 *
813 * @param app The current application
814 * @param perm The permission which should be granted.
815 * @param group An app permission group in which to look for background or foreground
816 * @param isOneTime Whether this is a one-time permission grant
817 * permissions
818 * @param userFixed Whether to mark the permissions as user fixed when granted
819 * @param withoutAppOps If these permission have app ops associated, and this value is true,
820 * then do not grant the app op when the permission is granted, and add the REVOKED_COMPAT flag.
821 *
822 * @return a LightPermission and boolean pair <permission with updated state (or the original
823 * state, if it wasn't changed), should kill app>
824 */
825 private fun grantRuntimePermission(
826 app: Application,
827 perm: LightPermission,
828 group: LightAppPermGroup,
829 isOneTime: Boolean,
830 userFixed: Boolean = false,
831 withoutAppOps: Boolean = false
832 ): Pair<LightPermission, Boolean> {
833 val pkgInfo = group.packageInfo
834 val user = UserHandle.getUserHandleForUid(pkgInfo.uid)
835 val supportsRuntime = pkgInfo.targetSdkVersion >= Build.VERSION_CODES.M
836 val isGrantingAllowed = (!pkgInfo.isInstantApp || perm.isInstantPerm) &&
837 (supportsRuntime || !perm.isRuntimeOnly)
838 // Do not touch permissions fixed by the system, or permissions that cannot be granted
839 if (!isGrantingAllowed || perm.isSystemFixed) {
840 return perm to false
841 }
842
843 var newFlags = perm.flags
844 var oldFlags = perm.flags
845 var isGranted = perm.isGrantedIncludingAppOp
846 var shouldKill = false
847
848 // Grant the permission if needed.
849 if (!perm.isGrantedIncludingAppOp) {
850 val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission
851
852 // TODO 195016052: investigate adding split permission handling
853 if (supportsRuntime) {
854 // If granting without app ops, explicitly disallow app op first, while setting the
855 // flag, so that the PermissionPolicyService doesn't reset the app op state
856 if (affectsAppOp && withoutAppOps) {
857 oldFlags = oldFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
858 app.packageManager.updatePermissionFlags(perm.name, group.packageName,
859 PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, oldFlags, user)
860 disallowAppOp(app, perm, group)
861 }
862 app.packageManager.grantRuntimePermission(group.packageName, perm.name, user)
863 isGranted = true
864 } else if (affectsAppOp) {
865 // Legacy apps do not know that they have to retry access to a
866 // resource due to changes in runtime permissions (app ops in this
867 // case). Therefore, we restart them on app op change, so they
868 // can pick up the change.
869 shouldKill = true
870 isGranted = true
871 }
872 newFlags = if (affectsAppOp && withoutAppOps) {
873 newFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
874 } else {
875 newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
876 }
877 newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)
878
879 // If this permission affects an app op, ensure the permission app op is enabled
880 // before the permission grant.
881 if (affectsAppOp && !withoutAppOps) {
882 allowAppOp(app, perm, group)
883 }
884 }
885
886 // Granting a permission explicitly means the user already
887 // reviewed it so clear the review flag on every grant.
888 newFlags = newFlags.clearFlag(FLAG_PERMISSION_REVIEW_REQUIRED)
889
890 // Update the permission flags
891 if (!withoutAppOps && !userFixed) {
892 // Now the apps can ask for the permission as the user
893 // no longer has it fixed in a denied state.
894 newFlags = newFlags.clearFlag(FLAG_PERMISSION_USER_FIXED)
895 newFlags = newFlags.setFlag(FLAG_PERMISSION_USER_SET)
896 } else if (userFixed) {
897 newFlags = newFlags.setFlag(FLAG_PERMISSION_USER_FIXED)
898 }
899 newFlags = newFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
900
901 newFlags = if (isOneTime) {
902 newFlags.setFlag(FLAG_PERMISSION_ONE_TIME)
903 } else {
904 newFlags.clearFlag(FLAG_PERMISSION_ONE_TIME)
905 }
906
907 // If we newly grant background access to the fine location, double-guess the user some
908 // time later if this was really the right choice.
909 if (!perm.isGrantedIncludingAppOp && isGranted) {
910 var triggerLocationAccessCheck = false
911 if (perm.name == ACCESS_FINE_LOCATION) {
912 val bgPerm = group.permissions[perm.backgroundPermission]
913 triggerLocationAccessCheck = bgPerm?.isGrantedIncludingAppOp == true
914 } else if (perm.name == ACCESS_BACKGROUND_LOCATION) {
915 val fgPerm = group.permissions[ACCESS_FINE_LOCATION]
916 triggerLocationAccessCheck = fgPerm?.isGrantedIncludingAppOp == true
917 }
918 if (triggerLocationAccessCheck) {
919 // trigger location access check
920 LocationAccessCheck(app, null).checkLocationAccessSoon()
921 }
922 }
923
924 if (oldFlags != newFlags) {
925 app.packageManager.updatePermissionFlags(perm.name, group.packageInfo.packageName,
926 PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, newFlags, user)
927 }
928
929 val newState = PermState(newFlags, isGranted)
930 return LightPermission(perm.pkgInfo, perm.permInfo, newState,
931 perm.foregroundPerms) to shouldKill
932 }
933
934 /**
935 * Revoke all foreground runtime permissions of a LightAppPermGroup
936 *
937 * <p>This also disallows all app ops for permissions that have app ops.
938 *
939 * @param app The current application
940 * @param group The group whose permissions should be revoked
941 * @param userFixed If the user requested that they do not want to be asked again
942 * @param oneTime If the permission should be mark as one-time
943 * @param filterPermissions If not specified, all permissions of the group will be revoked.
944 * Otherwise only permissions in {@code filterPermissions} will be
945 * revoked.
946 *
947 * @return a LightAppPermGroup representing the new state
948 */
949 @JvmOverloads
950 fun revokeForegroundRuntimePermissions(
951 app: Application,
952 group: LightAppPermGroup,
953 userFixed: Boolean = false,
954 oneTime: Boolean = false,
955 forceRemoveRevokedCompat: Boolean = false,
956 filterPermissions: List<String> = group.permissions.keys.toList()
957 ): LightAppPermGroup {
958 return revokeRuntimePermissions(app, group, false, userFixed, oneTime,
959 forceRemoveRevokedCompat, filterPermissions)
960 }
961
962 /**
963 * Revoke all background runtime permissions of a LightAppPermGroup
964 *
965 * <p>This also disallows all app ops for permissions that have app ops.
966 *
967 * @param app The current application
968 * @param group The group whose permissions should be revoked
969 * @param userFixed If the user requested that they do not want to be asked again
970 * @param filterPermissions If not specified, all permissions of the group will be revoked.
971 * Otherwise only permissions in {@code filterPermissions} will be
972 * revoked.
973 *
974 * @return a LightAppPermGroup representing the new state
975 */
976 @JvmOverloads
977 fun revokeBackgroundRuntimePermissions(
978 app: Application,
979 group: LightAppPermGroup,
980 userFixed: Boolean = false,
981 oneTime: Boolean = false,
982 forceRemoveRevokedCompat: Boolean = false,
983 filterPermissions: List<String> = group.permissions.keys.toList()
984 ): LightAppPermGroup {
985 return revokeRuntimePermissions(app, group, true, userFixed, oneTime,
986 forceRemoveRevokedCompat, filterPermissions)
987 }
988
989 private fun revokeRuntimePermissions(
990 app: Application,
991 group: LightAppPermGroup,
992 revokeBackground: Boolean,
993 userFixed: Boolean,
994 oneTime: Boolean,
995 forceRemoveRevokedCompat: Boolean = false,
996 filterPermissions: List<String>
997 ): LightAppPermGroup {
998 val wasOneTime = group.isOneTime
999 val newPerms = group.permissions.toMutableMap()
1000 var shouldKillForAnyPermission = false
1001 for (permName in filterPermissions) {
1002 val perm = group.permissions[permName] ?: continue
1003 val isBackgroundPerm = permName in group.backgroundPermNames
1004 if (isBackgroundPerm == revokeBackground) {
1005 val (newPerm, shouldKill) =
1006 revokeRuntimePermission(app, perm, userFixed, oneTime, forceRemoveRevokedCompat,
1007 group)
1008 newPerms[newPerm.name] = newPerm
1009 shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
1010 }
1011 }
1012
1013 if (shouldKillForAnyPermission && !shouldSkipKillForGroup(app, group)) {
1014 (app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
1015 group.packageInfo.uid, KILL_REASON_APP_OP_CHANGE)
1016 }
1017
1018 val newGroup = LightAppPermGroup(group.packageInfo, group.permGroupInfo, newPerms,
1019 group.hasInstallToRuntimeSplit, group.specialLocationGrant)
1020
1021 if (wasOneTime && !anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo, newGroup)) {
1022 app.getSystemService(PermissionManager::class.java)!!.stopOneTimePermissionSession(
1023 group.packageName)
1024 }
1025 return newGroup
1026 }
1027
1028 /**
1029 * Revoke background permissions
1030 *
1031 * @param context context
1032 * @param packageName Name of the package
1033 * @param permissionGroupName Name of the permission group
1034 * @param user User handle
1035 * @param postRevokeHandler Optional callback that lets us perform an action on revoke
1036 */
1037 fun revokeBackgroundRuntimePermissions(
1038 context: Context,
1039 packageName: String,
1040 permissionGroupName: String,
1041 user: UserHandle,
1042 postRevokeHandler: Runnable?
1043 ) {
1044 GlobalScope.launch(Dispatchers.Main) {
1045 val group = LightAppPermGroupLiveData[packageName, permissionGroupName, user]
1046 .getInitializedValue()
1047 if (group != null) {
1048 revokeBackgroundRuntimePermissions(context.application, group)
1049 }
1050 if (postRevokeHandler != null) {
1051 postRevokeHandler.run()
1052 }
1053 }
1054 }
1055
1056 /**
1057 * Determines if any permissions of a package are granted for one-time only
1058 *
1059 * @param app The current application
1060 * @param packageInfo The packageInfo we wish to examine
1061 * @param group Optional, the current app permission group we are examining
1062 *
1063 * @return true if any permission in the package is granted for one time, false otherwise
1064 */
1065 private fun anyPermsOfPackageOneTimeGranted(
1066 app: Application,
1067 packageInfo: LightPackageInfo,
1068 group: LightAppPermGroup? = null
1069 ): Boolean {
1070 val user = group?.userHandle ?: UserHandle.getUserHandleForUid(packageInfo.uid)
1071 if (group?.isOneTime == true) {
1072 return true
1073 }
1074 for ((idx, permName) in packageInfo.requestedPermissions.withIndex()) {
1075 if (permName in group?.permissions ?: emptyMap()) {
1076 continue
1077 }
1078 val flags = app.packageManager.getPermissionFlags(permName, packageInfo.packageName,
1079 user) and FLAG_PERMISSION_ONE_TIME
1080 val granted = packageInfo.requestedPermissionsFlags[idx] ==
1081 PackageManager.PERMISSION_GRANTED &&
1082 (flags and FLAG_PERMISSION_REVOKED_COMPAT) == 0
1083 if (granted && (flags and FLAG_PERMISSION_ONE_TIME) != 0) {
1084 return true
1085 }
1086 }
1087 return false
1088 }
1089 /**
1090 * Revokes a single runtime permission.
1091 *
1092 * @param app The current application
1093 * @param perm The permission which should be revoked.
1094 * @param userFixed If the user requested that they do not want to be asked again
1095 * @param group An optional app permission group in which to look for background or foreground
1096 * permissions
1097 *
1098 * @return a LightPermission and boolean pair <permission with updated state (or the original
1099 * state, if it wasn't changed), should kill app>
1100 */
1101 private fun revokeRuntimePermission(
1102 app: Application,
1103 perm: LightPermission,
1104 userFixed: Boolean,
1105 oneTime: Boolean,
1106 forceRemoveRevokedCompat: Boolean,
1107 group: LightAppPermGroup
1108 ): Pair<LightPermission, Boolean> {
1109 // Do not touch permissions fixed by the system.
1110 if (perm.isSystemFixed) {
1111 return perm to false
1112 }
1113
1114 val user = UserHandle.getUserHandleForUid(group.packageInfo.uid)
1115 var newFlags = perm.flags
1116 var isGranted = perm.isGrantedIncludingAppOp
1117 val supportsRuntime = group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.M
1118 var shouldKill = false
1119
1120 val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission
1121
1122 if (perm.isGrantedIncludingAppOp || (perm.isCompatRevoked && forceRemoveRevokedCompat)) {
1123 if (supportsRuntime && !isPermissionSplitFromNonRuntime(app, perm.name,
1124 group.packageInfo.targetSdkVersion)) {
1125 // Revoke the permission if needed.
1126 app.packageManager.revokeRuntimePermission(group.packageInfo.packageName,
1127 perm.name, user)
1128 isGranted = false
1129 if (forceRemoveRevokedCompat) {
1130 newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
1131 }
1132 } else if (affectsAppOp) {
1133 // If the permission has no corresponding app op, then it is a
1134 // third-party one and we do not offer toggling of such permissions.
1135
1136 // Disabling an app op may put the app in a situation in which it
1137 // has a handle to state it shouldn't have, so we have to kill the
1138 // app. This matches the revoke runtime permission behavior.
1139 shouldKill = true
1140 newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
1141 isGranted = false
1142 }
1143
1144 newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)
1145 if (affectsAppOp) {
1146 disallowAppOp(app, perm, group)
1147 }
1148 }
1149
1150 // Update the permission flags.
1151 // Take a note that the user fixed the permission, if applicable.
1152 newFlags = if (userFixed) newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
1153 else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
1154 newFlags = if (oneTime) newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_SET)
1155 else newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
1156 newFlags = if (oneTime) newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
1157 else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
1158 newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
1159 newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
1160
1161 if (perm.flags != newFlags) {
1162 app.packageManager.updatePermissionFlags(perm.name, group.packageInfo.packageName,
1163 PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, newFlags, user)
1164 }
1165
1166 // If we revoke background access to the fine location, we trigger a check to remove
1167 // notification warning about background location access
1168 if (perm.isGrantedIncludingAppOp && !isGranted) {
1169 var cancelLocationAccessWarning = false
1170 if (perm.name == ACCESS_FINE_LOCATION) {
1171 val bgPerm = group.permissions[perm.backgroundPermission]
1172 cancelLocationAccessWarning = bgPerm?.isGrantedIncludingAppOp == true
1173 } else if (perm.name == ACCESS_BACKGROUND_LOCATION) {
1174 val fgPerm = group.permissions[ACCESS_FINE_LOCATION]
1175 cancelLocationAccessWarning = fgPerm?.isGrantedIncludingAppOp == true
1176 }
1177 if (cancelLocationAccessWarning) {
1178 // cancel location access warning notification
1179 LocationAccessCheck(app, null).cancelBackgroundAccessWarningNotification(
1180 group.packageInfo.packageName,
1181 user,
1182 true
1183 )
1184 }
1185 }
1186
1187 val newState = PermState(newFlags, isGranted)
1188 return LightPermission(perm.pkgInfo, perm.permInfo, newState,
1189 perm.foregroundPerms) to shouldKill
1190 }
1191
1192 private fun Int.setFlag(flagToSet: Int): Int {
1193 return this or flagToSet
1194 }
1195
1196 private fun Int.clearFlag(flagToSet: Int): Int {
1197 return this and flagToSet.inv()
1198 }
1199
1200 /**
1201 * Allow the app op for a permission/uid.
1202 *
1203 * <p>There are three cases:
1204 * <dl>
1205 * <dt>The permission is not split into foreground/background</dt>
1206 * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
1207 * <dt>The permission is a foreground permission:</dt>
1208 * <dd><dl><dt>The background permission permission is granted</dt>
1209 * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
1210 * <dt>The background permission permission is <u>not</u> granted</dt>
1211 * <dd>The app op matching the permission will be set to
1212 * {@link AppOpsManager#MODE_FOREGROUND}</dd>
1213 * </dl></dd>
1214 * <dt>The permission is a background permission:</dt>
1215 * <dd>All granted foreground permissions for this background permission will be set to
1216 * {@link AppOpsManager#MODE_ALLOWED}</dd>
1217 * </dl>
1218 *
1219 * @param app The current application
1220 * @param perm The LightPermission whose app op should be allowed
1221 * @param group The LightAppPermGroup which will be looked in for foreground or
1222 * background LightPermission objects
1223 *
1224 * @return {@code true} iff app-op was changed
1225 */
1226 private fun allowAppOp(
1227 app: Application,
1228 perm: LightPermission,
1229 group: LightAppPermGroup
1230 ): Boolean {
1231 val packageName = group.packageInfo.packageName
1232 val uid = group.packageInfo.uid
1233 val appOpsManager = app.getSystemService(AppOpsManager::class.java) as AppOpsManager
1234 var wasChanged = false
1235
1236 if (perm.isBackgroundPermission && perm.foregroundPerms != null) {
1237 for (foregroundPermName in perm.foregroundPerms) {
1238 val fgPerm = group.permissions[foregroundPermName]
1239 val appOpName = permissionToOp(foregroundPermName) ?: continue
1240
1241 if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) {
1242 wasChanged = setOpMode(appOpName, uid, packageName, MODE_ALLOWED,
1243 appOpsManager) || wasChanged
1244 }
1245 }
1246 } else {
1247 val appOpName = permissionToOp(perm.name) ?: return false
1248 if (perm.backgroundPermission != null) {
1249 wasChanged = if (group.permissions.containsKey(perm.backgroundPermission)) {
1250 val bgPerm = group.permissions[perm.backgroundPermission]
1251 val mode = if (bgPerm != null && bgPerm.isGrantedIncludingAppOp) MODE_ALLOWED
1252 else MODE_FOREGROUND
1253
1254 setOpMode(appOpName, uid, packageName, mode, appOpsManager)
1255 } else {
1256 // The app requested a permission that has a background permission but it did
1257 // not request the background permission, hence it can never get background
1258 // access
1259 setOpMode(appOpName, uid, packageName, MODE_FOREGROUND, appOpsManager)
1260 }
1261 } else {
1262 wasChanged = setOpMode(appOpName, uid, packageName, MODE_ALLOWED, appOpsManager)
1263 }
1264 }
1265 return wasChanged
1266 }
1267
1268 /**
1269 * Disallow the app op for a permission/uid.
1270 *
1271 * <p>There are three cases:
1272 * <dl>
1273 * <dt>The permission is not split into foreground/background</dt>
1274 * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
1275 * <dt>The permission is a foreground permission:</dt>
1276 * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
1277 * <dt>The permission is a background permission:</dt>
1278 * <dd>All granted foreground permissions for this background permission will be set to
1279 * {@link AppOpsManager#MODE_FOREGROUND}</dd>
1280 * </dl>
1281 *
1282 * @param app The current application
1283 * @param perm The LightPermission whose app op should be allowed
1284 * @param group The LightAppPermGroup which will be looked in for foreground or
1285 * background LightPermission objects
1286 *
1287 * @return {@code true} iff app-op was changed
1288 */
1289 private fun disallowAppOp(
1290 app: Application,
1291 perm: LightPermission,
1292 group: LightAppPermGroup
1293 ): Boolean {
1294 val packageName = group.packageInfo.packageName
1295 val uid = group.packageInfo.uid
1296 val appOpsManager = app.getSystemService(AppOpsManager::class.java) as AppOpsManager
1297 var wasChanged = false
1298
1299 if (perm.isBackgroundPermission && perm.foregroundPerms != null) {
1300 for (foregroundPermName in perm.foregroundPerms) {
1301 val fgPerm = group.permissions[foregroundPermName]
1302 if (fgPerm != null && fgPerm.isGrantedIncludingAppOp) {
1303 val appOpName = permissionToOp(foregroundPermName) ?: return false
1304 wasChanged = wasChanged || setOpMode(appOpName, uid, packageName,
1305 MODE_FOREGROUND, appOpsManager)
1306 }
1307 }
1308 } else {
1309 val appOpName = permissionToOp(perm.name) ?: return false
1310 wasChanged = setOpMode(appOpName, uid, packageName, MODE_IGNORED, appOpsManager)
1311 }
1312 return wasChanged
1313 }
1314
1315 /**
1316 * Set mode of an app-op if needed.
1317 *
1318 * @param op The op to set
1319 * @param uid The uid the app-op belongs to
1320 * @param packageName The package the app-op belongs to
1321 * @param mode The new mode
1322 * @param manager The app ops manager to use to change the app op
1323 *
1324 * @return {@code true} iff app-op was changed
1325 */
1326 private fun setOpMode(
1327 op: String,
1328 uid: Int,
1329 packageName: String,
1330 mode: Int,
1331 manager: AppOpsManager
1332 ): Boolean {
1333 val currentMode = manager.unsafeCheckOpRaw(op, uid, packageName)
1334 if (currentMode == mode) {
1335 return false
1336 }
1337 manager.setUidMode(op, uid, mode)
1338 return true
1339 }
1340
1341 private fun shouldSkipKillForGroup(app: Application, group: LightAppPermGroup): Boolean {
1342 if (group.permGroupName != NOTIFICATIONS) {
1343 return false
1344 }
1345
1346 return shouldSkipKillOnPermDeny(app, POST_NOTIFICATIONS, group.packageName,
1347 group.userHandle)
1348 }
1349
1350 /**
1351 * Determine if the usual "kill app on permission denial" should be skipped. It should be
1352 * skipped if the permission is POST_NOTIFICATIONS, the app holds the BACKUP permission, and
1353 * a backup restore is currently in progress.
1354 *
1355 * @param app the current application
1356 * @param permission the permission being denied
1357 * @param packageName the package the permission was denied for
1358 * @param user the user whose package the permission was denied for
1359 *
1360 * @return true if the permission denied was POST_NOTIFICATIONS, the app is a backup app, and a
1361 * backup restore is in progress, false otherwise
1362 */
1363 fun shouldSkipKillOnPermDeny(
1364 app: Application,
1365 permission: String,
1366 packageName: String,
1367 user: UserHandle
1368 ): Boolean {
1369 val userContext: Context = Utils.getUserContext(app, user)
1370 if (permission != POST_NOTIFICATIONS || userContext.packageManager
1371 .checkPermission(BACKUP, packageName) != PackageManager.PERMISSION_GRANTED) {
1372 return false
1373 }
1374
1375 return try {
1376 val isInSetup = Settings.Secure.getInt(userContext.contentResolver,
1377 Settings.Secure.USER_SETUP_COMPLETE, user.identifier) == 0
1378 val isInDeferredSetup = Settings.Secure.getInt(userContext.contentResolver,
1379 Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, user.identifier) ==
1380 Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED
1381 isInSetup || isInDeferredSetup
1382 } catch (e: Settings.SettingNotFoundException) {
1383 Log.w(LOG_TAG, "Failed to check if the user is in restore: $e")
1384 false
1385 }
1386 }
1387
1388 /**
1389 * Determine if a given package has a launch intent. Will function correctly even if called
1390 * before user is unlocked.
1391 *
1392 * @param context: The context from which to retrieve the package
1393 * @param packageName: The package name to check
1394 *
1395 * @return whether or not the given package has a launch intent
1396 */
1397 fun packageHasLaunchIntent(context: Context, packageName: String): Boolean {
1398 val intentToResolve = Intent(ACTION_MAIN)
1399 intentToResolve.addCategory(CATEGORY_INFO)
1400 intentToResolve.setPackage(packageName)
1401 var resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
1402 MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
1403
1404 if (resolveInfos.size <= 0) {
1405 intentToResolve.removeCategory(CATEGORY_INFO)
1406 intentToResolve.addCategory(CATEGORY_LAUNCHER)
1407 intentToResolve.setPackage(packageName)
1408 resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
1409 MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
1410 }
1411 return resolveInfos.size > 0
1412 }
1413
1414 /**
1415 * Set selected location accuracy flags for COARSE and FINE location permissions.
1416 *
1417 * @param app: The current application
1418 * @param group: The LightAppPermGroup whose permission flags we wish to set
1419 * @param isFineSelected: Whether fine location is selected
1420 */
1421 fun setFlagsWhenLocationAccuracyChanged(
1422 app: Application,
1423 group: LightAppPermGroup,
1424 isFineSelected: Boolean
1425 ) {
1426 if (isFineSelected) {
1427 setGroupFlags(app, group,
1428 PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to true,
1429 filterPermissions = listOf(ACCESS_FINE_LOCATION))
1430 setGroupFlags(app, group,
1431 PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to false,
1432 filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION))
1433 } else {
1434 setGroupFlags(app, group,
1435 PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to false,
1436 filterPermissions = listOf(ACCESS_FINE_LOCATION))
1437 setGroupFlags(app, group,
1438 PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to true,
1439 filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION))
1440 }
1441 }
1442
1443 /**
1444 * Determines whether we should show the safety protection resources.
1445 * We show the resources only if
1446 * (1) the build version is T or after and
1447 * (2) the feature flag safety_protection_enabled is enabled and
1448 * (3) the config value config_safetyProtectionEnabled is enabled/true and
1449 * (4) the resources exist (currently the resources only exist on GMS devices)
1450 */
1451 @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
1452 fun shouldShowSafetyProtectionResources(context: Context): Boolean {
1453 return try {
1454 SdkLevel.isAtLeastT() &&
1455 DeviceConfig.getBoolean(
1456 DeviceConfig.NAMESPACE_PRIVACY, SAFETY_PROTECTION_RESOURCES_ENABLED, false) &&
1457 context.getResources().getBoolean(
1458 Resources.getSystem()
1459 .getIdentifier("config_safetyProtectionEnabled", "bool", "android")) &&
1460 context.getDrawable(android.R.drawable.ic_safety_protection) != null &&
1461 !context.getString(android.R.string.safety_protection_display_text).isNullOrEmpty()
1462 } catch (e: Resources.NotFoundException) {
1463 // We should expect the resources to not exist for non-pixel devices
1464 // (except for the OEMs that opt-in)
1465 false
1466 }
1467 }
1468
1469 fun addHealthPermissions(context: Context) {
1470 val permissions = HealthConnectManager.getHealthPermissions(context)
1471 PermissionMapping.addHealthPermissionsToPlatform(permissions)
1472 }
1473
1474 /**
1475 * Returns an [Intent] to the installer app store for a given package name, or {@code null} if
1476 * none found
1477 */
1478 fun getAppStoreIntent(
1479 context: Context,
1480 installerPackageName: String?,
1481 packageName: String?
1482 ): Intent? {
1483 val intent: Intent = Intent(Intent.ACTION_SHOW_APP_INFO)
1484 .setPackage(installerPackageName)
1485 val result: Intent? = resolveActivityForIntent(context, intent)
1486 if (result != null) {
1487 result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
1488 return result
1489 }
1490 return null
1491 }
1492
1493 /**
1494 * Verify that a component that supports the intent with action and return a new intent with
1495 * same action and resolved class name set. Returns null if no activity resolution.
1496 */
1497 private fun resolveActivityForIntent(context: Context, intent: Intent): Intent? {
1498 val result: ResolveInfo? = context.packageManager.resolveActivity(intent, 0)
1499 return if (result != null) {
1500 Intent(intent.action)
1501 .setClassName(result.activityInfo.packageName, result.activityInfo.name)
1502 } else {
1503 null
1504 }
1505 }
1506
1507 data class NotificationResources(val appLabel: String, val smallIcon: Icon, val color: Int)
1508
1509 fun getSafetyCenterNotificationResources(context: Context): NotificationResources {
1510 val appLabel: String
1511 val smallIcon: Icon
1512 val color: Int
1513 // If U resources are available, and this is a U+ device, use those
1514 if (SdkLevel.isAtLeastU()) {
1515 val scContext = SafetyCenterResourcesContext(context)
1516 val uIcon = scContext.getIconByDrawableName("ic_notification_badge_general")
1517 val uColor = scContext.getColorByName("notification_tint_normal")
1518 if (uIcon != null && uColor != null) {
1519 appLabel = context.getString(R.string.safety_privacy_qs_tile_title)
1520 return NotificationResources(appLabel, uIcon, uColor)
1521 }
1522 }
1523
1524 // Use PbA branding if available, otherwise default to more generic branding
1525 if (shouldShowSafetyProtectionResources(context)) {
1526 appLabel = Html.fromHtml(context.getString(
1527 android.R.string.safety_protection_display_text), 0).toString()
1528 smallIcon =
1529 Icon.createWithResource(context, android.R.drawable.ic_safety_protection)
1530 color = context.getColor(R.color.safety_center_info)
1531 } else {
1532 appLabel = context.getString(R.string.safety_center_notification_app_label)
1533 smallIcon =
1534 Icon.createWithResource(context, R.drawable.ic_settings_notification)
1535 color = context.getColor(android.R.color.system_notification_accent_color)
1536 }
1537 return NotificationResources(appLabel, smallIcon, color)
1538 }
1539 }
1540
1541 /**
1542 * Get the [value][LiveData.getValue], suspending until [isInitialized] if not yet so
1543 */
getInitializedValuenull1544 suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
1545 observe: LD.(Observer<T>) -> Unit = { observeForever(it) },
<lambda>null1546 isInitialized: LD.() -> Boolean = { value != null }
1547 ): T {
1548 return if (isInitialized()) {
1549 value!!
1550 } else {
continuationnull1551 suspendCoroutine { continuation: Continuation<T> ->
1552 val observer = AtomicReference<Observer<T>>()
1553 observer.set(Observer { newValue ->
1554 if (isInitialized()) {
1555 GlobalScope.launch(Dispatchers.Main) {
1556 observer.getAndSet(null)?.let { observerSnapshot ->
1557 removeObserver(observerSnapshot)
1558 continuation.resume(newValue)
1559 }
1560 }
1561 }
1562 })
1563
1564 GlobalScope.launch(Dispatchers.Main) {
1565 observe(observer.get())
1566 }
1567 }
1568 }
1569 }
1570
1571 /**
1572 * A parallel equivalent of [map]
1573 *
1574 * Starts the given suspending function for each item in the collection without waiting for
1575 * previous ones to complete, then suspends until all the started operations finish.
1576 */
mapInParallelnull1577 suspend inline fun <T, R> Iterable<T>.mapInParallel(
1578 context: CoroutineContext,
1579 scope: CoroutineScope = GlobalScope,
1580 crossinline transform: suspend CoroutineScope.(T) -> R
1581 ): List<R> = map { scope.async(context) { transform(it) } }.map { it.await() }
1582
1583 /**
1584 * A parallel equivalent of [forEach]
1585 *
1586 * See [mapInParallel]
1587 */
forEachInParallelnull1588 suspend inline fun <T> Iterable<T>.forEachInParallel(
1589 context: CoroutineContext,
1590 scope: CoroutineScope = GlobalScope,
1591 crossinline action: suspend CoroutineScope.(T) -> Unit
1592 ) {
1593 mapInParallel(context, scope) { action(it) }
1594 }
1595
1596 /**
1597 * Check that we haven't already started transitioning to a given destination. If we haven't,
1598 * start navigating to that destination.
1599 *
1600 * @param destResId The ID of the desired destination
1601 * @param args The optional bundle of args to be passed to the destination
1602 */
navigateSafenull1603 fun NavController.navigateSafe(destResId: Int, args: Bundle? = null) {
1604 val navAction = currentDestination?.getAction(destResId) ?: graph.getAction(destResId)
1605 navAction?.let { action ->
1606 if (currentDestination?.id != action.destinationId) {
1607 navigate(destResId, args)
1608 }
1609 }
1610 }
1611