• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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