Trainingsrouten hinzufügen

Diese Anleitung ist mit Health Connect-Version 1.1.0-alpha12 kompatibel.

Mit Trainingsrouten können Nutzer eine GPS-Route für zugehörige Trainingsaktivitäten aufzeichnen und Karten ihrer Trainings mit anderen Apps teilen.

Verfügbarkeit von Health Connect prüfen

Bevor Ihre App versucht, Health Connect zu verwenden, sollte sie prüfen, ob Health Connect auf dem Gerät des Nutzers verfügbar ist. Health Connect ist möglicherweise nicht auf allen Geräten vorinstalliert oder wurde deaktiviert. Sie können die Verfügbarkeit mit der Methode HealthConnectClient.getSdkStatus() prüfen.

So prüfen Sie die Verfügbarkeit von 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
}

Je nach dem von getSdkStatus() zurückgegebenen Status können Sie den Nutzer bei Bedarf anweisen, Health Connect im Google Play Store zu installieren oder zu aktualisieren.

In dieser Anleitung erfahren Sie, wie Sie Berechtigungen vom Nutzer anfordern und wie Apps die Berechtigung zum Schreiben von Routendaten im Rahmen einer Trainingseinheit erhalten.

Die Lese- und Schreibfunktionen für Trainingsrouten umfassen:

  1. Apps erstellen eine neue Schreibberechtigung für Trainingsrouten.
  2. Das Einfügen erfolgt durch Schreiben einer Trainingseinheit mit einer Route als Feld.
  3. Lesen:
    1. Für den Inhaber der Sitzung erfolgt der Zugriff auf Daten über eine Sitzungslesung.
    2. Über eine Drittanbieter-App über ein Dialogfeld, in dem der Nutzer eine einmalige Lesung einer Route gewähren kann.

Wenn der Nutzer keine Schreibberechtigungen hat und die Route nicht festgelegt ist, wird die Route nicht aktualisiert.

Wenn Ihre App eine Schreibberechtigung für Routen hat und versucht, eine Sitzung zu aktualisieren, indem sie ein Sitzungsobjekt ohne Route übergibt, wird die vorhandene Route gelöscht.

Verfügbarkeit der Funktion

Um herauszufinden, ob das Gerät eines Nutzers geplante Trainings in Health Connect unterstützt, prüfen Sie, ob FEATURE_PLANNED_EXERCISE auf dem Client verfügbar ist:

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

  // Feature is available
} else {
  // Feature isn't available
}
Weitere Informationen finden Sie unter Verfügbarkeit von Funktionen prüfen.

Erforderliche Berechtigungen

Der Zugriff auf Trainingsrouten wird durch die folgenden Berechtigungen geschützt:

  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.WRITE_EXERCISE_ROUTE
Hinweis: Bei diesem Berechtigungstyp ist READ_EXERCISE_ROUTES im Plural, während WRITE_EXERCISE_ROUTE im Singular steht.

Wenn Sie Ihrer App die Funktion für Trainingsrouten hinzufügen möchten, fordern Sie zuerst Berechtigungen für den Datentyp ExerciseSession an.

Hier ist die Berechtigung, die Sie deklarieren müssen, um Trainingsrouten schreiben zu können:

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

Zum Lesen von Trainingsrouten müssen Sie die folgenden Berechtigungen anfordern:

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

Sie müssen auch eine Trainingsberechtigung deklarieren, da jede Route mit einer Trainingseinheit verknüpft ist (eine Sitzung = ein Training).

Verwenden Sie zum Anfordern von Berechtigungen die Methode PermissionController.createRequestPermissionResultContract(), wenn Sie Ihre App zum ersten Mal mit Health Connect verbinden. Mehrere Berechtigungen, die Sie möglicherweise anfordern möchten:

  • Gesundheits- und Fitnessdaten lesen, einschließlich Routendaten: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Gesundheits- und Fitnessdaten schreiben, einschließlich Routendaten: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • Daten zu Trainingsrouten schreiben: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

Berechtigungen vom Nutzer anfordern

Nachdem Sie eine Clientinstanz erstellt haben, muss Ihre App Berechtigungen vom Nutzer anfordern. Nutzer müssen jederzeit Berechtigungen erteilen oder verweigern können. Erstellen Sie dazu eine Reihe von Berechtigungen für die erforderlichen Datentypen. Achten Sie darauf, dass die Berechtigungen im Set zuerst in Ihrem Android-Manifest deklariert werden.

val permissions =
    setOf(
        HealthPermission.getReadPermission(ExerciseSessionRecord::class),
        HealthPermission.getWritePermission(ExerciseSessionRecord::class)
    )
Mit getGrantedPermissions können Sie prüfen, ob Ihrer App bereits die erforderlichen Berechtigungen erteilt wurden. Wenn nicht, fordern Sie diese Berechtigungen mit createRequestPermissionResultContract an. Daraufhin wird der Berechtigungsbildschirm von Health Connect angezeigt.
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.") }
    }
}
Da Nutzer Berechtigungen jederzeit erteilen oder widerrufen können, muss Ihre App jedes Mal vor der Verwendung von Berechtigungen prüfen, ob sie vorhanden sind, und Szenarien berücksichtigen, in denen Berechtigungen verloren gehen.

Informationen in einem Datensatz für Trainingseinheiten

Jeder Datensatz für Trainingseinheiten enthält die folgenden Informationen:

  • Der Typ des Trainings, z. B. Radfahren.
  • Die Trainingsroute , die Informationen wie Breitengrad, Längengrad und Höhe enthält.

Unterstützte Aggregationen

Die folgenden aggregierten Werte sind für ExerciseSessionRecord verfügbar:

Nutzungsbeispiel

Die folgenden Code-Snippets zeigen, wie Sie eine Trainingsroute lesen und schreiben.

Trainingsroute abrufen

Ihre App kann keine Daten zu Trainingsrouten lesen, die von anderen Apps erstellt wurden, wenn sie im Hintergrund ausgeführt wird.

Wenn Ihre App im Hintergrund ausgeführt wird und versucht, eine Trainingsroute zu lesen, die von einer anderen App erstellt wurde, gibt Health Connect die Antwort ExerciseRouteResult.ConsentRequired zurück, auch wenn Ihre App den Zugriff auf Daten zu Trainingsrouten mit der Berechtigung Immer zulassen hat.

Wir empfehlen daher dringend, Routen nur dann anzufordern, wenn der Nutzer bewusst mit Ihrer App interagiert und die Benutzeroberfläche Ihrer App aktiv verwendet.

Weitere Informationen zu Hintergrundlesungen finden Sie im Beispiel für Hintergrundlesungen.

Das folgende Code-Snippet zeigt, wie Sie eine Sitzung in Health Connect lesen und eine Route aus dieser Sitzung anfordern:

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

Trainingsroute schreiben

Der folgende Code zeigt, wie Sie eine Sitzung aufzeichnen, die eine Trainingsroute enthält:

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

Trainingseinheiten

Trainingseinheiten können alles von Laufen bis Badminton umfassen.

Trainingseinheiten schreiben

So erstellen Sie eine Einfügeanfrage, die eine Sitzung enthält:

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

Trainingseinheit lesen

Hier ein Beispiel für das Lesen einer Trainingseinheit:

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

Untertypdaten schreiben

Sitzungen können auch optionale Untertypdaten enthalten, die die Sitzung mit zusätzlichen Informationen ergänzen.

Trainingseinheiten können beispielsweise die Klassen ExerciseSegment, ExerciseLap und ExerciseRoute enthalten:

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

Trainingseinheit löschen

Es gibt zwei Möglichkeiten, eine Trainingseinheit zu löschen:

  1. Nach Zeitraum.
  2. Nach UID.

So löschen Sie Untertypdaten nach Zeitraum:

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

Sie können Untertypdaten auch nach UID löschen. Dadurch wird nur die Trainingseinheit gelöscht, nicht die zugehörigen Daten:

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