Agregar rutas de ejercicio

Esta guía es compatible con Health Connect versión 1.1.0-alpha12.

Las rutas de ejercicio permiten a los usuarios registrar una ruta de GPS de las actividades asociadas y compartir mapas de sus entrenamientos con otras apps.

Verifica la disponibilidad de Health Connect

Antes de intentar usar Health Connect, tu app debe verificar que esté disponible en el dispositivo del usuario. Es posible que Health Connect no esté preinstalado en todos los dispositivos o que esté inhabilitado. Puedes verificar la disponibilidad con el método HealthConnectClient.getSdkStatus().

Cómo verificar la disponibilidad de Health Connect

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    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
}

Según el estado que muestra getSdkStatus(), puedes guiar al usuario para que instale o actualice Health Connect desde Google Play Store si es necesario.

En esta guía, se proporciona información para solicitar permisos al usuario y se describe cómo las apps reciben permiso para escribir datos de ruta como parte de una sesión de ejercicio.

La función de lectura y escritura de las rutas de ejercicio incluye lo siguiente:

  1. Las apps crean un nuevo permiso de escritura para las rutas de ejercicio.
  2. La inserción se lleva a cabo escribiendo una sesión de ejercicio con una ruta como su campo.
  3. Lectura:
    1. El propietario de la sesión puede acceder a los datos con una lectura de la sesión
    2. Desde una app de terceros, a través de un diálogo que permite al usuario otorgar una lectura única de una ruta

Si el usuario no tiene permisos de escritura y no está configurada la ruta, esta no se actualiza.

Si tu app tiene un permiso de escritura de ruta y trata de actualizar una sesión pasando un objeto de sesión sin una ruta, se borra la ruta existente.

Disponibilidad de funciones

Para determinar si el dispositivo de un usuario admite el ejercicio planificado en Health Connect, verifica la disponibilidad de FEATURE_PLANNED_EXERCISE en el cliente:

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

  // Feature is available
} else {
  // Feature isn't available
}
Consulta Cómo verificar la disponibilidad de funciones para obtener más información.

Permisos necesarios

El acceso a la ruta de ejercicio está protegido por los siguientes permisos:

  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.WRITE_EXERCISE_ROUTE
Nota: Para este tipo de permiso, READ_EXERCISE_ROUTES está en plural, mientras que WRITE_EXERCISE_ROUTE está en singular.

Si deseas agregar la función de ruta de ejercicio a tu app, comienza por solicitar permisos para el tipo de datos ExerciseSession.

Este es el permiso que debes declarar para poder escribir rutas de ejercicio:

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

Para leer la ruta de ejercicio, debes solicitar los siguientes permisos:

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

También debes declarar un permiso de ejercicio, ya que cada ruta está asociada con una sesión de ejercicio (una sesión = un entrenamiento).

Para solicitar permisos, usa el método PermissionController.createRequestPermissionResultContract() cuando conectes tu app por primera vez a Health Connect. Estos son varios permisos que puedes solicitar:

  • Leer datos de salud y actividad física, incluidos los datos de ruta: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Escribir datos de actividad física y salud, incluidos los datos de ruta: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • Escribir datos de ruta de ejercicio: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

Solicita permisos al usuario

Después de crear una instancia de cliente, tu app debe solicitarle permisos al usuario. Los usuarios deben poder otorgar o rechazar permisos en cualquier momento.

Para hacerlo, crea un conjunto de permisos para los tipos de datos necesarios. Primero, asegúrate de que los permisos del conjunto se declaren en tu manifiesto de Android.

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

Usa getGrantedPermissions para ver si tu app ya tiene otorgados los permisos necesarios. De lo contrario, usa createRequestPermissionResultContract para solicitar esos permisos. Se mostrará la pantalla de permisos de Health Connect.

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

Debido a que los usuarios pueden otorgar o revocar permisos en cualquier momento, tu app debe verificarlos cada vez antes de usarlos y controlar las situaciones en las que se pierden los permisos.

Información incluida en un registro de sesión de ejercicio

Cada registro de sesión de ejercicio contiene la siguiente información:

  • El tipo de ejercicio, por ejemplo, andar en bicicleta
  • La ruta de ejercicio, que contiene información como latitud, longitud y altitud.

Agregaciones admitidas

Los siguientes valores agregados están disponibles para ExerciseSessionRecord:

Ejemplo de uso

En los siguientes fragmentos de código, se muestra cómo leer y escribir una ruta de ejercicio.

Leer la ruta de ejercicio

Tu app no puede leer los datos de la ruta de ejercicio creados por otras apps cuando se ejecuta en segundo plano.

Cuando tu app se ejecuta en segundo plano y trata de leer una ruta de ejercicio creada por otra app, Health Connect muestra una respuesta ExerciseRouteResult.ConsentRequired, incluso si tu app tiene acceso de Permitir siempre a los datos de la ruta de ejercicio.

Por este motivo, te recomendamos que solicites rutas cuando el usuario interactúe de forma deliberada con tu app, cuando esté participando activamente en la IU de tu app.

Para obtener más información sobre las lecturas en segundo plano, consulta el ejemplo de lectura en segundo plano.

En el siguiente fragmento de código, se muestra cómo leer una sesión en Health Connect y solicitar una ruta desde esa sesión:

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)
    }
}

Escribe una ruta de ejercicio

En el siguiente código, se muestra cómo registrar una sesión que incluye una ruta de ejercicio:

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(
            device = Device(type = Device.TYPE_PHONE)
        )
    )

    // 4. Insert into Health Connect
    client.insertRecords(listOf(exerciseSessionRecord))
}

Sesiones de ejercicio

Las sesiones de ejercicio pueden incluir cualquier actividad, desde correr hasta jugar bádminton.

Cómo escribir sesiones de ejercicio

A continuación, se muestra cómo compilar una solicitud de inserción que incluya una sesión:

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
        )
    )
}

Cómo leer una sesión de ejercicio

Este es un ejemplo de cómo leer una sesión de ejercicio:

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

Cómo escribir datos de subtipos

Las sesiones también pueden estar compuestas por datos de subtipo opcionales, que enriquecen la sesión con información adicional.

Por ejemplo, las sesiones de ejercicio pueden incluir las clases ExerciseSegment, ExerciseLap y 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()
)

Cómo borrar una sesión de ejercicio

Existen dos maneras de borrar una sesión de ejercicio:

  1. Por intervalo de tiempo
  2. Por UID

A continuación, te mostramos cómo borrar los datos de subtipo según el intervalo de tiempo:

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)
}

También puedes borrar datos de subtipo por UID. Esto solo borra la sesión de ejercicio, no los datos asociados:

suspend fun deleteExerciseSessionByUid(
    healthConnectClient: HealthConnectClient,
    exerciseRecord: ExerciseSessionRecord,
) {
    healthConnectClient.deleteRecords(
        ExerciseSessionRecord::class,
        recordIdsList = listOf(exerciseRecord.metadata.id),
        clientRecordIdsList = emptyList()
    )
}