Este guia é compatível com a versão 1.1.0-alpha11 do Health Connect.
A Conexão Saúde oferece um tipo de dados de exercício planejado para permitir que apps de treino gravem planos de treino e que apps de exercícios leiam esses planos. Os exercícios gravados (treinos) podem ser lidos para uma análise de desempenho personalizada e ajudar os usuários a alcançar as metas de treino.
Disponibilidade do recurso
Para determinar se o dispositivo de um usuário é compatível com planos de treino no Conexão Saúde, verifique a disponibilidade deFEATURE_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 ao exercício planejado é protegido pelas seguintes permissões:
android.permission.health.READ_PLANNED_EXERCISE
android.permission.health.WRITE_PLANNED_EXERCISE
Para adicionar a capability de exercício planejado ao app, solicite permissões de
gravação do tipo de dado PlannedExerciseSession
.
Confira a permissão necessária para poder gravar exercícios planejados:
<application>
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
Para ler o exercício planejado, solicite as seguintes permissões:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
...
</application>
Solicitar permissões do usuário
Depois de criar uma instância de cliente, seu app precisa solicitar permissões do usuário. Os usuários precisam poder conceder ou negar permissões a qualquer momento.
Para isso, crie um conjunto de permissões para os tipos de dados necessários. Verifique se as permissões no conjunto foram declaradas primeiro no manifesto do 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)
)
Use getGrantedPermissions
para verificar se o app já tem as
permissões necessárias concedidas. Caso contrário, use
createRequestPermissionResultContract
para solicitar
essas permissões. Isso mostra a tela de permissões da Conexão Saúde.
// 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)
}
}
Como os usuários podem conceder ou revogar permissões a qualquer momento, seu app precisa verificar periodicamente as permissões concedidas e lidar com cenários em que a permissão é perdida.
Permissões relacionadas
Os planos de treino são vinculados a sessões de exercícios. Portanto, o usuário precisa dar permissão para usar cada tipo de registro relacionado a um plano de treino para aproveitar ao máximo esse recurso do 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, o desenvolvedor precisará declarar e o usuário precisará conceder as seguintes permissões para gravar a sessão de exercícios e ler os resultados para avaliação posterior:
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
No entanto, muitas vezes, o app que cria planos de treinamento e avalia a performance em relação a eles não é o mesmo 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 serão necessárias. Por exemplo, talvez você só precise destas permissões para cada tipo de app:
App de plano de treinamento | App de treino |
---|---|
WRITE_PLANNED_EXERCISE |
READ_PLANNED_EXERCISE |
READ_EXERCISE |
WRITE_EXERCISE |
READ_EXERCISE_ROUTES |
WRITE_EXERCISE_ROUTE |
READ_HEART_RATE |
WRITE_HEART_RATE |
Informações incluídas em um registro de sessão de exercícios 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.
- Observações sobre a atividade.
- Metadados.
- ID da sessão de exercícios concluída: é gravado automaticamente após a conclusão de uma sessão de exercícios relacionada à sessão planejada.
Informações incluídas em um registro de bloco de exercícios planejados
Um bloco de exercícios planejado contém uma lista de etapas 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).
- Descrição do bloco.
- Uma lista de etapas de exercícios planejados.
- Número de repetições.
Informações incluídas em um registro de etapa de exercício planejado
- Descrição da etapa.
- Categoria de exercício.
- Tipo de exercício.
- Uma lista de metas de performance.
- Meta de conclusão.
Agregações compatíveis
Não há agregações compatíveis para esse tipo de dado.
Exemplo de uso
Suponha que um usuário planeje uma corrida de 90 minutos para daqui a dois dias. Essa corrida terá três voltas em torno de um lago com uma meta de frequência cardíaca entre 90 e 110 bpm.
- Uma sessão de exercícios planejada com o seguinte é definida pelo usuário em
um app de plano de treino:
- Início e término planejados da execução
- O tipo de exercício (corrida)
- Número de voltas (repetições)
- Meta de desempenho para frequência cardíaca (entre 90 e 110 bpm)
- 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 treino como um
PlannedExerciseSessionRecord
. - O usuário realiza a sessão planejada (em execução).
- Os dados de exercícios relacionados à sessão são registrados de uma destas formas:
- Por um wearable durante a sessão. Por exemplo, frequência cardíaca.
Esses dados são gravados na Conexão Saúde como o tipo de registro da
atividade. Nesse caso,
HeartRateRecord
. - 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 um
ExerciseSessionRecord
.
- Por um wearable durante a sessão. Por exemplo, frequência cardíaca.
Esses dados são gravados na Conexão Saúde como o tipo de registro da
atividade. Nesse caso,
- Mais tarde, o app de plano de treino lê dados da Conexão Saúde para avaliar a performance real em relação às metas definidas pelo usuário na sessão de exercícios planejada.
Planejar exercícios e definir metas
Um usuário pode planejar o exercício para o futuro e definir metas. Grave isso na Conexão Saúde como uma sessão de exercícios planejada.
No exemplo descrito em Exemplo de uso, o usuário planeja uma corrida de 90 minutos para daqui a dois dias. Essa corrida terá três voltas em torno de um lago com uma frequência cardíaca alvo entre 90 e 110 bpm.
Um snippet como este pode ser encontrado no manipulador de formulários de um app que registra sessões de exercícios planejadas no Conexão Saúde. Também pode ser encontrado no ponto de ingestão para integrações, por exemplo, com um serviço que oferece treinamento.
// 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()
Registrar dados de exercícios e atividades
Dois dias depois, o usuário registra a sessão de exercícios real. Grave isso na Conexão Saúde como uma sessão de exercícios.
Neste exemplo, a duração da sessão do usuário correspondeu exatamente à duração planejada.
O snippet a seguir pode ser encontrado no manipulador de formulários de um app que registra sessões de exercícios no Conexão Saúde. Também pode ser encontrado em manipuladores de ingestão e exportaçã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 ao selecionar uma sessão de exercício planejada
em uma lista de sessões existentes.
// 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))
Um dispositivo wearable também registra a frequência cardíaca durante a corrida. O snippet a seguir pode ser usado para gerar registros no intervalo de destino.
Em um app real, as principais partes desse snippet podem ser encontradas no manipulador de uma mensagem de um wearable, que gravaria a medição no Conexão Saúde após a coleta.
// 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))
Avaliar metas de performance
No dia seguinte ao treino do usuário, é possível recuperar o exercício registrado, verificar se há metas de exercícios planejadas e avaliar outros tipos de dados para determinar se as metas definidas foram atingidas.
Um snippet como este provavelmente seria encontrado em um job periódico para avaliar metas de performance ou ao carregar uma lista de exercícios e mostrar uma notificação sobre metas de performance em um app.
// 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
}
}
}
}
}
}
}
}
Sessões de exercícios
As sessões de exercícios podem incluir várias atividades, de corrida a badminton.
Gravar sessões de exercícios
Confira como criar uma solicitação de inserção que inclui uma sessão:
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
)
)
}
Ler uma sessão de exercícios
Confira um exemplo de como ler uma sessão de exercícios:
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
}
}
Gravar dados de subtipo
As sessões também podem ser compostas por dados de subtipo opcionais, que fornecem mais informações para melhorar a sessão.
Por exemplo, as sessões de exercício podem incluir as classes ExerciseSegment
, ExerciseLap
e 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
)