-
Notifications
You must be signed in to change notification settings - Fork 13
Unnecessary fetch health permissions #449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,12 +30,13 @@ 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. | ||
| * Handles pagination for large datasets and applies recording method filtering. | ||
| * Supports special processing for workout and sleep data. | ||
| * | ||
| * | ||
| * @param call Method call containing 'dataTypeKey', 'startTime', 'endTime', 'recordingMethodsToFilter' | ||
| * @param result Flutter result callback returning list of health data maps | ||
| */ | ||
|
|
@@ -91,8 +92,8 @@ class HealthDataReader( | |
| // Handle special cases | ||
| when (dataType) { | ||
| WORKOUT -> handleWorkoutData(records, recordingMethodsToFilter, healthConnectData) | ||
| SLEEP_SESSION, SLEEP_ASLEEP, SLEEP_AWAKE, SLEEP_AWAKE_IN_BED, | ||
| SLEEP_LIGHT, SLEEP_DEEP, SLEEP_REM, SLEEP_OUT_OF_BED, SLEEP_UNKNOWN -> | ||
| SLEEP_SESSION, SLEEP_ASLEEP, SLEEP_AWAKE, SLEEP_AWAKE_IN_BED, | ||
| SLEEP_LIGHT, SLEEP_DEEP, SLEEP_REM, SLEEP_OUT_OF_BED, SLEEP_UNKNOWN -> | ||
| handleSleepData(records, recordingMethodsToFilter, dataType, healthConnectData) | ||
| else -> { | ||
| val filteredRecords = recordingFilter.filterRecordsByRecordingMethods( | ||
|
|
@@ -114,14 +115,15 @@ class HealthDataReader( | |
| "Unable to return $dataType due to the following exception:" | ||
| ) | ||
| Log.e("FLUTTER_HEALTH::ERROR", Log.getStackTraceString(e)) | ||
| result.success(emptyList<Map<String, Any?>>()) // Return empty list instead of null | ||
| // Return empty list instead of null | ||
| result.error("UNAVAILABLE", "Data not available", emptyList<Map<String, Any?>>()) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves single health data point by given UUID and type. | ||
| * | ||
| * | ||
| * @param call Method call containing 'UUID' and 'dataTypeKey' | ||
| * @param result Flutter result callback returning list of health data maps | ||
| */ | ||
|
|
@@ -192,7 +194,7 @@ class HealthDataReader( | |
| /** | ||
| * Retrieves aggregated health data grouped by time intervals. | ||
| * Calculates totals, averages, or counts over specified time periods. | ||
| * | ||
| * | ||
| * @param call Method call containing 'dataTypeKey', 'interval', 'startTime', 'endTime' | ||
| * @param result Flutter result callback returning list of aggregated data maps | ||
| */ | ||
|
|
@@ -202,7 +204,7 @@ class HealthDataReader( | |
| val startTime = Instant.ofEpochMilli(call.argument<Long>("startTime")!!) | ||
| val endTime = Instant.ofEpochMilli(call.argument<Long>("endTime")!!) | ||
| val healthConnectData = mutableListOf<Map<String, Any?>>() | ||
|
|
||
| scope.launch { | ||
| try { | ||
| HealthConstants.mapToAggregateMetric[dataType]?.let { metricClassType -> | ||
|
|
@@ -250,7 +252,7 @@ class HealthDataReader( | |
| /** | ||
| * Retrieves interval-based health data. Currently delegates to getAggregateData. | ||
| * Maintained for API compatibility and potential future differentiation. | ||
| * | ||
| * | ||
| * @param call Method call with interval data parameters | ||
| * @param result Flutter result callback returning interval data | ||
| */ | ||
|
|
@@ -262,7 +264,7 @@ class HealthDataReader( | |
| * Gets total step count within a specified time interval with optional filtering. | ||
| * Optimizes between aggregated queries and filtered individual record queries | ||
| * based on whether recording method filtering is required. | ||
| * | ||
| * | ||
| * @param call Method call containing 'startTime', 'endTime', 'recordingMethodsToFilter' | ||
| * @param result Flutter result callback returning total step count as integer | ||
| */ | ||
|
|
@@ -283,15 +285,15 @@ class HealthDataReader( | |
| /** | ||
| * Retrieves aggregated step count using Health Connect's built-in aggregation. | ||
| * Provides optimized step counting when no filtering is required. | ||
| * | ||
| * | ||
| * @param start Start time in milliseconds | ||
| * @param end End time in milliseconds | ||
| * @param result Flutter result callback returning step count | ||
| */ | ||
| private fun getAggregatedStepCount(start: Long, end: Long, result: Result) { | ||
| val startInstant = Instant.ofEpochMilli(start) | ||
| val endInstant = Instant.ofEpochMilli(end) | ||
|
|
||
| scope.launch { | ||
| try { | ||
| val response = healthConnectClient.aggregate( | ||
|
|
@@ -318,16 +320,16 @@ class HealthDataReader( | |
| /** | ||
| * Retrieves step count with recording method filtering applied. | ||
| * Manually sums individual step records after applying specified filters. | ||
| * | ||
| * | ||
| * @param start Start time in milliseconds | ||
| * @param end End time in milliseconds | ||
| * @param recordingMethodsToFilter List of recording methods to exclude | ||
| * @param result Flutter result callback returning filtered step count | ||
| */ | ||
| private fun getStepCountFiltered( | ||
| start: Long, | ||
| end: Long, | ||
| recordingMethodsToFilter: List<Int>, | ||
| start: Long, | ||
| end: Long, | ||
| recordingMethodsToFilter: List<Int>, | ||
| result: Result | ||
| ) { | ||
| scope.launch { | ||
|
|
@@ -345,7 +347,7 @@ class HealthDataReader( | |
| response.records | ||
| ) | ||
| val totalSteps = filteredRecords.sumOf { (it as StepsRecord).count.toInt() } | ||
|
|
||
| Log.i( | ||
| "FLUTTER_HEALTH::SUCCESS", | ||
| "returning $totalSteps steps (excluding manual entries)" | ||
|
|
@@ -366,7 +368,7 @@ class HealthDataReader( | |
| * Handles special processing for workout/exercise session data. | ||
| * Enriches workout records with associated distance, energy, and step data | ||
| * by querying related records within the workout time period. | ||
| * | ||
| * | ||
| * @param records List of ExerciseSessionRecord objects | ||
| * @param recordingMethodsToFilter Recording methods to exclude (empty list means no filtering) | ||
| * @param healthConnectData Mutable list to append processed workout data | ||
|
|
@@ -387,7 +389,7 @@ class HealthDataReader( | |
|
|
||
| for (rec in filteredRecords) { | ||
| val record = rec as ExerciseSessionRecord | ||
|
|
||
| // Get distance data | ||
| val distanceRequest = healthConnectClient.readRecords( | ||
| ReadRecordsRequest( | ||
|
|
@@ -399,38 +401,70 @@ class HealthDataReader( | |
| ), | ||
| ) | ||
| var totalDistance = 0.0 | ||
| for (distanceRec in distanceRequest.records) { | ||
| totalDistance += distanceRec.distance.inMeters | ||
| if (permissionChecker.isLocationPermissionGranted() && permissionChecker.isHealthDistancePermissionGranted()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need both permissions to obtain distance? From what I understand, you only need READ_DISTANCE to access this. |
||
| 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to return |
||
| 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 | ||
| for (stepRec in stepRequest.records) { | ||
| totalSteps += stepRec.count | ||
| 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 | ||
|
|
@@ -462,7 +496,7 @@ class HealthDataReader( | |
| * Handles special processing for sleep session and stage data. | ||
| * Processes sleep sessions and individual sleep stages based on requested data type. | ||
| * Converts sleep stage enumerations to meaningful duration and type information. | ||
| * | ||
| * | ||
| * @param records List of SleepSessionRecord objects | ||
| * @param recordingMethodsToFilter Recording methods to exclude (empty list means no filtering) | ||
| * @param dataType Specific sleep data type being requested | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you forgot to remove this. You are requesting distance twice.