summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/permission/utils
diff options
context:
space:
mode:
Diffstat (limited to 'PermissionController/src/com/android/permissioncontroller/permission/utils')
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/AdminRestrictedPermissionsUtils.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt376
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt81
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionRationales.kt64
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt44
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java43
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java44
8 files changed, 627 insertions, 53 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/AdminRestrictedPermissionsUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/AdminRestrictedPermissionsUtils.java
index 4ee6411d8..81c16f0e6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/AdminRestrictedPermissionsUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/AdminRestrictedPermissionsUtils.java
@@ -52,10 +52,17 @@ public final class AdminRestrictedPermissionsUtils {
if (SdkLevel.isAtLeastT()) {
ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
}
+ // New U permissions - do not add unless running on U and above.
+ if (SdkLevel.isAtLeastU()) {
+ ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add(
+ Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
+ ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add(
+ Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
+ }
}
/**
- * A set of permissions that the managed Profile Owner cannot grant.
+ * A set of permissions that the non-organization owned managed Profile Owner cannot grant.
*/
private static final ArraySet<String> MANAGED_PROFILE_OWNER_RESTRICTED_PERMISSIONS =
new ArraySet<>();
@@ -75,7 +82,8 @@ public final class AdminRestrictedPermissionsUtils {
DevicePolicyManager dpm = userContext.getSystemService(DevicePolicyManager.class);
UserManager um = userContext.getSystemService(UserManager.class);
if (um.isManagedProfile(userId)
- && MANAGED_PROFILE_OWNER_RESTRICTED_PERMISSIONS.contains(permission)) {
+ && MANAGED_PROFILE_OWNER_RESTRICTED_PERMISSIONS.contains(permission)
+ && !(SdkLevel.isAtLeastU() && dpm.isOrganizationOwnedDeviceWithManagedProfile())) {
return false;
}
if (!ADMIN_RESTRICTED_SENSORS_PERMISSIONS.contains(permission)) {
@@ -89,11 +97,13 @@ public final class AdminRestrictedPermissionsUtils {
* Returns true if the admin may grant this permission, false otherwise.
*/
public static boolean mayAdminGrantPermission(String permission,
- boolean canAdminGrantSensorsPermissions, boolean isManagedProfile) {
+ boolean canAdminGrantSensorsPermissions, boolean isManagedProfile,
+ boolean isOrganizationOwnedDevice) {
if (!SdkLevel.isAtLeastS()) {
return true;
}
- if (isManagedProfile && MANAGED_PROFILE_OWNER_RESTRICTED_PERMISSIONS.contains(permission)) {
+ if (isManagedProfile && MANAGED_PROFILE_OWNER_RESTRICTED_PERMISSIONS.contains(permission)
+ && !(SdkLevel.isAtLeastU() && isOrganizationOwnedDevice)) {
return false;
}
if (!ADMIN_RESTRICTED_SENSORS_PERMISSIONS.contains(permission)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
index 70b357ba3..6a6623da7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
@@ -68,18 +68,12 @@ fun PackageManager.updatePermissionFlags(
vararg flags: Pair<Int, Boolean>
) {
val mask = flags.fold(0, { mask, (flag, _) -> mask or flag })
- val value = flags.fold(0, { mask, (flag, flagValue) -> if (flagValue) mask or flag else mask })
+ val value = flags.fold(0,
+ { mask2, (flag, flagValue) -> if (flagValue) mask2 or flag else mask2 })
updatePermissionFlags(permissionName, packageName, mask, value, user)
}
/**
- * @see UserHandle.getUid
- */
-fun UserHandle.getUid(appId: Int): Int {
- return identifier * 100000 + (appId % 100000)
-}
-
-/**
* Gets a [ComponentInfo] from a [ResolveInfo]
*/
val ResolveInfo.componentInfo: ComponentInfo
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index 3ab0a83fc..29d511f00 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
package com.android.permissioncontroller.permission.utils
@@ -21,6 +22,8 @@ import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.BACKUP
import android.Manifest.permission.POST_NOTIFICATIONS
+import android.Manifest.permission.READ_MEDIA_IMAGES
+import android.Manifest.permission.READ_MEDIA_VIDEO
import android.Manifest.permission_group.NOTIFICATIONS
import android.app.ActivityManager
import android.app.AppOpsManager
@@ -46,16 +49,20 @@ import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
+import android.content.pm.ResolveInfo
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
+import android.health.connect.HealthConnectManager
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
import android.permission.PermissionManager
import android.provider.DeviceConfig
import android.provider.Settings
+import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
import android.text.TextUtils
import android.util.Log
import androidx.annotation.ChecksSdkIntAtLeast
@@ -75,16 +82,18 @@ import com.android.permissioncontroller.permission.model.livedatatypes.LightPerm
import com.android.permissioncontroller.permission.model.livedatatypes.PermState
import com.android.permissioncontroller.permission.service.LocationAccessCheck
import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
+import java.time.Duration
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+
/**
* A set of util functions designed to work with kotlin, though they can work with java, as well.
*/
@@ -117,6 +126,209 @@ object KotlinUtils {
private val ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
+ /** Whether to show the Permissions Hub. */
+ private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"
+
+ /** Whether to show the mic and camera icons. */
+ private const val PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"
+
+ /** Whether to show the location indicators. */
+ private const val PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled"
+
+ /** Whether location accuracy feature is enabled */
+ private const val PROPERTY_LOCATION_ACCURACY_ENABLED = "location_accuracy_enabled"
+
+ /** Whether to show 7-day toggle in privacy hub. */
+ private const val PRIVACY_DASHBOARD_7_DAY_TOGGLE = "privacy_dashboard_7_day_toggle"
+
+ /** Whether to placeholder safety label data in permission settings and grant dialog. */
+ private const val PRIVACY_PLACEHOLDER_SAFETY_LABEL_DATA_ENABLED =
+ "privacy_placeholder_safety_label_data_enabled"
+
+ /** Default location precision */
+ private const val PROPERTY_LOCATION_PRECISION = "location_precision"
+
+ /** Whether to show the photo picker option in permission prompts. */
+ private const val PROPERTY_PHOTO_PICKER_PROMPT_ENABLED = "photo_picker_prompt_enabled"
+
+ /**
+ * The minimum amount of time to wait, after scheduling the safety label changes job, before
+ * the job actually runs for the first time.
+ */
+ private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_DELAY_MILLIS =
+ "safety_label_changes_job_delay_millis"
+
+ /** How often the safety label changes job service will run its job. */
+ private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS =
+ "safety_label_changes_job_interval_millis"
+
+ /** Whether the safety label changes job should only be run when the device is idle. */
+ private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_RUN_WHEN_IDLE =
+ "safety_label_changes_job_run_when_idle"
+
+ /**
+ * Whether the Permissions Hub 2 flag is enabled
+ *
+ * @return whether the flag is enabled
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isPermissionsHub2FlagEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_2_ENABLED, false)
+ }
+ /**
+ * Whether to show the Permissions Dashboard
+ *
+ * @return whether to show the Permissions Dashboard.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun shouldShowPermissionsDashboard(): Boolean {
+ return isPermissionsHub2FlagEnabled()
+ }
+
+ /**
+ * Whether the Camera and Mic Icons are enabled by flag.
+ *
+ * @return whether the Camera and Mic Icons are enabled.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isCameraMicIconsFlagEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_CAMERA_MIC_ICONS_ENABLED, true)
+ }
+
+ /**
+ * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the icons
+ * specifically, are enabled.
+ *
+ * @return whether to show the icons.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun shouldShowCameraMicIndicators(): Boolean {
+ return isCameraMicIconsFlagEnabled() || isPermissionsHub2FlagEnabled()
+ }
+
+ /**
+ * Whether the location indicators are enabled by flag.
+ *
+ * @return whether the location indicators are enabled by flag.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isLocationIndicatorsFlagEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_INDICATORS_ENABLED, false)
+ }
+
+ /**
+ * Whether to show the location indicators. The location indicators are enable if the
+ * permission hub, or location indicator specifically are enabled.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun shouldShowLocationIndicators(): Boolean {
+ return isLocationIndicatorsFlagEnabled() || isPermissionsHub2FlagEnabled()
+ }
+
+ /**
+ * Whether the location accuracy feature is enabled
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isLocationAccuracyEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCURACY_ENABLED, true)
+ }
+
+ /**
+ * Default state of location precision
+ * true: default is FINE.
+ * false: default is COARSE.
+ */
+ fun getDefaultPrecision(): Boolean {
+ return !SdkLevel.isAtLeastS() || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_PRECISION, true)
+ }
+
+ /**
+ * Whether we should enable the 7-day toggle in privacy dashboard
+ *
+ * @return whether the flag is enabled
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun is7DayToggleEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PRIVACY_DASHBOARD_7_DAY_TOGGLE, false)
+ }
+
+ /**
+ * Whether the Photo Picker Prompt is enabled
+ *
+ * @return `true` iff the Location Access Check is enabled.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun isPhotoPickerPromptEnabled(): Boolean {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PHOTO_PICKER_PROMPT_ENABLED, true)
+ }
+
+ /*
+ * Whether we should enable the permission rationale in permission settings and grant dialog
+ *
+ * @return whether the flag is enabled
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun isPermissionRationaleEnabled(): Boolean {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PERMISSION_RATIONALE_ENABLED, false)
+ }
+
+ /**
+ * Whether we should use placeholder safety label data
+ *
+ * @return whether the flag is enabled
+ */
+ fun isPlaceholderSafetyLabelDataEnabled(): Boolean {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PRIVACY_PLACEHOLDER_SAFETY_LABEL_DATA_ENABLED, false)
+ }
+
+ /**
+ * Whether we should enable the safety label change notifications and data sharing updates UI.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun isSafetyLabelChangeNotificationsEnabled(): Boolean {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, false)
+ }
+
+ /**
+ * The minimum amount of time to wait, after scheduling the safety label changes job, before
+ * the job actually runs for the first time.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun getSafetyLabelChangesJobDelayMillis(): Long {
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_LABEL_CHANGES_JOB_DELAY_MILLIS,
+ Duration.ofMinutes(30).toMillis())
+ }
+
+ /** How often the safety label changes job will run. */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun getSafetyLabelChangesJobIntervalMillis(): Long {
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS,
+ Duration.ofDays(30).toMillis())
+ }
+
+ /** Whether the safety label changes job should only be run when the device is idle. */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun runSafetyLabelChangesJobOnlyWhenDeviceIdle(): Boolean {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_LABEL_CHANGES_JOB_RUN_WHEN_IDLE,
+ true)
+ }
+
/**
* Given a Map, and a List, determines which elements are in the list, but not the map, and
* vice versa. Used primarily for determining which liveDatas are already being watched, and
@@ -316,7 +528,6 @@ object KotlinUtils {
*
* @return The package's icon, or null, if the package does not exist
*/
- @JvmOverloads
fun getBadgedPackageIcon(
app: Application,
packageName: String,
@@ -385,6 +596,20 @@ object KotlinUtils {
}
/**
+ * Return a specific MIME type, if a set of permissions is associated with one
+ */
+ fun getMimeTypeForPermissions(permissions: List<String>): String? {
+ if (permissions.contains(READ_MEDIA_IMAGES) && !permissions.contains(READ_MEDIA_VIDEO)) {
+ return "image/*"
+ }
+ if (permissions.contains(READ_MEDIA_VIDEO) && !permissions.contains(READ_MEDIA_IMAGES)) {
+ return "video/*"
+ }
+
+ return null
+ }
+
+ /**
* Determines if an app is R or above, or if it is Q-, and has auto revoke enabled
*
* @param app The currenct application
@@ -494,9 +719,12 @@ object KotlinUtils {
app: Application,
group: LightAppPermGroup,
filterPermissions: List<String> = group.permissions.keys.toList(),
- isOneTime: Boolean = false
+ isOneTime: Boolean = false,
+ userFixed: Boolean = false,
+ withoutAppOps: Boolean = false,
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, false, isOneTime, filterPermissions)
+ return grantRuntimePermissions(app, group, false, isOneTime, userFixed,
+ withoutAppOps, filterPermissions)
}
/**
@@ -518,7 +746,8 @@ object KotlinUtils {
group: LightAppPermGroup,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, true, false, filterPermissions)
+ return grantRuntimePermissions(app, group, true, false, false, false,
+ filterPermissions)
}
private fun grantRuntimePermissions(
@@ -526,16 +755,18 @@ object KotlinUtils {
group: LightAppPermGroup,
grantBackground: Boolean,
isOneTime: Boolean = false,
- filterPermissions: List<String> = group.permissions.keys.toList()
+ userFixed: Boolean = false,
+ withoutAppOps: Boolean = false,
+ filterPermissions: List<String> = group.permissions.keys.toList(),
): LightAppPermGroup {
- val wasOneTime = group.isOneTime
val newPerms = group.permissions.toMutableMap()
var shouldKillForAnyPermission = false
for (permName in filterPermissions) {
val perm = group.permissions[permName] ?: continue
val isBackgroundPerm = permName in group.backgroundPermNames
if (isBackgroundPerm == grantBackground) {
- val (newPerm, shouldKill) = grantRuntimePermission(app, perm, isOneTime, group)
+ val (newPerm, shouldKill) = grantRuntimePermission(app, perm, group, isOneTime,
+ userFixed, withoutAppOps)
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
}
@@ -543,10 +774,10 @@ object KotlinUtils {
if (!newPerms.isEmpty()) {
val user = UserHandle.getUserHandleForUid(group.packageInfo.uid)
for (groupPerm in group.allPermissions.values) {
- var permFlags = groupPerm!!.flags
- permFlags = permFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
- if (groupPerm!!.flags != permFlags) {
- app.packageManager.updatePermissionFlags(groupPerm!!.name,
+ var permFlags = groupPerm.flags
+ permFlags = permFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
+ if (groupPerm.flags != permFlags) {
+ app.packageManager.updatePermissionFlags(groupPerm.name,
group.packageInfo.packageName, PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
permFlags, user)
}
@@ -582,8 +813,12 @@ object KotlinUtils {
*
* @param app The current application
* @param perm The permission which should be granted.
- * @param group An optional app permission group in which to look for background or foreground
+ * @param group An app permission group in which to look for background or foreground
+ * @param isOneTime Whether this is a one-time permission grant
* permissions
+ * @param userFixed Whether to mark the permissions as user fixed when granted
+ * @param withoutAppOps If these permission have app ops associated, and this value is true,
+ * then do not grant the app op when the permission is granted, and add the REVOKED_COMPAT flag.
*
* @return a LightPermission and boolean pair <permission with updated state (or the original
* state, if it wasn't changed), should kill app>
@@ -591,8 +826,10 @@ object KotlinUtils {
private fun grantRuntimePermission(
app: Application,
perm: LightPermission,
+ group: LightAppPermGroup,
isOneTime: Boolean,
- group: LightAppPermGroup
+ userFixed: Boolean = false,
+ withoutAppOps: Boolean = false
): Pair<LightPermission, Boolean> {
val pkgInfo = group.packageInfo
val user = UserHandle.getUserHandleForUid(pkgInfo.uid)
@@ -605,6 +842,7 @@ object KotlinUtils {
}
var newFlags = perm.flags
+ var oldFlags = perm.flags
var isGranted = perm.isGrantedIncludingAppOp
var shouldKill = false
@@ -614,6 +852,14 @@ object KotlinUtils {
// TODO 195016052: investigate adding split permission handling
if (supportsRuntime) {
+ // If granting without app ops, explicitly disallow app op first, while setting the
+ // flag, so that the PermissionPolicyService doesn't reset the app op state
+ if (affectsAppOp && withoutAppOps) {
+ oldFlags = oldFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ app.packageManager.updatePermissionFlags(perm.name, group.packageName,
+ PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, oldFlags, user)
+ disallowAppOp(app, perm, group)
+ }
app.packageManager.grantRuntimePermission(group.packageName, perm.name, user)
isGranted = true
} else if (affectsAppOp) {
@@ -624,31 +870,39 @@ object KotlinUtils {
shouldKill = true
isGranted = true
}
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ newFlags = if (affectsAppOp && withoutAppOps) {
+ newFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ } else {
+ newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ }
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)
// If this permission affects an app op, ensure the permission app op is enabled
// before the permission grant.
- if (affectsAppOp) {
+ if (affectsAppOp && !withoutAppOps) {
allowAppOp(app, perm, group)
}
}
// Granting a permission explicitly means the user already
// reviewed it so clear the review flag on every grant.
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
+ newFlags = newFlags.clearFlag(FLAG_PERMISSION_REVIEW_REQUIRED)
// Update the permission flags
- // Now the apps can ask for the permission as the user
- // no longer has it fixed in a denied state.
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
- newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
+ if (!withoutAppOps && !userFixed) {
+ // Now the apps can ask for the permission as the user
+ // no longer has it fixed in a denied state.
+ newFlags = newFlags.clearFlag(FLAG_PERMISSION_USER_FIXED)
+ newFlags = newFlags.setFlag(FLAG_PERMISSION_USER_SET)
+ } else if (userFixed) {
+ newFlags = newFlags.setFlag(FLAG_PERMISSION_USER_FIXED)
+ }
+ newFlags = newFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
newFlags = if (isOneTime) {
- newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ newFlags.setFlag(FLAG_PERMISSION_ONE_TIME)
} else {
- newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ newFlags.clearFlag(FLAG_PERMISSION_ONE_TIME)
}
// If we newly grant background access to the fine location, double-guess the user some
@@ -668,7 +922,7 @@ object KotlinUtils {
}
}
- if (perm.flags != newFlags) {
+ if (oldFlags != newFlags) {
app.packageManager.updatePermissionFlags(perm.name, group.packageInfo.packageName,
PERMISSION_CONTROLLER_CHANGED_FLAG_MASK, newFlags, user)
}
@@ -699,9 +953,11 @@ object KotlinUtils {
group: LightAppPermGroup,
userFixed: Boolean = false,
oneTime: Boolean = false,
+ forceRemoveRevokedCompat: Boolean = false,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return revokeRuntimePermissions(app, group, false, userFixed, oneTime, filterPermissions)
+ return revokeRuntimePermissions(app, group, false, userFixed, oneTime,
+ forceRemoveRevokedCompat, filterPermissions)
}
/**
@@ -724,9 +980,11 @@ object KotlinUtils {
group: LightAppPermGroup,
userFixed: Boolean = false,
oneTime: Boolean = false,
+ forceRemoveRevokedCompat: Boolean = false,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return revokeRuntimePermissions(app, group, true, userFixed, oneTime, filterPermissions)
+ return revokeRuntimePermissions(app, group, true, userFixed, oneTime,
+ forceRemoveRevokedCompat, filterPermissions)
}
private fun revokeRuntimePermissions(
@@ -735,6 +993,7 @@ object KotlinUtils {
revokeBackground: Boolean,
userFixed: Boolean,
oneTime: Boolean,
+ forceRemoveRevokedCompat: Boolean = false,
filterPermissions: List<String>
): LightAppPermGroup {
val wasOneTime = group.isOneTime
@@ -745,7 +1004,8 @@ object KotlinUtils {
val isBackgroundPerm = permName in group.backgroundPermNames
if (isBackgroundPerm == revokeBackground) {
val (newPerm, shouldKill) =
- revokeRuntimePermission(app, perm, userFixed, oneTime, group)
+ revokeRuntimePermission(app, perm, userFixed, oneTime, forceRemoveRevokedCompat,
+ group)
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
}
@@ -844,6 +1104,7 @@ object KotlinUtils {
perm: LightPermission,
userFixed: Boolean,
oneTime: Boolean,
+ forceRemoveRevokedCompat: Boolean,
group: LightAppPermGroup
): Pair<LightPermission, Boolean> {
// Do not touch permissions fixed by the system.
@@ -859,13 +1120,16 @@ object KotlinUtils {
val affectsAppOp = permissionToOp(perm.name) != null || perm.isBackgroundPermission
- if (perm.isGrantedIncludingAppOp) {
+ if (perm.isGrantedIncludingAppOp || (perm.isCompatRevoked && forceRemoveRevokedCompat)) {
if (supportsRuntime && !isPermissionSplitFromNonRuntime(app, perm.name,
group.packageInfo.targetSdkVersion)) {
// Revoke the permission if needed.
app.packageManager.revokeRuntimePermission(group.packageInfo.packageName,
perm.name, user)
isGranted = false
+ if (forceRemoveRevokedCompat) {
+ newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ }
} else if (affectsAppOp) {
// If the permission has no corresponding app op, then it is a
// third-party one and we do not offer toggling of such permissions.
@@ -1138,14 +1402,14 @@ object KotlinUtils {
var resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
- if (resolveInfos == null || resolveInfos.size <= 0) {
+ if (resolveInfos.size <= 0) {
intentToResolve.removeCategory(CATEGORY_INFO)
intentToResolve.addCategory(CATEGORY_LAUNCHER)
intentToResolve.setPackage(packageName)
resolveInfos = context.packageManager.queryIntentActivities(intentToResolve,
MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)
}
- return resolveInfos != null && resolveInfos.size > 0
+ return resolveInfos.size > 0
}
/**
@@ -1196,6 +1460,44 @@ object KotlinUtils {
context.getDrawable(android.R.drawable.ic_safety_protection) != null &&
!context.getString(android.R.string.safety_protection_display_text).isNullOrEmpty()
}
+
+ fun addHealthPermissions(context: Context) {
+ val permissions = HealthConnectManager.getHealthPermissions(context)
+ PermissionMapping.addHealthPermissionsToPlatform(permissions)
+ }
+
+ /**
+ * Returns an [Intent] to the installer app store for a given package name, or {@code null} if
+ * none found
+ */
+ fun getAppStoreIntent(
+ context: Context,
+ installerPackageName: String?,
+ packageName: String?
+ ): Intent? {
+ val intent: Intent = Intent(Intent.ACTION_SHOW_APP_INFO)
+ .setPackage(installerPackageName)
+ val result: Intent? = resolveActivityForIntent(context, intent)
+ if (result != null) {
+ result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ return result
+ }
+ return null
+ }
+
+ /**
+ * Verify that a component that supports the intent with action and return a new intent with
+ * same action and resolved class name set. Returns null if no activity resolution.
+ */
+ private fun resolveActivityForIntent(context: Context, intent: Intent): Intent? {
+ val result: ResolveInfo? = context.packageManager.resolveActivity(intent, 0)
+ return if (result != null) {
+ Intent(intent.action)
+ .setClassName(result.activityInfo.packageName, result.activityInfo.name)
+ } else {
+ null
+ }
+ }
}
/**
@@ -1206,7 +1508,7 @@ suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
isInitialized: LD.() -> Boolean = { value != null }
): T {
return if (isInitialized()) {
- value as T
+ value!!
} else {
suspendCoroutine { continuation: Continuation<T> ->
val observer = AtomicReference<Observer<T>>()
@@ -1267,4 +1569,4 @@ fun NavController.navigateSafe(destResId: Int, args: Bundle? = null) {
navigate(destResId, args)
}
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
index fa7cbec6d..3567c300d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
@@ -13,12 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
package com.android.permissioncontroller.permission.utils
import android.Manifest
+import android.app.AppOpsManager
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
+import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
+import android.util.Log
import com.android.modules.utils.build.SdkLevel
/**
@@ -27,6 +31,8 @@ import com.android.modules.utils.build.SdkLevel
*/
object PermissionMapping {
+ private val LOG_TAG = "PermissionMapping"
+
@JvmField
val SENSOR_DATA_PERMISSIONS: List<String> =
listOf(
@@ -52,6 +58,8 @@ object PermissionMapping {
/** Set of groups that will be able to receive one-time grant */
private val ONE_TIME_PERMISSION_GROUPS: MutableSet<String> = mutableSetOf()
+ private val HEALTH_PERMISSIONS_SET: MutableSet<String> = mutableSetOf()
+
init {
PLATFORM_PERMISSIONS[Manifest.permission.READ_CONTACTS] = Manifest.permission_group.CONTACTS
PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CONTACTS] =
@@ -62,6 +70,9 @@ object PermissionMapping {
PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CALENDAR] =
Manifest.permission_group.CALENDAR
+ // Any updates to the permissions for the SMS permission group must also be made in
+ // Permissions {@link com.android.role.controller.model.Permissions} in the role
+ // library
PLATFORM_PERMISSIONS[Manifest.permission.SEND_SMS] = Manifest.permission_group.SMS
PLATFORM_PERMISSIONS[Manifest.permission.RECEIVE_SMS] = Manifest.permission_group.SMS
PLATFORM_PERMISSIONS[Manifest.permission.READ_SMS] = Manifest.permission_group.SMS
@@ -92,6 +103,11 @@ object PermissionMapping {
Manifest.permission_group.READ_MEDIA_VISUAL
}
+ if (SdkLevel.isAtLeastU()) {
+ PLATFORM_PERMISSIONS[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] =
+ Manifest.permission_group.READ_MEDIA_VISUAL
+ }
+
PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_FINE_LOCATION] =
Manifest.permission_group.LOCATION
PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_COARSE_LOCATION] =
@@ -114,6 +130,9 @@ object PermissionMapping {
Manifest.permission_group.NEARBY_DEVICES
}
+ // Any updates to the permissions for the CALL_LOG permission group must also be made in
+ // Permissions {@link com.android.role.controller.model.Permissions} in the role
+ // library
PLATFORM_PERMISSIONS[Manifest.permission.READ_CALL_LOG] = Manifest.permission_group.CALL_LOG
PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CALL_LOG] =
Manifest.permission_group.CALL_LOG
@@ -155,6 +174,14 @@ object PermissionMapping {
Manifest.permission_group.SENSORS
}
+ if (SdkLevel.isAtLeastU()) {
+ PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE] =
+ Manifest.permission_group.SENSORS
+ PLATFORM_PERMISSIONS[Manifest
+ .permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND] =
+ Manifest.permission_group.SENSORS
+ }
+
for ((permission, permissionGroup) in PLATFORM_PERMISSIONS) {
PLATFORM_PERMISSION_GROUPS.getOrPut(permissionGroup) { mutableListOf() }.add(permission)
}
@@ -274,4 +301,58 @@ object PermissionMapping {
fun supportsOneTimeGrant(permissionGroup: String?): Boolean {
return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup)
}
+
+ /**
+ * Adds health permissions as platform permissions.
+ */
+ @JvmStatic
+ fun addHealthPermissionsToPlatform(permissions: Set<String>) {
+ if (permissions.isEmpty()) {
+ Log.w(LOG_TAG, "No health connect permissions found.")
+ return
+ }
+
+ PLATFORM_PERMISSION_GROUPS[HEALTH_PERMISSION_GROUP] = mutableListOf()
+
+ for (permission in permissions) {
+ PLATFORM_PERMISSIONS[permission] = HEALTH_PERMISSION_GROUP
+ PLATFORM_PERMISSION_GROUPS[HEALTH_PERMISSION_GROUP]?.add(permission)
+ HEALTH_PERMISSIONS_SET.add(permission)
+ }
+ }
+
+ /**
+ * Returns true if the given permission is a health platform permission.
+ */
+ @JvmStatic
+ fun isHealthPermission(permissionName: String): Boolean {
+ return HEALTH_PERMISSIONS_SET.contains(permissionName)
+ }
+
+ /**
+ * Returns the platform permission group for the permission that the provided op backs, if any.
+ */
+ fun getPlatformPermissionGroupForOp(opName: String): String? {
+ // The OPSTR_READ_WRITE_HEALTH_DATA is a special case as unlike other ops, it does not
+ // map to a single permission. However it is safe to retrieve a permission group for it,
+ // as all permissions it maps to, map to the same permission group
+ // HEALTH_PERMISSION_GROUP.
+ if (opName == AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA) {
+ return HEALTH_PERMISSION_GROUP
+ }
+
+ // The following app ops are special cased as they don't back any permissions on their own,
+ // but do indicate usage of certain permissions.
+ if (opName == AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE) {
+ return Manifest.permission_group.MICROPHONE
+ }
+ if (SdkLevel.isAtLeastT() && opName == AppOpsManager.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+ return Manifest.permission_group.MICROPHONE
+ }
+ if (opName == AppOpsManager.OPSTR_PHONE_CALL_CAMERA) {
+ return Manifest.permission_group.CAMERA
+ }
+
+ return AppOpsManager.opToPermission(opName)?.let { getGroupOfPlatformPermission(it) }
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionRationales.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionRationales.kt
new file mode 100644
index 000000000..ede87e84f
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionRationales.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.utils
+
+import com.android.permission.safetylabel.DataCategory
+import com.android.permission.safetylabel.DataType
+import com.android.permission.safetylabel.DataTypeConstants
+import com.android.permission.safetylabel.SafetyLabel
+
+/**
+ * A set of util functions used for permission rationale dialog.
+ */
+object PermissionRationales {
+
+ /**
+ * Returns if the permission rationale dialog should be shown.
+ * @param safetyLabel the [SafetyLabel] bundle provided
+ * @param groupName the permission group name
+ * @return true if the permission dialog should be shown, otherwise false.
+ */
+ fun shouldShowPermissionRationale(
+ safetyLabel: SafetyLabel?,
+ groupName: String
+ ): Boolean {
+ if (safetyLabel == null || safetyLabel.dataLabel.dataShared.isEmpty()) {
+ return false
+ }
+ val categoriesForPermission: List<String> =
+ SafetyLabelPermissionMapping.getCategoriesForPermissionGroup(groupName)
+ categoriesForPermission.forEach categoryLoop@{ category ->
+ val dataCategory: DataCategory? = safetyLabel.dataLabel.dataShared[category]
+ if (dataCategory == null) {
+ // Continue to next
+ return@categoryLoop
+ }
+ val typesForCategory = DataTypeConstants.getValidDataTypesForCategory(category)
+ typesForCategory.forEach typeLoop@{ type ->
+ val dataType: DataType? = dataCategory.dataTypes[type]
+ if (dataType == null) {
+ // Continue to next
+ return@typeLoop
+ }
+ if (dataType.purposeSet.isNotEmpty()) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt
new file mode 100644
index 000000000..5fd852bb6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.utils
+
+import android.Manifest
+import com.android.permission.safetylabel.DataCategoryConstants
+
+/**
+ * This file contains the canonical mapping of permission and permission group to Safety Label
+ * categories and types used in the Permission settings screens and grant dialog. It also includes
+ * methods related to that mapping.
+ */
+object SafetyLabelPermissionMapping {
+
+ /**
+ * Get the Safety Label categories pertaining to a specified permission group.
+ *
+ * @param groupName the permission group name
+ *
+ * @return The categories or an empty list if the group does not have supported and mapped group
+ * to safety label category
+ */
+ fun getCategoriesForPermissionGroup(groupName: String): List<String> {
+ return if (groupName == Manifest.permission_group.LOCATION) {
+ listOf(DataCategoryConstants.CATEGORY_LOCATION)
+ } else {
+ emptyList()
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java
index e60b6c18a..2785eca74 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo;
import java.util.HashMap;
import java.util.Map;
@@ -48,14 +49,18 @@ public class SubattributionUtils {
return appInfo.areAttributionsUserVisible();
}
+ /** Returns whether the provided package supports subattribution. */
+ public static boolean isSubattributionSupported(LightPackageInfo lightPackageInfo) {
+ return SdkLevel.isAtLeastS() && lightPackageInfo.getAreAttributionsUserVisible();
+ }
+
/**
- * Returns the attribution label map for the package if the app supports subattribtion; Returns
+ * Returns the attribution label map for the package if the app supports subattribution; Returns
* {@code null} otherwise.
*/
@Nullable
@SuppressLint("NewApi") // isSubattributionSupported checks api level
- public static Map<Integer, String> getAttributionLabels(Context context,
- PackageInfo pkgInfo) {
+ public static Map<Integer, String> getAttributionLabels(Context context, PackageInfo pkgInfo) {
if (!isSubattributionSupported(context, pkgInfo.applicationInfo)) {
return null;
}
@@ -63,7 +68,7 @@ public class SubattributionUtils {
}
/**
- * Returns the attribution label map for the package if the app supports subattribtion; Returns
+ * Returns the attribution label map for the package if the app supports subattribtuion; Returns
* {@code null} otherwise.
*/
@Nullable
@@ -106,4 +111,34 @@ public class SubattributionUtils {
}
return attributionLabels;
}
+
+ /** Returns the attribution label map for the package if the app supports subattribution. */
+ @Nullable
+ @SuppressLint("NewApi") // isSubattributionSupported checks api level
+ public static Map<Integer, String> getAttributionLabels(Context context,
+ LightPackageInfo lightPackageInfo) {
+ if (!isSubattributionSupported(lightPackageInfo)) {
+ return null;
+ }
+
+ Context pkgContext;
+ try {
+ pkgContext = context.createPackageContext(lightPackageInfo.getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+
+ Map<Integer, String> attributionLabels = new HashMap<>();
+ for (Map.Entry<String, Integer> attribution :
+ lightPackageInfo.getAttributionTagsToLabels().entrySet()) {
+ int label = attribution.getValue();
+ try {
+ String resourceForLabel = pkgContext.getString(attribution.getValue());
+ attributionLabels.put(label, resourceForLabel);
+ } catch (Resources.NotFoundException e) {
+ // should never happen
+ }
+ }
+ return attributionLabels;
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index 8f5a70ca0..bfc6b35ca 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -34,12 +34,14 @@ import static android.Manifest.permission_group.STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.content.Context.MODE_PRIVATE;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.health.connect.HealthConnectManager.ACTION_MANAGE_HEALTH_PERMISSIONS;
import static android.os.UserHandle.myUserId;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
@@ -87,6 +89,7 @@ import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.ColorRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -153,6 +156,10 @@ public final class Utils {
/** Whether or not app hibernation is enabled on the device **/
public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
+ /** Whether the system exempt from hibernation is enabled on the device **/
+ public static final String PROPERTY_SYSTEM_EXEMPT_HIBERNATION_ENABLED =
+ "system_exempt_hibernation_enabled";
+
/** Whether to show the Permissions Hub. */
private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
@@ -168,6 +175,11 @@ public final class Utils {
private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
"location_access_check_enabled";
+ /** Whether to show health permission in various permission controller UIs. */
+ private static final String PROPERTY_HEALTH_PERMISSION_UI_ENABLED =
+ "health_permission_ui_enabled";
+
+
/** How frequently to check permission event store to scrub old data */
public static final String PROPERTY_PERMISSION_EVENTS_CHECK_OLD_FREQUENCY_MILLIS =
"permission_events_check_old_frequency_millis";
@@ -923,6 +935,16 @@ public final class Utils {
}
/**
+ * Whether we should show health permissions as platform permissions in the various
+ * permission controller UI.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ public static boolean isHealthPermissionUiEnabled() {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_HEALTH_PERMISSION_UI_ENABLED, true);
+ }
+
+ /**
* Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
*
* @param context the context to get the shared preferences
@@ -1236,6 +1258,28 @@ public final class Utils {
}
/**
+ * Navigate to health connect settings for all apps
+ * @param context The current Context
+ */
+ public static void navigateToHealthConnectSettings(@NonNull Context context) {
+ Intent healthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS);
+ context.startActivity(healthConnectIntent);
+ }
+
+ /**
+ * Navigate to health connect settings for an app
+ * @param context The current Context
+ * @param packageName The package's health connect settings to navigate to
+ */
+ public static void navigateToAppHealthConnectSettings(@NonNull Context context,
+ @NonNull String packageName, @NonNull UserHandle user) {
+ Intent appHealthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS);
+ appHealthConnectIntent.putExtra(EXTRA_PACKAGE_NAME, packageName);
+ appHealthConnectIntent.putExtra(Intent.EXTRA_USER, user);
+ context.startActivity(appHealthConnectIntent);
+ }
+
+ /**
* Returns if a card should be shown if the sensor is blocked
**/
public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) {