Este guia é compatível com a versão 1.1.0-alpha12 ou mais recente do app Conexão Saúde.
Os trajetos de exercícios permitem que os usuários acompanhem por GPS o trajeto das atividades físicas associadas e compartilhem mapas dos treinos deles com outros apps.
Verificar a disponibilidade do app Conexão Saúde
Antes de tentar usar o Conexão Saúde, seu app precisa verificar se ele está disponível
no dispositivo do usuário. A Conexão Saúde pode não estar pré-instalada em todos os dispositivos ou pode estar desativada.
É possível verificar a disponibilidade usando o método
HealthConnectClient.getSdkStatus().
Como verificar a disponibilidade do Conexão Saúde
fun checkHealthConnectAvailability(context: Context) { val providerPackageName = "com.google.android.apps.healthdata" val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName) if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) { // Health Connect is not available. Guide the user to install/enable it. // For example, show a dialog. return // early return as there is no viable integration } if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) { // Health Connect is available but requires an update. // Optionally redirect to package installer to find a provider, for example: val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding" context.startActivity( Intent(Intent.ACTION_VIEW).apply { setPackage("com.android.vending") data = Uri.parse(uriString) putExtra("overlay", true) putExtra("callerId", context.packageName) } ) return } // Health Connect is available, obtain a HealthConnectClient instance val healthConnectClient = HealthConnectClient.getOrCreate(context) // Issue operations with healthConnectClient }
Dependendo do status retornado por getSdkStatus(), você pode orientar o usuário
a instalar ou atualizar o Conexão Saúde na Google Play Store, se necessário.
Este guia mostra como solicitar permissões ao usuário e descreve como os apps recebem a permissão para gravar dados de trajeto como parte de uma sessão de exercício.
A funcionalidade de leitura e gravação de trajetos de exercícios inclui:
- Os apps criam uma nova permissão de gravação para trajetos de exercícios.
- A inserção acontece com a criação de uma sessão de exercício com um trajeto como campo.
- Leitura:
- Para o proprietário da sessão, os dados são acessados usando uma leitura de sessão.
- Em um app de terceiros, em uma caixa de diálogo que permite ao usuário conceder uma leitura única de um trajeto.
Se o usuário não tiver permissões de gravação e o trajeto não estiver definido, ele não será atualizado.
Se o app tiver uma permissão de gravação de trajeto e tentar atualizar uma sessão transmitindo um objeto de sessão sem um trajeto, o trajeto atual será excluído.
Disponibilidade do recurso
Para determinar se o dispositivo de um usuário é compatível com exercícios planejados no app 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
}
Permissões necessárias
O acesso ao trajeto do exercício é protegido pelas seguintes permissões:
android.permission.health.READ_EXERCISE_ROUTESandroid.permission.health.WRITE_EXERCISE_ROUTE
READ_EXERCISE_ROUTES é plural, enquanto
WRITE_EXERCISE_ROUTE é singular.
Para adicionar o recurso de trajeto de exercício ao app, solicite
permissões para o tipo de dados ExerciseSession.
Confira a permissão necessária para poder gravar trajetos de exercícios:
<application>
<uses-permission
android:name="android.permission.health.WRITE_EXERCISE_ROUTE" />
...
</application>
Para ler o trajeto do exercício, você precisa solicitar as seguintes permissões:
<application>
<uses-permission
android:name="android.permission.health.READ_EXERCISE_ROUTES" />
...
</application>
Você também precisa declarar uma permissão de exercício, já que cada trajeto está associado a uma sessão (o que é igual a um treino).
Para solicitar permissões, use o
método PermissionController.createRequestPermissionResultContract() ao
conectar seu app ao Conexão Saúde pela primeira vez. Várias permissões que podem
ser solicitadas são:
- Ler dados de saúde e condicionamento físico, incluindo dados de trajetos:
HealthPermission.getReadPermission(ExerciseSessionRecord::class) - Gravar dados de saúde e condicionamento físico, incluindo dados de trajetos:
HealthPermission.getWritePermission(ExerciseSessionRecord::class) - Gravar dados de trajeto de exercício:
HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE
Solicitar permissões do usuário
Depois de criar uma instância de cliente, seu app precisa solicitar permissões aos usuários. Os usuários precisam concedê-las ou negá-las a qualquer momento. Para fazer isso, crie um conjunto de permissões para os tipos de dados necessários. Verifique se as permissões no conjunto foram declaradas no manifesto do Android primeiro.
val permissions = setOf( HealthPermission.getReadPermission(ExerciseSessionRecord::class), HealthPermission.getWritePermission(ExerciseSessionRecord::class) )
getGrantedPermissions
para verificar se o app já tem as permissões necessárias concedidas. Caso contrário, use
createRequestPermissionResultContract
para solicitá-las. Isso mostra a tela de permissões da Conexão Saúde.
val permissions = setOf( HealthPermission.getReadPermission(StepsRecord::class), HealthPermission.getWritePermission(StepsRecord::class), HealthPermission.getReadPermission(HeartRateRecord::class), HealthPermission.getWritePermission(HeartRateRecord::class) ) val requestPermissionsLauncher = rememberLauncherForActivityResult( contract = PermissionController.createRequestPermissionResultContract() ) { grantedPermissions -> if (grantedPermissions.containsAll(permissions)) { coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") } } else { coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") } } }
Informações incluídas em um registro de sessão de exercício
Cada registro de sessão de exercício contém as seguintes informações:
- O tipo de exercício, por exemplo, ciclismo.
- O trajeto do exercício, que contém informações como latitude, longitude e altitude.
Agregações compatíveis
Os seguintes valores agregados estão disponíveis para
ExerciseSessionRecord:
Exemplo de uso
Os snippets de código abaixo mostram como ler e gravar um trajeto de exercício.
Ler trajeto do exercício
O app não pode ler dados de percurso de exercícios criados por outros apps quando é executado em segundo plano.
Quando o app é executado em segundo plano e tenta ler um trajeto de exercício criado
por outro app, o Conexão Saúde retorna uma resposta
ExerciseRouteResult.ConsentRequired, mesmo que o app tenha acesso Sempre permitir aos dados de trajeto
de exercício.
Por esse motivo, recomendamos que você solicite trajetos após uma interação deliberada do usuário com seu app, quando ele estiver interagindo ativamente com a interface do app.
Para saber mais sobre leituras em segundo plano, consulte Exemplo de leitura em segundo plano.
O snippet de código abaixo demonstra como ler uma sessão na Conexão Saúde e solicitar um trajeto dessa sessão:
private suspend fun readExerciseSessionAndRoute() { val client = healthConnectClient ?: return val endTime = Instant.now() val startTime = endTime.minus(Duration.ofHours(1)) val grantedPermissions = client.permissionController.getGrantedPermissions() // 1. Verify basic Exercise Session permissions if (!grantedPermissions.contains( HealthPermission.getReadPermission(ExerciseSessionRecord::class) ) ) { return } // 2. Read the sessions val readResponse = client.readRecords( ReadRecordsRequest( ExerciseSessionRecord::class, TimeRangeFilter.between(startTime, endTime) ) ) val exerciseRecord = readResponse.records.firstOrNull() ?: return val recordId = exerciseRecord.metadata.id // 3. Read the specific record to check for the route val sessionResponse = client.readRecord(ExerciseSessionRecord::class, recordId) // 4. Handle the Route Result directly from the response when (val routeResult = sessionResponse.record.exerciseRouteResult) { is ExerciseRouteResult.Data -> { displayExerciseRoute(routeResult.exerciseRoute) } is ExerciseRouteResult.ConsentRequired -> { // Since you are in a Service, you cannot launch ActivityResultLauncher. // Send a notification to the user to grant route-specific consent. handleConsentRequired(recordId) } is ExerciseRouteResult.NoData -> Unit else -> Unit } } private fun displayExerciseRoute(route: ExerciseRoute) { val locations = route.route.orEmpty() for (location in locations) { println(location) } }
Gravar um trajeto de exercício
O código a seguir demonstra como gravar uma sessão que inclui um trajeto de exercício:
private suspend fun insertExerciseRoute() { val client = healthConnectClient ?: return val grantedPermissions = client.permissionController.getGrantedPermissions() // 1. Verify Session Write Permission val hasWriteSession = grantedPermissions.contains( HealthPermission.getWritePermission(ExerciseSessionRecord::class) ) if (!hasWriteSession) return val sessionStartTime = Instant.now() val sessionDuration = Duration.ofMinutes(20) val sessionEndTime = sessionStartTime.plus(sessionDuration) // 2. Build the route if route-specific write permission is granted val hasWriteRoute = grantedPermissions.contains(HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE) val exerciseRoute = if (hasWriteRoute) { ExerciseRoute( listOf( ExerciseRoute.Location( time = sessionStartTime, latitude = 6.5483, longitude = 0.5488, horizontalAccuracy = Length.meters(2.0), verticalAccuracy = Length.meters(2.0), altitude = Length.meters(9.0), ), ExerciseRoute.Location( time = sessionEndTime.minusSeconds(1), latitude = 6.4578, longitude = 0.6577, horizontalAccuracy = Length.meters(2.0), verticalAccuracy = Length.meters(2.0), altitude = Length.meters(9.2), ) ) ) } else { null } // 3. Create the session record val exerciseSessionRecord = ExerciseSessionRecord( startTime = sessionStartTime, startZoneOffset = ZoneOffset.UTC, endTime = sessionEndTime, endZoneOffset = ZoneOffset.UTC, exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING, title = "Morning Bike Ride", exerciseRoute = exerciseRoute, metadata = Metadata.activelyRecorded( device = Device(type = Device.TYPE_PHONE) ) ) // 4. Insert into Health Connect client.insertRecords(listOf(exerciseSessionRecord)) }
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",
metadata = Metadata.manualEntry()
),
// ... 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,
metadata = Metadata.manualEntry()
)
Excluir uma sessão de exercícios
Há duas maneiras de excluir uma sessão de exercícios:
- Por período.
- Por UID.
Confira como excluir dados de subtipo de acordo com o período:
suspend fun deleteExerciseSessionByTimeRange( healthConnectClient: HealthConnectClient, exerciseRecord: ExerciseSessionRecord, ) { val timeRangeFilter = TimeRangeFilter.between(exerciseRecord.startTime, exerciseRecord.endTime) healthConnectClient.deleteRecords(ExerciseSessionRecord::class, timeRangeFilter) // delete the associated distance record healthConnectClient.deleteRecords(DistanceRecord::class, timeRangeFilter) }
Também é possível excluir dados de subtipo pelo UID. Isso exclui apenas a sessão de exercícios, não os dados associados:
suspend fun deleteExerciseSessionByUid( healthConnectClient: HealthConnectClient, exerciseRecord: ExerciseSessionRecord, ) { healthConnectClient.deleteRecords( ExerciseSessionRecord::class, recordIdsList = listOf(exerciseRecord.metadata.id), clientRecordIdsList = emptyList() ) }