merge: integre fix/distance-calories-nouvelle-seance dans develop
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -2,6 +2,8 @@ package com.gametime.watch.bridge
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.location.LocationManager
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -315,11 +317,12 @@ internal class WatchHeartRateCollector(
|
|||||||
val requestedTypeNames = (
|
val requestedTypeNames = (
|
||||||
executionContext["healthServicesExerciseTypeStrategy"] as? List<*>
|
executionContext["healthServicesExerciseTypeStrategy"] as? List<*>
|
||||||
)?.filterIsInstance<String>().orEmpty()
|
)?.filterIsInstance<String>().orEmpty()
|
||||||
val config = exerciseConfigFromCapabilities(
|
val configs = exerciseConfigsFromCapabilities(
|
||||||
capabilities,
|
capabilities,
|
||||||
requestedTypeNames,
|
requestedTypeNames,
|
||||||
|
canUseGps = canStartGpsExercise(context),
|
||||||
)
|
)
|
||||||
if (config == null) {
|
if (configs.isEmpty()) {
|
||||||
exerciseMetricsStartInFlight = false
|
exerciseMetricsStartInFlight = false
|
||||||
Log.w(TAG, "no exercise type supports usable live metrics sessionId=$sessionId")
|
Log.w(TAG, "no exercise type supports usable live metrics sessionId=$sessionId")
|
||||||
if (shouldAggregate) {
|
if (shouldAggregate) {
|
||||||
@ -331,29 +334,11 @@ internal class WatchHeartRateCollector(
|
|||||||
exerciseMetricsStartInFlight = false
|
exerciseMetricsStartInFlight = false
|
||||||
return@addListener
|
return@addListener
|
||||||
}
|
}
|
||||||
exerciseHeartRateSupported = DataType.HEART_RATE_BPM in config.dataTypes
|
startExerciseMetricsConfig(
|
||||||
exerciseClient.setUpdateCallback(context.mainExecutor, exerciseCallback)
|
context,
|
||||||
val startFuture = exerciseClient.startExerciseAsync(config)
|
exerciseClient,
|
||||||
startFuture.addListener(
|
configs,
|
||||||
{
|
index = 0,
|
||||||
exerciseMetricsStartInFlight = false
|
|
||||||
try {
|
|
||||||
startFuture.get()
|
|
||||||
exerciseMetricsStarted = true
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"exercise metrics started sessionId=$sessionId type=${config.exerciseType} dataTypes=${config.dataTypes}",
|
|
||||||
)
|
|
||||||
} catch (error: Exception) {
|
|
||||||
Log.w(TAG, "exercise metrics start failed", error)
|
|
||||||
clearExerciseCallback(exerciseClient)
|
|
||||||
exerciseHeartRateSupported = false
|
|
||||||
if (shouldAggregate) {
|
|
||||||
startMeasureHeartRateFallback(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context.mainExecutor,
|
|
||||||
)
|
)
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
exerciseMetricsStartInFlight = false
|
exerciseMetricsStartInFlight = false
|
||||||
@ -364,73 +349,144 @@ internal class WatchHeartRateCollector(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exerciseConfigFromCapabilities(
|
private fun startExerciseMetricsConfig(
|
||||||
|
context: Context,
|
||||||
|
exerciseClient: ExerciseClient,
|
||||||
|
configs: List<ExerciseConfig>,
|
||||||
|
index: Int,
|
||||||
|
) {
|
||||||
|
if (!shouldAggregate) {
|
||||||
|
exerciseMetricsStartInFlight = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val config = configs.getOrNull(index)
|
||||||
|
if (config == null) {
|
||||||
|
exerciseMetricsStartInFlight = false
|
||||||
|
clearExerciseCallback(exerciseClient)
|
||||||
|
exerciseHeartRateSupported = false
|
||||||
|
Log.w(TAG, "all exercise metric configs failed sessionId=$sessionId")
|
||||||
|
if (shouldAggregate) {
|
||||||
|
startMeasureHeartRateFallback(context)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exerciseHeartRateSupported = DataType.HEART_RATE_BPM in config.dataTypes
|
||||||
|
exerciseClient.setUpdateCallback(context.mainExecutor, exerciseCallback)
|
||||||
|
val startFuture = exerciseClient.startExerciseAsync(config)
|
||||||
|
startFuture.addListener(
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
startFuture.get()
|
||||||
|
exerciseMetricsStartInFlight = false
|
||||||
|
exerciseMetricsStarted = true
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"exercise metrics started sessionId=$sessionId type=${config.exerciseType} dataTypes=${config.dataTypes}",
|
||||||
|
)
|
||||||
|
} catch (error: Exception) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"exercise metrics start failed type=${config.exerciseType} dataTypes=${config.dataTypes}",
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
clearExerciseCallback(exerciseClient)
|
||||||
|
exerciseHeartRateSupported = false
|
||||||
|
startExerciseMetricsConfig(
|
||||||
|
context,
|
||||||
|
exerciseClient,
|
||||||
|
configs,
|
||||||
|
index + 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context.mainExecutor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exerciseConfigsFromCapabilities(
|
||||||
capabilities: androidx.health.services.client.data.ExerciseCapabilities,
|
capabilities: androidx.health.services.client.data.ExerciseCapabilities,
|
||||||
requestedTypeNames: List<String>,
|
requestedTypeNames: List<String>,
|
||||||
): ExerciseConfig? {
|
canUseGps: Boolean,
|
||||||
|
): List<ExerciseConfig> {
|
||||||
val requestedTypes = exerciseTypesFromNames(requestedTypeNames)
|
val requestedTypes = exerciseTypesFromNames(requestedTypeNames)
|
||||||
var distanceOnlyConfig: ExerciseConfig? = null
|
val distanceCaloriesConfigs = mutableListOf<ExerciseConfig>()
|
||||||
var caloriesOnlyConfig: ExerciseConfig? = null
|
val caloriesConfigs = mutableListOf<ExerciseConfig>()
|
||||||
var heartRateOnlyConfig: ExerciseConfig? = null
|
val heartRateConfigs = mutableListOf<ExerciseConfig>()
|
||||||
for (exerciseType in requestedTypes) {
|
for (exerciseType in requestedTypes) {
|
||||||
if (exerciseType !in capabilities.supportedExerciseTypes) {
|
if (exerciseType !in capabilities.supportedExerciseTypes) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val supported = capabilities.getExerciseTypeCapabilities(exerciseType)
|
val supported = capabilities.getExerciseTypeCapabilities(exerciseType)
|
||||||
.supportedDataTypes
|
.supportedDataTypes
|
||||||
val dataTypes = mutableSetOf<androidx.health.services.client.data.DataType<*, *>>()
|
val supportsHeartRate = DataType.HEART_RATE_BPM in supported
|
||||||
if (DataType.DISTANCE !in supported) {
|
if (
|
||||||
if (DataType.CALORIES in supported) {
|
canUseGps &&
|
||||||
dataTypes.add(DataType.CALORIES)
|
DataType.DISTANCE in supported &&
|
||||||
}
|
DataType.CALORIES in supported
|
||||||
if (DataType.HEART_RATE_BPM in supported) {
|
) {
|
||||||
|
val dataTypes = mutableSetOf<androidx.health.services.client.data.DataType<*, *>>(
|
||||||
|
DataType.DISTANCE,
|
||||||
|
DataType.CALORIES,
|
||||||
|
)
|
||||||
|
if (supportsHeartRate) {
|
||||||
dataTypes.add(DataType.HEART_RATE_BPM)
|
dataTypes.add(DataType.HEART_RATE_BPM)
|
||||||
}
|
}
|
||||||
if (dataTypes.isEmpty()) {
|
distanceCaloriesConfigs.add(
|
||||||
|
ExerciseConfig.builder(exerciseType)
|
||||||
|
.setDataTypes(dataTypes)
|
||||||
|
.setIsAutoPauseAndResumeEnabled(false)
|
||||||
|
.setIsGpsEnabled(true)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (DataType.CALORIES in supported) {
|
||||||
|
val dataTypes = mutableSetOf<androidx.health.services.client.data.DataType<*, *>>(
|
||||||
|
DataType.CALORIES,
|
||||||
|
)
|
||||||
|
if (supportsHeartRate) {
|
||||||
|
dataTypes.add(DataType.HEART_RATE_BPM)
|
||||||
|
}
|
||||||
|
caloriesConfigs.add(
|
||||||
|
ExerciseConfig.builder(exerciseType)
|
||||||
|
.setDataTypes(dataTypes)
|
||||||
|
.setIsAutoPauseAndResumeEnabled(false)
|
||||||
|
.setIsGpsEnabled(false)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
} else if (supportsHeartRate) {
|
||||||
|
heartRateConfigs.add(
|
||||||
|
ExerciseConfig.builder(exerciseType)
|
||||||
|
.setDataTypes(setOf(DataType.HEART_RATE_BPM))
|
||||||
|
.setIsAutoPauseAndResumeEnabled(false)
|
||||||
|
.setIsGpsEnabled(false)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
Log.w(
|
Log.w(
|
||||||
TAG,
|
TAG,
|
||||||
"exercise type lacks usable metrics type=$exerciseType supported=$supported",
|
"exercise type lacks usable metrics type=$exerciseType supported=$supported",
|
||||||
)
|
)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return distanceCaloriesConfigs + caloriesConfigs + heartRateConfigs
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canStartGpsExercise(context: Context): Boolean {
|
||||||
if (
|
if (
|
||||||
DataType.CALORIES in dataTypes &&
|
context.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) !=
|
||||||
caloriesOnlyConfig == null
|
PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
caloriesOnlyConfig = ExerciseConfig.builder(exerciseType)
|
return false
|
||||||
.setDataTypes(dataTypes)
|
|
||||||
.setIsAutoPauseAndResumeEnabled(false)
|
|
||||||
.setIsGpsEnabled(false)
|
|
||||||
.build()
|
|
||||||
} else if (heartRateOnlyConfig == null) {
|
|
||||||
heartRateOnlyConfig = ExerciseConfig.builder(exerciseType)
|
|
||||||
.setDataTypes(dataTypes)
|
|
||||||
.setIsAutoPauseAndResumeEnabled(false)
|
|
||||||
.setIsGpsEnabled(false)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
continue
|
return try {
|
||||||
|
val locationManager = context.getSystemService(LocationManager::class.java)
|
||||||
|
?: return false
|
||||||
|
locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
|
||||||
|
locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
|
} catch (error: RuntimeException) {
|
||||||
|
Log.w(TAG, "location availability check failed", error)
|
||||||
|
false
|
||||||
}
|
}
|
||||||
dataTypes.add(DataType.DISTANCE)
|
|
||||||
if (DataType.CALORIES in supported) {
|
|
||||||
dataTypes.add(DataType.CALORIES)
|
|
||||||
}
|
|
||||||
if (DataType.HEART_RATE_BPM in supported) {
|
|
||||||
dataTypes.add(DataType.HEART_RATE_BPM)
|
|
||||||
}
|
|
||||||
val config = ExerciseConfig.builder(exerciseType)
|
|
||||||
.setDataTypes(dataTypes)
|
|
||||||
.setIsAutoPauseAndResumeEnabled(false)
|
|
||||||
.setIsGpsEnabled(true)
|
|
||||||
.build()
|
|
||||||
if (DataType.CALORIES in dataTypes) {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
if (distanceOnlyConfig == null) {
|
|
||||||
distanceOnlyConfig = config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return caloriesOnlyConfig ?: distanceOnlyConfig ?: heartRateOnlyConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exerciseTypesFromNames(names: List<String>): List<ExerciseType> {
|
private fun exerciseTypesFromNames(names: List<String>): List<ExerciseType> {
|
||||||
|
|||||||
Reference in New Issue
Block a user