diff options
Diffstat (limited to 'PermissionController/src/com/android/permissioncontroller/permission/utils')
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) { |