Programmes d'entraînement

Ce guide est compatible avec la version 1.1.0-alpha11 de Santé Connect.

Santé Connect fournit un type de données exercice planifié pour permettre aux applications d'entraînement d'écrire des plans d'entraînement et aux applications d'entraînement de lire des plans d'entraînement. Les exercices enregistrés (entraînements) peuvent être relus pour une analyse personnalisée des performances afin d'aider les utilisateurs à atteindre leurs objectifs d'entraînement.

Disponibilité de la fonctionnalité

Pour déterminer si l'appareil d'un utilisateur est compatible avec les plans d'entraînement sur Santé Connect, vérifiez la disponibilité de FEATURE_PLANNED_EXERCISE sur le client :

if (healthConnectClient
     .features
     .getFeatureStatus(
       HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
     ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {

  // Feature is available
} else {
  // Feature isn't available
}

Pour en savoir plus, consultez Vérifier la disponibilité des fonctionnalités.

Autorisations requises

L'accès aux exercices planifiés est protégé par les autorisations suivantes :

  • android.permission.health.READ_PLANNED_EXERCISE
  • android.permission.health.WRITE_PLANNED_EXERCISE

Pour ajouter la fonctionnalité d'exercice planifié à votre application, commencez par demander des autorisations d'écriture pour le type de données PlannedExerciseSession.

Voici l'autorisation que vous devez déclarer pour pouvoir écrire des exercices planifiés :

<application>
  <uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>

Pour lire les exercices planifiés, vous devez demander les autorisations suivantes :

<application>
  <uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>

Demander des autorisations à l'utilisateur

Après avoir créé une instance de client, votre application doit demander des autorisations à l'utilisateur. Les utilisateurs doivent être autorisés à accorder ou à refuser des autorisations à tout moment.

Pour ce faire, créez un ensemble d'autorisations pour les types de données requis. Assurez-vous d'abord que les autorisations de l'ensemble sont déclarées dans votre fichier manifeste Android.

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(HeartRateRecord::class),
  HealthPermission.getWritePermission(HeartRateRecord::class),
  HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class),
  HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class),
  HealthPermission.getReadPermission(ExerciseSessionRecord::class),
  HealthPermission.getWritePermission(ExerciseSessionRecord::class)
)

Utilisez getGrantedPermissions pour voir si votre application dispose déjà des autorisations requises accordées. Si ce n'est pas le cas, utilisez createRequestPermissionResultContract pour demander ces autorisations. L'écran des autorisations de Santé Connect s'affiche.

// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()

val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions successfully granted
  } else {
    // Lack of required permissions
  }
}

suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
  val granted = healthConnectClient.permissionController.getGrantedPermissions()
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions already granted; proceed with inserting or reading data
  } else {
    requestPermissions.launch(PERMISSIONS)
  }
}

Étant donné que les utilisateurs peuvent accorder ou révoquer des autorisations à tout moment, votre application doit vérifier régulièrement les autorisations accordées et être capable de gérer les cas de perte d'autorisations.

Les programmes d'entraînement sont associés à des séances d'exercice. Par conséquent, l'utilisateur doit autoriser l'utilisation de chaque type d'enregistrement lié à un programme d'entraînement pour utiliser pleinement cette fonctionnalité de Santé Connect.

Par exemple, si un programme d'entraînement mesure la fréquence cardiaque d'un utilisateur lors d'une série de courses, le développeur devra peut-être déclarer les autorisations suivantes, que l'utilisateur devra accorder, afin d'enregistrer la séance d'exercice et de lire les résultats pour une évaluation ultérieure :

  • android.permission.health.READ_EXERCISE
  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.READ_HEART_RATE
  • android.permission.health.WRITE_EXERCISE
  • android.permission.health.WRITE_EXERCISE_ROUTE
  • android.permission.health.WRITE_HEART_RATE

Toutefois, l'application qui crée des plans d'entraînement et évalue les performances par rapport aux plans n'est souvent pas la même que celle qui consomme les plans d'entraînement et écrit les données d'exercice réelles. Selon le type d'application, toutes les autorisations de lecture et d'écriture ne sont pas nécessaires. Par exemple, vous n'aurez peut-être besoin que des autorisations suivantes pour chaque type d'application :

Application de programme d'entraînement Application d'entraînement
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Informations incluses dans l'enregistrement d'une séance d'exercice planifiée

  • Titre de la session.
  • Liste des blocs d'exercices prévus.
  • Heure de début et de fin de la session.
  • Type d'exercice.
  • Notes concernant l'activité.
  • Les métadonnées
  • ID de la session d'exercice terminée : il est écrit automatiquement après la fin d'une session d'exercice liée à cette session d'exercice planifiée.

Informations incluses dans un enregistrement de bloc d'exercice planifié

Un bloc d'exercices planifié contient une liste d'étapes d'exercice pour permettre la répétition de différents groupes d'étapes (par exemple, effectuer une séquence de flexions des bras, de burpees et de crunches cinq fois de suite).

Informations incluses dans un enregistrement d'étape d'exercice planifié

Agrégations acceptées

Aucune agrégation n'est acceptée pour ce type de données.

Exemples d'utilisation

Supposons qu'un utilisateur prévoie de courir pendant 90 minutes dans deux jours. Cette course comprend trois tours autour d'un lac, avec une fréquence cardiaque cible comprise entre 90 et 110 bpm.

  1. Une séance d'exercice planifiée avec les éléments suivants est définie par l'utilisateur dans une application de programme d'entraînement :
    1. Début et fin prévus de la course
    2. Type d'exercice (course à pied)
    3. Nombre de longueurs (répétitions)
    4. Fréquence cardiaque cible (entre 90 et 110 bpm)
  2. Ces informations sont regroupées en blocs d'exercices et de pas, et écrites dans Santé Connect par l'application de programme d'entraînement sous la forme d'un PlannedExerciseSessionRecord.
  3. L'utilisateur effectue la séance prévue (course).
  4. Les données d'exercice liées à la séance sont enregistrées :
    1. par un accessoire connecté pendant la séance. Par exemple, la fréquence cardiaque. Ces données sont écrites dans Santé Connect en tant que type d'enregistrement de l'activité. Dans ce cas, HeartRateRecord.
    2. Manuellement par l'utilisateur après la session. Par exemple, en indiquant le début et la fin de la course réelle. Ces données sont écrites dans Santé Connect sous la forme d'un ExerciseSessionRecord.
  5. Plus tard, l'application de programme d'entraînement lit les données de Santé Connect pour évaluer les performances réelles par rapport aux objectifs définis par l'utilisateur lors de la séance d'entraînement prévue.

Planifier des exercices et définir des objectifs

Un utilisateur peut planifier son exercice à l'avance et définir des objectifs. Écris cela dans Santé Connect en tant que séance d'exercice planifiée.

Dans l'exemple décrit dans Exemple d'utilisation, l'utilisateur prévoit une course de 90 minutes dans deux jours. Cette course comprend trois tours autour d'un lac, avec une fréquence cardiaque cible comprise entre 90 et 110 bpm.

Un extrait de code comme celui-ci peut être trouvé dans le gestionnaire de formulaire d'une application qui enregistre les séances d'exercice planifiées dans Santé Connect. Il peut également se trouver au point d'ingestion pour les intégrations, par exemple avec un service qui propose des formations.

// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
    healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
      HealthPermission.getWritePermission(PlannedExerciseSessionRecord::class))) {
    // The user hasn't granted the app permission to write planned exercise session data.
    return
}

val plannedDuration = Duration.ofMinutes(90)
val plannedStartDate = LocalDate.now().plusDays(2)

val plannedExerciseSessionRecord = PlannedExerciseSessionRecord(
    startDate = plannedStartDate,
    duration = plannedDuration,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    blocks = listOf(
        PlannedExerciseBlock(
            repetitions = 1, steps = listOf(
                PlannedExerciseStep(
                    exerciseType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING,
                    exercisePhase = PlannedExerciseStep.EXERCISE_PHASE_ACTIVE,
                    completionGoal = ExerciseCompletionGoal.RepetitionsGoal(repetitions = 3),
                    performanceTargets = listOf(
                        ExercisePerformanceTarget.HeartRateTarget(
                            minHeartRate = 90.0, maxHeartRate = 110.0
                        )
                    )
                ),
            ), description = "Three laps around the lake"
        )
    ),
    title = "Run at lake",
    notes = null,
    metadata = Metadata.manualEntry(
      device = Device(type = Device.Companion.TYPE_PHONE)
    )
)
val insertedPlannedExerciseSessions =
    healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)).recordIdsList
val insertedPlannedExerciseSessionId = insertedPlannedExerciseSessions.first()

Enregistrer des données d'exercice et d'activité

Deux jours plus tard, l'utilisateur enregistre la séance d'exercice réelle. Écris cela dans Santé Connect en tant que séance d'exercice.

Dans cet exemple, la durée de la session de l'utilisateur correspond exactement à la durée prévue.

L'extrait suivant peut être trouvé dans le gestionnaire de formulaire d'une application qui enregistre des séances d'exercice dans Santé Connect. Il peut également être utilisé dans les gestionnaires d'ingestion et d'exportation de données pour un accessoire connecté capable de détecter et d'enregistrer les séances d'exercice.

insertedPlannedExerciseSessionId est réutilisé à partir de l'exemple précédent. Dans une application réelle, l'ID serait déterminé par l'utilisateur qui sélectionne une séance d'exercice planifiée dans une liste de séances existantes.

// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
    healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
      HealthPermission.getWritePermission(ExerciseSessionRecord::class))) {
    // The user doesn't granted the app permission to write exercise session data.
    return
}

val sessionDuration = Duration.ofMinutes(90)
val sessionEndTime = Instant.now()
val sessionStartTime = sessionEndTime.minus(sessionDuration)

val exerciseSessionRecord = ExerciseSessionRecord(
    startTime = sessionStartTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = sessionEndTime,
    endZoneOffset = ZoneOffset.UTC,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    segments = listOf(
        ExerciseSegment(
            startTime = sessionStartTime,
            endTime = sessionEndTime,
            repetitions = 3,
            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_RUNNING
        )
    ),
    title = "Run at lake",
    plannedExerciseSessionId = insertedPlannedExerciseSessionId,
    metadata = Metadata.manualEntry(
      device = Device(type = Device.Companion.TYPE_PHONE)
    )
)
val insertedExerciseSessions =
    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))

Un wearable enregistre également sa fréquence cardiaque tout au long de la course. L'extrait de code suivant peut être utilisé pour générer des enregistrements dans la plage cible.

Dans une application réelle, les principaux éléments de cet extrait peuvent être trouvés dans le gestionnaire d'un message provenant d'un wearable, qui écrirait la mesure dans Santé Connect lors de la collecte.

// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
    healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.contains(
      HealthPermission.getWritePermission(HeartRateRecord::class))) {
    // The user doesn't granted the app permission to write heart rate record data.
    return
}

val samples = mutableListOf<HeartRateRecord.Sample>()
var currentTime = sessionStartTime
while (currentTime.isBefore(sessionEndTime)) {
    val bpm = Random.nextInt(21) + 90
    val heartRateRecord = HeartRateRecord.Sample(
        time = currentTime,
        beatsPerMinute = bpm.toLong(),
    )
    samples.add(heartRateRecord)
    currentTime = currentTime.plusSeconds(180)
}

val heartRateRecord = HeartRateRecord(
    startTime = sessionStartTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = sessionEndTime,
    endZoneOffset = ZoneOffset.UTC,
    samples = samples,
    metadata = Metadata.autoRecorded(
      device = Device(type = Device.Companion.TYPE_WATCH)
    )
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))

Évaluer les objectifs de performances

Le lendemain de l'entraînement de l'utilisateur, vous pouvez récupérer l'exercice enregistré, vérifier s'il existe des objectifs d'exercice planifiés et évaluer d'autres types de données pour déterminer si les objectifs définis ont été atteints.

Un extrait comme celui-ci se trouve généralement dans un job périodique permettant d'évaluer les objectifs de performances ou lors du chargement d'une liste d'exercices et de l'affichage d'une notification concernant les objectifs de performances dans une application.

// Verify the user has granted all necessary permissions for this task
val grantedPermissions =
     healthConnectClient.permissionController.getGrantedPermissions()
if (!grantedPermissions.containsAll(
        listOf(
            HealthPermission.getReadPermission(ExerciseSessionRecord::class),
            HealthPermission.getReadPermission(PlannedExerciseSessionRecord::class),
            HealthPermission.getReadPermission(HeartRateRecord::class)
        )
    )
) {
    // The user doesn't granted the app permission to read exercise session record data.
    return
}

val searchDuration = Duration.ofDays(1)
val searchEndTime = Instant.now()
val searchStartTime = searchEndTime.minus(searchDuration)

val response = healthConnectClient.readRecords(
    ReadRecordsRequest<ExerciseSessionRecord>(
        timeRangeFilter = TimeRangeFilter.between(searchStartTime, searchEndTime)
    )
)
for (exerciseRecord in response.records) {
    val plannedExerciseRecordId = exerciseRecord.plannedExerciseSessionId
    val plannedExerciseRecord =
        if (plannedExerciseRecordId == null) null else healthConnectClient.readRecord(
            PlannedExerciseSessionRecord::class, plannedExerciseRecordId
        ).record
    if (plannedExerciseRecord != null) {
        val aggregateRequest = AggregateRequest(
            metrics = setOf(HeartRateRecord.BPM_AVG),
            timeRangeFilter = TimeRangeFilter.between(
                exerciseRecord.startTime, exerciseRecord.endTime
            ),
        )
        val aggregationResult = healthConnectClient.aggregate(aggregateRequest)

        val maxBpm = aggregationResult[HeartRateRecord.BPM_MAX]
        val minBpm = aggregationResult[HeartRateRecord.BPM_MIN]
        if (maxBpm != null && minBpm != null) {
            plannedExerciseRecord.blocks.forEach { block ->
                block.steps.forEach { step ->
                    step.performanceTargets.forEach { target ->
                        when (target) {
                            is ExercisePerformanceTarget.HeartRateTarget -> {
                                val minTarget = target.minHeartRate
                                val maxTarget = target.maxHeartRate
                                if(
                                    minBpm >= minTarget && maxBpm <= maxTarget
                                ) {
                                  // Success!
                                }
                            }
                            // Handle more target types
                            }
                        }
                    }
                }
            }
        }
    }
}

Sessions d'exercice

Les sessions d'exercice peuvent inclure toutes sortes d'activités, de la course à pied au badminton.

Écrire des sessions d'exercice

Voici comment créer une requête d'insertion incluant une session :

suspend fun writeExerciseSession(healthConnectClient: HealthConnectClient) {
    healthConnectClient.insertRecords(
        listOf(
            ExerciseSessionRecord(
                startTime = START_TIME,
                startZoneOffset = START_ZONE_OFFSET,
                endTime = END_TIME,
                endZoneOffset = END_ZONE_OFFSET,
                exerciseType = ExerciseSessionRecord.ExerciseType.RUNNING,
                title = "My Run"
            ),
            // ... other records
        )
    )
}

Lire une session d'exercice

Voici un exemple de lecture d'une session d'exercice :

suspend fun readExerciseSessions(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    val response =
        healthConnectClient.readRecords(
            ReadRecordsRequest(
                ExerciseSessionRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
    for (exerciseRecord in response.records) {
        // Process each exercise record
        // Optionally pull in with other data sources of the same time range.
        val distanceRecord =
            healthConnectClient
                .readRecords(
                    ReadRecordsRequest(
                        DistanceRecord::class,
                        timeRangeFilter =
                            TimeRangeFilter.between(
                                exerciseRecord.startTime,
                                exerciseRecord.endTime
                            )
                    )
                )
                .records
    }
}

Écrire des données de sous-type

Les sessions peuvent également être constituées de données de sous-type facultatives, qui enrichiront la session avec des informations supplémentaires.

Par exemple, les sessions d'exercice peuvent inclure les classes ExerciseSegment, ExerciseLap et ExerciseRoute :

val segments = listOf(
  ExerciseSegment(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    segmentType = ActivitySegmentType.BENCH_PRESS,
    repetitions = 373
  )
)

val laps = listOf(
  ExerciseLap(
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
    length = 0.meters
  )
)

ExerciseSessionRecord(
  exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_CALISTHENICS,
    startTime = Instant.parse("2022-01-02T10:10:10Z"),
    endTime = Instant.parse("2022-01-02T10:10:13Z"),
  startZoneOffset = ZoneOffset.UTC,
  endZoneOffset = ZoneOffset.UTC,
  segments = segments,
  laps = laps,
  route = route
)