diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt index ba0db624c..283a62fb0 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthDataReader.kt @@ -29,6 +29,7 @@ class HealthDataReader( private val dataConverter: HealthDataConverter ) { private val recordingFilter = HealthRecordingFilter() + private val permissionChecker = HealthPermissionChecker(context) /** * Retrieves all health data points of a specified type within a given time range. @@ -306,49 +307,73 @@ class HealthDataReader( val record = rec as ExerciseSessionRecord // Get distance data - val distanceRequest = healthConnectClient.readRecords( - ReadRecordsRequest( - recordType = DistanceRecord::class, - timeRangeFilter = TimeRangeFilter.between( - record.startTime, - record.endTime, - ), - ), - ) var totalDistance = 0.0 - for (distanceRec in distanceRequest.records) { - totalDistance += distanceRec.distance.inMeters + if (permissionChecker.isLocationPermissionGranted() && permissionChecker.isHealthDistancePermissionGranted()) { + val distanceRequest = healthConnectClient.readRecords( + ReadRecordsRequest( + recordType = DistanceRecord::class, + timeRangeFilter = TimeRangeFilter.between( + record.startTime, + record.endTime, + ), + ), + ) + for (distanceRec in distanceRequest.records) { + totalDistance += distanceRec.distance.inMeters + } + } else { + Log.i( + "FLUTTER_HEALTH", + "Skipping distance data retrieval for workout due to missing permissions (location or health distance)" + ) } // Get energy burned data - val energyBurnedRequest = healthConnectClient.readRecords( - ReadRecordsRequest( - recordType = TotalCaloriesBurnedRecord::class, - timeRangeFilter = TimeRangeFilter.between( - record.startTime, - record.endTime, - ), - ), - ) var totalEnergyBurned = 0.0 - for (energyBurnedRec in energyBurnedRequest.records) { - totalEnergyBurned += energyBurnedRec.energy.inKilocalories + if (permissionChecker.isHealthTotalCaloriesBurnedPermissionGranted()) { + val energyBurnedRequest = healthConnectClient.readRecords( + ReadRecordsRequest( + recordType = TotalCaloriesBurnedRecord::class, + timeRangeFilter = TimeRangeFilter.between( + record.startTime, + record.endTime, + ), + ), + ) + for (energyBurnedRec in energyBurnedRequest.records) { + totalEnergyBurned += energyBurnedRec.energy.inKilocalories + } + } else { + Log.i( + "FLUTTER_HEALTH", + "Skipping total calories burned data retrieval for workout due to missing permissions" + ) } + // Get steps data - val stepRequest = healthConnectClient.readRecords( - ReadRecordsRequest( - recordType = StepsRecord::class, - timeRangeFilter = TimeRangeFilter.between( - record.startTime, - record.endTime - ), - ), - ) var totalSteps = 0.0 + if (permissionChecker.isHealthStepsPermissionGranted()) { + val stepRequest = healthConnectClient.readRecords( + ReadRecordsRequest( + recordType = StepsRecord::class, + timeRangeFilter = TimeRangeFilter.between( + record.startTime, + record.endTime + ), + ), + ) + for (stepRec in stepRequest.records) { totalSteps += stepRec.count } + + } else { + Log.i( + "FLUTTER_HEALTH", + "Skipping steps data retrieval for workout due to missing permissions" + ) + } // Add final datapoint healthConnectData.add( diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPermissionChecker.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPermissionChecker.kt new file mode 100644 index 000000000..8388299be --- /dev/null +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPermissionChecker.kt @@ -0,0 +1,47 @@ +package cachet.plugins.health + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat + +class HealthPermissionChecker(private val context: Context) { + + fun isLocationPermissionGranted(): Boolean { + val fineLocationGranted = ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + + val coarseLocationGranted = ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + + return fineLocationGranted || coarseLocationGranted + } + + fun isHealthDistancePermissionGranted(): Boolean { + val healthDistancePermission = "android.permission.health.READ_DISTANCE" + return ContextCompat.checkSelfPermission( + context, + healthDistancePermission + ) == PackageManager.PERMISSION_GRANTED + } + + fun isHealthTotalCaloriesBurnedPermissionGranted(): Boolean { + val healthCaloriesPermission = "android.permission.health.READ_TOTAL_CALORIES_BURNED" + return ContextCompat.checkSelfPermission( + context, + healthCaloriesPermission + ) == PackageManager.PERMISSION_GRANTED + } + + fun isHealthStepsPermissionGranted(): Boolean { + val healthStepsPermission = "android.permission.health.READ_STEPS" + return ContextCompat.checkSelfPermission( + context, + healthStepsPermission + ) == PackageManager.PERMISSION_GRANTED + } +} \ No newline at end of file diff --git a/packages/health/lib/src/health_value_types.dart b/packages/health/lib/src/health_value_types.dart index 6a8b28819..2f4822145 100644 --- a/packages/health/lib/src/health_value_types.dart +++ b/packages/health/lib/src/health_value_types.dart @@ -140,6 +140,9 @@ class WorkoutHealthValue extends HealthValue { /// Might not be available for all workouts. HealthDataUnit? totalStepsUnit; + /// Raw workoutActivityType from native data format. + String? rawWorkoutActivityType; + WorkoutHealthValue( {required this.workoutActivityType, this.totalEnergyBurned, @@ -147,11 +150,13 @@ class WorkoutHealthValue extends HealthValue { this.totalDistance, this.totalDistanceUnit, this.totalSteps, - this.totalStepsUnit}); + this.totalStepsUnit, + this.rawWorkoutActivityType}); /// Create a [WorkoutHealthValue] based on a health data point from native data format. factory WorkoutHealthValue.fromHealthDataPoint(dynamic dataPoint) => WorkoutHealthValue( + rawWorkoutActivityType: dataPoint['workoutActivityType'] as String?, workoutActivityType: HealthWorkoutActivityType.values.firstWhere( (element) => element.name == dataPoint['workoutActivityType'], orElse: () => HealthWorkoutActivityType.OTHER, @@ -188,6 +193,7 @@ class WorkoutHealthValue extends HealthValue { @override String toString() => """$runtimeType - workoutActivityType: ${workoutActivityType.name}, + rawWorkoutActivityType: $rawWorkoutActivityType, totalEnergyBurned: $totalEnergyBurned, totalEnergyBurnedUnit: ${totalEnergyBurnedUnit?.name}, totalDistance: $totalDistance, @@ -198,6 +204,7 @@ class WorkoutHealthValue extends HealthValue { @override bool operator ==(Object other) => other is WorkoutHealthValue && + rawWorkoutActivityType == other.rawWorkoutActivityType && workoutActivityType == other.workoutActivityType && totalEnergyBurned == other.totalEnergyBurned && totalEnergyBurnedUnit == other.totalEnergyBurnedUnit && @@ -209,6 +216,7 @@ class WorkoutHealthValue extends HealthValue { @override int get hashCode => Object.hash( workoutActivityType, + rawWorkoutActivityType, totalEnergyBurned, totalEnergyBurnedUnit, totalDistance,