Piani di allenamento

Questa guida è compatibile con la versione 1.1.0-alpha11 di Connessione Salute.

Connessione Salute fornisce un tipo di dati esercizio pianificato per consentire alle app di allenamento di scrivere piani di allenamento e alle app di allenamento di leggere i piani di allenamento. Gli esercizi registrati (allenamenti) possono essere riletti per un'analisi personalizzata delle prestazioni per aiutare gli utenti a raggiungere i loro obiettivi di allenamento.

Disponibilità della funzionalità

Per determinare se il dispositivo di un utente supporta i piani di allenamento su Health Connect, controlla la disponibilità di FEATURE_PLANNED_EXERCISE sul client:

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

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

Per saperne di più, consulta la sezione Verificare la disponibilità delle funzionalità.

Autorizzazioni richieste

L'accesso all'allenamento pianificato è protetto dalle seguenti autorizzazioni:

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

Per aggiungere la funzionalità di allenamento pianificato alla tua app, inizia richiedendo le autorizzazioni di scrittura per il tipo di dati PlannedExerciseSession.

Ecco l'autorizzazione che devi dichiarare per poter scrivere l'allenamento pianificato:

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

Per leggere l'esercizio pianificato, devi richiedere le seguenti autorizzazioni:

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

Richiedere le autorizzazioni all'utente

Dopo aver creato un'istanza client, la tua app deve richiedere le autorizzazioni all'utente. Gli utenti devono poter concedere o negare le autorizzazioni in qualsiasi momento.

A questo scopo, crea un insieme di autorizzazioni per i tipi di dati richiesti. Assicurati che le autorizzazioni nel set siano dichiarate prima nel file manifest di 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)
)

Utilizza getGrantedPermissions per verificare se la tua app dispone già delle autorizzazioni richieste. In caso contrario, utilizza createRequestPermissionResultContract per richiedere queste autorizzazioni. Viene visualizzata la schermata delle autorizzazioni di Connessione Salute.

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

Poiché gli utenti possono concedere o revocare le autorizzazioni in qualsiasi momento, la tua app deve controllare periodicamente le autorizzazioni concesse e gestire gli scenari in cui l'autorizzazione viene persa.

I piani di allenamento sono collegati alle sessioni di allenamento. Pertanto, l'utente deve concedere l'autorizzazione a utilizzare ogni tipo di record correlato a un piano di allenamento per sfruttare appieno questa funzionalità di Connessione Salute.

Ad esempio, se un piano di allenamento misura la frequenza cardiaca di un utente durante una serie di corse, lo sviluppatore potrebbe dover dichiarare le seguenti autorizzazioni e l'utente potrebbe doverle concedere per scrivere la sessione di allenamento e leggere i risultati per una valutazione successiva:

  • 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

Tuttavia, spesso l'app che crea i piani di allenamento e valuta le prestazioni rispetto ai piani non è la stessa che utilizza i piani di allenamento e scrive i dati di allenamento effettivi. A seconda del tipo di app, non saranno necessarie tutte le autorizzazioni di lettura e scrittura. Ad esempio, potresti aver bisogno di queste autorizzazioni per ogni tipo di app:

App per piani di allenamento App Allenamento
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Informazioni incluse in un record di sessione di allenamento pianificata

  • Titolo della sessione.
  • Un elenco di blocchi di allenamento pianificati.
  • Ora di inizio e di fine della sessione.
  • Tipo di esercizio.
  • Note per l'attività.
  • Metadati.
  • ID sessione di allenamento completata: viene scritto automaticamente al termine di una sessione di allenamento correlata a questa sessione di allenamento pianificata.

Informazioni incluse in un record di blocco di esercizio pianificato

Un blocco di allenamento pianificato contiene un elenco di passaggi di allenamento per supportare la ripetizione di diversi gruppi di passaggi (ad esempio, esegui una sequenza di flessioni per i bicipiti, burpee e crunch cinque volte di seguito).

Informazioni incluse in un record di passo di esercizio pianificato

Aggregazioni supportate

Non sono presenti aggregazioni supportate per questo tipo di dati.

Esempio di utilizzo

Supponiamo che un utente pianifichi una corsa di 90 minuti tra due giorni. Questa corsa prevede tre giri intorno a un lago con una frequenza cardiaca target compresa tra 90 e 110 battiti al minuto.

  1. Una sessione di allenamento pianificata con quanto segue è definita dall'utente in un'app per piani di allenamento:
    1. Inizio e fine pianificati della corsa
    2. Il tipo di esercizio (corsa)
    3. Numero di giri (ripetizioni)
    4. Obiettivo di rendimento per il battito cardiaco (tra 90 e 110 bpm)
  2. Queste informazioni vengono raggruppate in blocchi di allenamento e passi e scritte in Connessione Salute dall'app del piano di allenamento come PlannedExerciseSessionRecord.
  3. L'utente esegue la sessione pianificata (in esecuzione).
  4. I dati di allenamento relativi alla sessione vengono registrati:
    1. Da un dispositivo indossabile durante la sessione. Ad esempio, il battito cardiaco. Questi dati vengono scritti in Connessione Salute come tipo di record per l'attività. In questo caso, HeartRateRecord.
    2. Manualmente dall'utente dopo la sessione. Ad esempio, indicando l'inizio e la fine della corsa effettiva. Questi dati vengono scritti in Connessione Salute come ExerciseSessionRecord.
  5. In un secondo momento, l'app del piano di allenamento legge i dati da Connessione Salute per valutare le prestazioni effettive rispetto agli obiettivi impostati dall'utente nella sessione di allenamento pianificata.

Pianificare gli esercizi e impostare i target

Un utente può pianificare il proprio allenamento in futuro e impostare obiettivi. Scrivi questo in Connessione Salute come sessione di allenamento pianificata.

Nell'esempio descritto in Esempio di utilizzo, l'utente pianifica una corsa di 90 minuti tra due giorni. Questa corsa prevede tre giri intorno a un lago con una frequenza cardiaca target compresa tra 90 e 110 battiti al minuto.

Uno snippet come questo può essere trovato nel gestore di moduli per un'app che registra le sessioni di allenamento pianificate in Connessione Salute. Potrebbe anche trovarsi nel punto di inserimento per le integrazioni, ad esempio con un servizio che offre formazione.

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

Registrare i dati di attività ed esercizio

Due giorni dopo, l'utente registra la sessione di allenamento effettiva. Scrivi questo dato su Connessione Salute come sessione di allenamento.

In questo esempio, la durata della sessione dell'utente corrisponde esattamente alla durata pianificata.

Il seguente snippet potrebbe essere trovato nel gestore del modulo per un'app che registra sessioni di allenamento in Connessione Salute. Potrebbe essere presente anche nei gestori di importazione ed esportazione dei dati per un dispositivo indossabile in grado di rilevare e registrare le sessioni di allenamento.

insertedPlannedExerciseSessionId viene riutilizzato dall'esempio precedente. In un'app reale, l'ID verrebbe determinato dall'utente che seleziona una sessione di allenamento pianificata da un elenco di sessioni esistenti.

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

Un dispositivo indossabile registra anche la frequenza cardiaca durante la corsa. Il seguente snippet potrebbe essere utilizzato per generare record all'interno dell'intervallo target.

In un'app reale, le parti principali di questo snippet potrebbero essere trovate nel gestore per un messaggio proveniente da un dispositivo indossabile, che scriverebbe la misurazione in Connessione Salute al momento della raccolta.

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

Valutare i target di rendimento

Il giorno dopo l'allenamento dell'utente, puoi recuperare l'esercizio registrato, controllare eventuali target di allenamento pianificati e valutare tipi di dati aggiuntivi per determinare se i target impostati sono stati raggiunti.

Uno snippet come questo si trova probabilmente in un job periodico per valutare i target di rendimento o durante il caricamento di un elenco di esercizi e la visualizzazione di una notifica sui target di rendimento in un'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
                            }
                        }
                    }
                }
            }
        }
    }
}

Sessioni di allenamento

Le sessioni di allenamento possono includere qualsiasi attività, dalla corsa al badminton.

Scrittura delle sessioni di allenamento

Ecco come creare una richiesta di inserimento che includa una sessione:

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

Lettura di una sessione di allenamento

Ecco un esempio di come leggere una sessione di allenamento:

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

Scrivere i dati del sottotipo

Le sessioni possono anche essere composte da dati di sottotipo facoltativi, che arricchiscono la sessione con informazioni aggiuntive.

Ad esempio, le sessioni di allenamento possono includere le classi 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
)