Planos de treino

Este guia é compatível com a versão 1.1.0-alpha11 do app Saúde.

A Conexão Saúde oferece um tipo de dados de exercício planejado para que os apps de treinamento possam gravar e ler planos de treinamento. Os exercícios gravados (treinos) podem ser lidos novamente para uma análise de desempenho personalizada para ajudar os usuários a alcançar as metas de treinamento.

Disponibilidade do recurso

Para determinar se o dispositivo de um usuário oferece suporte a planos de treinamento na Conexão Saúde, verifique a disponibilidade de FEATURE_PLANNED_EXERCISE no cliente:

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

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

Consulte Verificar a disponibilidade de recursos para saber mais.

Permissões necessárias

O acesso aos planos de treinamento é protegido pelas seguintes permissões:

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

Declare estas permissões no Play Console e no manifesto do app:

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

Você é responsável por declarar todas as permissões adequadas que pretende usar nos seus dispositivos e apps. Verifique também se cada permissão foi concedida pelo usuário antes do uso.

Os planos de treinamento são vinculados a sessões de exercício. Portanto, o usuário precisa conceder permissão para usar cada tipo de registro relacionado a um plano de treinamento para utilizar totalmente esse recurso da Conexão Saúde.

Por exemplo, se um plano de treinamento medir a frequência cardíaca de um usuário durante uma série de corridas, as seguintes permissões talvez precisem ser declaradas pelo desenvolvedor e concedidas pelo usuário para gravar a sessão de exercício e ler os resultados para avaliação posterior:

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

No entanto, muitas vezes o app que cria planos de treinamento e avalia o desempenho em relação aos planos não é o mesmo que o app que consome planos de treinamento e grava dados de exercícios reais. Dependendo do tipo de app, nem todas as permissões de leitura e gravação são necessárias. Por exemplo, talvez você só precise dessas permissões para cada tipo de app:

App do plano de treinamento App de treino
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTE WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Informações incluídas em um registro de sessão de exercício planejada

  • Título da sessão.
  • Uma lista de blocos de exercícios planejados.
  • Horário de início e término da sessão.
  • Tipo de exercício.
  • Anotações para a atividade.
  • Metadados.
  • ID da sessão de exercício concluída: é gravado automaticamente após a conclusão de uma sessão de exercício relacionada a essa sessão de exercício planejada.

Informações incluídas em um registro de bloco de exercício planejado

Um bloco de exercício planejado contém uma lista de etapas de exercício para apoiar a repetição de diferentes grupos de etapas (por exemplo, faça uma sequência de flexões de braço, burpees e abdominais cinco vezes seguidas).

Informações incluídas em um registro de etapa de exercício planejado

Agregações compatíveis

Não há agregações compatíveis com esse tipo de dados.

Exemplo de uso

Suponha que um usuário planeje uma corrida de 90 minutos daqui a dois dias. Esta corrida terá três voltas ao redor de um lago com uma frequência cardíaca desejada entre 90 e 110 bpm.

  1. Uma sessão de exercício planejada com o seguinte é definida pelo usuário em um app de plano de treinamento:
    1. Início e término planejados da corrida
    2. O tipo de exercício (corrida)
    3. Número de voltas (repetições)
    4. Meta de desempenho para a frequência cardíaca (entre 90 e 110 bpm)
  2. Essas informações são agrupadas em blocos de exercícios e etapas e gravadas no Conexão Saúde pelo app de plano de treinamento como PlannedExerciseSessionRecord.
  3. O usuário realiza a sessão planejada (em execução).
  4. Os dados de exercício relacionados à sessão são registrados de duas maneiras:
    1. Por um wearable durante a sessão. Por exemplo, a frequência cardíaca. Esses dados são gravados na Conexão Saúde como o tipo de registro da atividade. Nesse caso, HeartRateRecord.
    2. Manualmente pelo usuário após a sessão. Por exemplo, indicando o início e o fim da execução real. Esses dados são gravados na Conexão Saúde como uma ExerciseSessionRecord.
  5. Mais tarde, o app de plano de treinamento lê dados da Conexão Saúde para avaliar o desempenho real em relação às metas definidas pelo usuário na sessão de treino planejada.

Planejar exercícios e definir metas

Um usuário pode planejar o exercício no futuro e definir metas. Grave isso na Conexão Saúde como uma sessão de exercício planejada.

No exemplo descrito em Exemplo de uso, o usuário planeja uma corrida de 90 minutos daqui a dois dias. Esta corrida terá três voltas ao redor de um lago com uma frequência cardíaca alvo entre 90 e 110 bpm.

Um snippet como este pode ser encontrado no gerenciador de formulários de um app que registra sessões de exercícios planejadas na Conexão Saúde. Também pode ser encontrado no ponto de integração mais importante, por exemplo, com um serviço que oferece treinamento.

// Ensure 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 = ExerciseSessionRecord.EXERCISE_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
)
val insertedPlannedExerciseSessions =
    healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)).recordIdsList
val insertedPlannedExerciseSessionId = insertedPlannedExerciseSessions.first()

Registrar dados de exercícios e atividades

Dois dias depois, o usuário registra a sessão de exercício. Grave isso na Conexão Saúde como uma sessão de exercício.

Neste exemplo, a duração da sessão do usuário correspondeu exatamente à duração planejada.

O snippet a seguir pode ser encontrado no gerenciador de formulários de um app que registra sessões de exercícios no Conexão Saúde. Ele também pode ser encontrado em processadores de exportação e ingestão de dados para um wearable capaz de detectar e registrar sessões de exercícios.

insertedPlannedExerciseSessionId aqui é reutilizado do exemplo anterior. Em um app real, o ID seria determinado pelo usuário selecionando uma sessão de exercício planejada em uma lista de sessões.

// Ensure 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,
)
val insertedExerciseSessions =
    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))

Um wearable também registra a frequência cardíaca durante a corrida. O snippet a seguir pode ser usado para gerar registros dentro do intervalo de destino.

Em um app real, as partes principais desse snippet podem ser encontradas no gerenciador de uma mensagem de um wearable, que grava a medição no Conexão Saúde após a coleta.

// Ensure 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,
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))

Avaliar metas de performance

No dia seguinte ao treino do usuário, você pode recuperar o exercício registrado, verificar as metas de exercício planejadas e avaliar outros tipos de dados para determinar se as metas definidas foram alcançadas.

Um snippet como esse provavelmente seria encontrado em um job periódico para avaliar metas de desempenho ou ao carregar uma lista de exercícios e exibir uma notificação sobre metas de desempenho em um app.

// Ensure 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
                            }
                        }
                    }
                }
            }
        }
    }
}