From 84128e559cd5aa36e997f7b4ae966d7417a9fd5f Mon Sep 17 00:00:00 2001 From: Scarlett Song Date: Tue, 21 Jan 2025 23:16:06 +0000 Subject: ShouldShowHealthPermissions: Skip intent check for Wear and split permissions This bug was caught when no health permissions are granted to a wear app and "Fitness&Wellness" does not show up in AppInfo->Permissions screen. The root cause is, currently, PermissionController displays HealthConnect UI in AppInfo flow only if 1. Any health permission is granted --> This gives user option to revoke any granted permissions, or 2. Intent filter is declared by the requesting app [2] is inconsistent with current Health&Fitness requirement about intent filters, where H&F relaxes this intent on Wear devices or if the permissions are from split permissions. To achieve consistent behavior with H&F, in this CL we change the PermissionController intent filter requirement same with H&F. Flag: android.permission.flags.replace_body_sensor_permission_enabled Bug: 390731163 Test: On both Wear and Phone emulator, `atest HealthConnectAppPermissionFragmentTest` Relnote: Granular health permission phase one LOW_COVERAGE_REASON=OTHER_FORM_FACTOR Change-Id: I9480b9673de578ba3edc20f680b5a13a06c72c59 --- .../permission/utils/Utils.java | 83 +++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) (limited to 'PermissionController/src') diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java index 38495f3b3..327142896 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java @@ -39,6 +39,7 @@ import static android.content.Intent.EXTRA_REASON; 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_REVOKE_WHEN_REQUESTED; 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; @@ -78,6 +79,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.hardware.SensorPrivacyManager; import android.health.connect.HealthConnectManager; +import android.health.connect.HealthPermissions; import android.os.Binder; import android.os.Build; import android.os.Parcelable; @@ -1107,17 +1109,31 @@ public final class Utils { return false; } + // Always show Fitness&Wellness chip on Wear. + if (Flags.replaceBodySensorPermissionEnabled() + && pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) { + return true; + } + // Check in permission is already granted as we should not hide it in the UX at that point. List grantedPermissions = packageInfo.getGrantedPermissions(); for (PermissionInfo permission : permissions) { boolean isCurrentlyGranted = grantedPermissions.contains(permission.name); if (isCurrentlyGranted) { - Log.d(LOG_TAG, "At least one Health permission group permission is granted, " + Log.d( + LOG_TAG, + "At least one Health permission group permission is granted, " + "show permission group entry"); return true; } } + // When none health permission is granted, exempt health permission view usage intent filter + // if all the requested health permissions are from permission splits. + if (isRequestFromSplitHealthPermission(packageInfo)) { + return true; + } + Intent viewUsageIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE); viewUsageIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS); viewUsageIntent.setPackage(packageInfo.getPackageName()); @@ -1131,6 +1147,71 @@ public final class Utils { return true; } + /** + * Returns true if the request is being made as the result of a split health permission from + * BODY_SENSORS call. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private static boolean isRequestFromSplitHealthPermission(LightPackageInfo packageInfo) { + // Sdk check to make sure HealthConnectManager.isHealthPermission() is supported. + if (!SdkLevel.isAtLeastU() || !Flags.replaceBodySensorPermissionEnabled()) { + return false; + } + + PermissionControllerApplication app = PermissionControllerApplication.get(); + PackageManager pm = app.getPackageManager(); + String packageName = packageInfo.getPackageName(); + UserHandle user = UserHandle.getUserHandleForUid(packageInfo.getUid()); + Context context = Utils.getUserContext(app, user); + + List requestedHealthPermissions = new ArrayList<>(); + for (String permission : packageInfo.getRequestedPermissions()) { + if (HealthConnectManager.isHealthPermission(context, permission)) { + requestedHealthPermissions.add(permission); + } + } + + // Split permission only applies to READ_HEART_RATE. + if (!requestedHealthPermissions.contains(HealthPermissions.READ_HEART_RATE)) { + return false; + } + + // If there are other health permissions (other than READ_HEALTH_DATA_IN_BACKGROUND) + // don't consider this a pure split-permission request. + if (requestedHealthPermissions.size() > 2) { + return false; + } + + boolean isBackgroundPermissionRequested = + requestedHealthPermissions.contains( + HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND); + // If there are two health permissions declared, make sure the other is + // READ_HEALTH_DATA_IN_BACKGROUND. + if (requestedHealthPermissions.size() == 2 && !isBackgroundPermissionRequested) { + return false; + } + + // If READ_HEALTH_DATA_IN_BACKGROUND is requested, check permission flag to see if is from + // split permission. + if (isBackgroundPermissionRequested) { + int readHealthDataInBackgroundFlag = + pm.getPermissionFlags( + HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND, packageName, user); + if (!isFromSplitPermission(readHealthDataInBackgroundFlag)) { + return false; + } + } + + // Check READ_HEART_RATE permission flag to see if is from split permission. + int readHeartRateFlag = + pm.getPermissionFlags(HealthPermissions.READ_HEART_RATE, packageName, user); + return isFromSplitPermission(readHeartRateFlag); + } + + private static boolean isFromSplitPermission(int permissionFlag) { + return (permissionFlag & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0; + } + /** * Get a device protected storage based shared preferences. Avoid storing sensitive data in it. * -- cgit v1.2.3-59-g8ed1b