Adicionar trajetos de exercícios

Este guia é compatível com a versão 1.1.0-alpha12 do Health Connect.

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.

Disponibilidade do recurso

Para determinar se o dispositivo de um usuário é compatível com trajetos de exercícios no 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.

Este guia fornece informações sobre como solicitar permissões do usuário e também descreve como os apps recebem 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:

  1. Os apps criam uma nova permissão de gravação para trajetos de exercícios.
  2. A inserção acontece com a criação de uma sessão de exercício com um trajeto como campo.
  3. Leitura:
    1. Para o proprietário da sessão, os dados são acessados usando uma leitura de sessão.
    2. 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.

Permissões necessárias

O acesso à rota de exercícios é protegido pelas seguintes permissões:

  • android.permission.health.READ_EXERCISE_ROUTE
  • android.permission.health.WRITE_EXERCISE_ROUTE

Para adicionar a capability de trajeto de exercício ao app, solicite permissões de gravação do tipo de dado 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 de um exercício, solicite as seguintes permissões:

<application>
  <uses-permission
android:name="android.permission.health.READ_EXERCISE_ROUTE" />
...
</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 integridade, incluindo dados de trajetos: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Gravar dados de saúde, 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 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(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.

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

Cada registro de sessão de exercícios 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

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

Exemplo de uso

Solicitar ou gravar um trajeto em uma sessão.

Solicitar um trajeto em uma sessão

Aprenda a ler uma sessão na Conexão Saúde e solicitar um trajeto nessa sessão:

suspend fun readExerciseSessionAndRoute() {
    val endTime = Instant.now()
    val startTime = endTime.minus(Duration.ofHours(1))

    val grantedPermissions =
        healthConnectClient.permissionController.getGrantedPermissions()
    if (!grantedPermissions.contains(
          HealthPermission.getReadPermission(ExerciseSessionRecord::class))) {
        // The user doesn't allow the app to read exercise session data.
        return
    }

    val readResponse =
      healthConnectClient.readRecords(
        ReadRecordsRequest(
          ExerciseSessionRecord::class,
          TimeRangeFilter.between(startTime, endTime)
        )
      )
    val exerciseRecord = readResponse.records.first()
    val recordId = exerciseRecord.metadata.id

    // See https://developer.android.com/training/basics/intents/result#launch
    // for appropriately handling ActivityResultContract.
    val requestExerciseRouteLauncher = fragment.registerForActivityResul
    (ExerciseRouteRequestContract()) { exerciseRoute: ExerciseRoute? ->
            if (exerciseRoute != null) {
                displayExerciseRoute(exerciseRoute)
            } else {
                // Consent was denied
            }
        }

    val exerciseSessionRecord =
      healthConnectClient.readRecord(ExerciseSessionRecord::class, recordId).record

    when (val exerciseRouteResult = exerciseSessionRecord.exerciseRouteResult) {
        is ExerciseRouteResult.Data ->
            displayExerciseRoute(exerciseRouteResult.exerciseRoute)
        is ExerciseRouteResult.ConsentRequired ->
            requestExerciseRouteLauncher.launch(recordId)
        is ExerciseRouteResult.NoData -> Unit // No exercise route to show
        else -> Unit
    }
  }

  fun displayExerciseRoute(route: ExerciseRoute?) {
    val locations = route.route.orEmpty()
    for (location in locations) {
      // Handle location.
    }
  }

Gravar um trajeto em uma sessão

O código a seguir demonstra como gravar uma sessão que inclui um trajeto de exercício:

suspend fun InsertExerciseRoute(healthConnectClient: HealthConnectClient) {
    val grantedPermissions =
        healthConnectClient.permissionController.getGrantedPermissions()
    if (!grantedPermissions.contains(
          getWritePermission(ExerciseSessionRecord::class))) {
        // The user doesn't allow the app to write exercise session data.
        return
    }

    val sessionStartTime = Instant.now()
    val sessionDuration = Duration.ofMinutes(20)
    val sessionEndTime = sessionStartTime.plus(sessionDuration)

    val exerciseRoute =
        if (grantedPermissions.contains(PERMISSION_WRITE_EXERCISE_ROUTE)) ExerciseRoute(
            listOf(
                ExerciseRoute.Location(
                    // Location times must be on or after the session start time
                    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(
                    // Location times must be before the session end time
                    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
        // The user doesn't allow the app to write exercise route data.
            null
    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.manualEntry(
            device = Device(type = Device.TYPE_PHONE)
        ),
    )
    val response = healthConnectClient.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"
            ),
            // ... 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
)

Excluir uma sessão de exercícios

Há duas maneiras de excluir uma sessão de exercícios:

  1. Por período.
  2. 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()
    )
}