Aggiungi percorsi per l'esercizio fisico

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

I percorsi di allenamento consentono agli utenti di monitorare un percorso GPS per le attività di allenamento associate e condividere le mappe dei loro allenamenti con altre app.

Questa guida fornisce informazioni su come richiedere le autorizzazioni all'utente e descrive anche come le app ricevono l'autorizzazione a scrivere dati di itinerario nell'ambito di una sessione di allenamento.

La funzionalità di lettura e scrittura per i percorsi di allenamento include:

  1. Le app creano una nuova autorizzazione di scrittura per i percorsi di allenamento.
  2. L'inserimento avviene scrivendo una sessione di allenamento con un percorso come campo.
  3. Lettura:
    1. Per il proprietario della sessione, i dati vengono accessibili tramite una lettura della sessione.
    2. Da un'app di terze parti, tramite una finestra di dialogo che consente all'utente di concedere una lettura una tantum di un percorso.

Se l'utente non dispone delle autorizzazioni di scrittura e la route non è impostata, la route non viene aggiornata.

Se la tua app dispone dell'autorizzazione di scrittura delle route e tenta di aggiornare una sessione passando un oggetto sessione senza una route, la route esistente viene eliminata.

Disponibilità della funzionalità

Non esiste un flag di disponibilità delle funzionalità per questo tipo di dati.

Autorizzazioni richieste

L'accesso al percorso dell'allenamento è protetto dalle seguenti autorizzazioni:

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

Per aggiungere la funzionalità di percorso dell'allenamento alla tua app, inizia richiedendo le autorizzazioni di scrittura per il tipo di dati ExerciseSession.

Ecco l'autorizzazione che devi dichiarare per poter scrivere il percorso dell'allenamento:

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

Per leggere il percorso dell'allenamento, devi richiedere le seguenti autorizzazioni:

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

Devi anche dichiarare un'autorizzazione di attività fisica, poiché ogni percorso è associato a una sessione di attività fisica (una sessione = un allenamento).

Per richiedere le autorizzazioni, utilizza il metodo PermissionController.createRequestPermissionResultContract() quando colleghi per la prima volta la tua app a Connessione Salute. Alcune autorizzazioni che potresti voler richiedere sono:

  • Leggi i dati sanitari, inclusi i dati sui percorsi: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Scrivi dati sanitari, inclusi i dati del percorso: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • Scrittura dei dati del percorso per l'esercizio fisico: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

Richiedi 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 nell'insieme siano dichiarate prima nel file manifest di Android.

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

Utilizza getGrantedPermissions per verificare se alla tua app sono già state concesse le 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 revocata.

Informazioni incluse in un record di sessione di allenamento

Ogni record di sessione di allenamento contiene le seguenti informazioni:

  • Il tipo di allenamento, ad esempio ciclismo.
  • L'itinerario dell'esercizio, che contiene informazioni come latitudine, longitudine e altitudine.

Aggregazioni supportate

Non sono presenti aggregazioni supportate per questo tipo di dati.

Esempio di utilizzo

I seguenti snippet di codice mostrano come leggere e scrivere un percorso di allenamento.

Lettura del percorso per l'esercizio fisico

Quando viene eseguita in background, l'app non può leggere i dati del percorso di allenamento creati da altre app.

Quando la tua app viene eseguita in background e tenta di leggere un percorso di allenamento creato da un'altra app, Connessione Salute restituisce una risposta ExerciseRouteResult.ConsentRequired, anche se la tua app ha accesso Consenti sempre ai dati del percorso di allenamento.

Per questo motivo, ti consigliamo vivamente di richiedere itinerari in seguito a un'interazione deliberata dell'utente con la tua app, quando l'utente è attivamente impegnato con l'interfaccia utente della tua app.

Per saperne di più sulle letture in background, consulta Esempio di lettura in background.

Il seguente snippet di codice mostra come leggere una sessione in Connessione Salute e richiedere un percorso da quella sessione:

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

Scrivere un percorso per l'esercizio fisico

Il seguente codice mostra come registrare una sessione che include un percorso di allenamento:

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

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
)

Eliminare una sessione di allenamento

Esistono due modi per eliminare una sessione di allenamento:

  1. Per intervallo di tempo.
  2. Per UID.

Ecco come eliminare i dati dei sottotipi in base all'intervallo di tempo:

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

Puoi anche eliminare i dati del sottotipo per UID. In questo modo viene eliminata solo la sessione di allenamento, non i dati associati:

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