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