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.

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 Sitzungsinhaber wird über einen Sitzungslesevorgang auf Daten zugegriffen.
    2. Über eine Drittanbieter-App über ein Dialogfeld, in dem der Nutzer das einmalige Lesen einer Route zulassen kann.

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

Wenn Ihre App die Berechtigung zum Schreiben von 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

Für diesen Datentyp ist kein Verfügbarkeitskennzeichen für Funktionen vorhanden.

Erforderliche Berechtigungen

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

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

Wenn Sie Ihrer App die Funktion zum Hinzufügen von Trainingsrouten hinzufügen möchten, müssen Sie zuerst Schreibberechtigungen für den Datentyp ExerciseSession anfordern.

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

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

Wenn Sie Trainingsrouten lesen möchten, müssen Sie die folgenden Berechtigungen anfordern:

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

Außerdem müssen Sie eine Berechtigung für Training deklarieren, da jede Route mit einer Trainingseinheit verknüpft ist (eine Sitzung = ein Training).

Verwenden Sie die Methode PermissionController.createRequestPermissionResultContract(), um Berechtigungen anzufordern, wenn Sie Ihre App zum ersten Mal mit Health Connect verbinden. Hier sind einige Berechtigungen, die Sie möglicherweise anfordern möchten:

  • Gesundheitsdaten lesen, einschließlich Routendaten: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Gesundheitsdaten, einschließlich Routendaten, schreiben: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • Daten zur Trainingsroute schreiben: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

Berechtigungen vom Nutzer anfordern

Nachdem Sie eine Client-Instanz erstellt haben, muss Ihre App Berechtigungen vom Nutzer anfordern. Nutzer müssen jederzeit die Möglichkeit haben, Berechtigungen zu erteilen oder zu verweigern.

Erstellen Sie dazu eine Reihe von Berechtigungen für die erforderlichen Datentypen. Die Berechtigungen im Set müssen zuerst in Ihrem Android-Manifest deklariert werden.

// Create a set of permissions for required data types
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. Falls nicht, verwenden Sie createRequestPermissionResultContract, um diese Berechtigungen anzufordern. Der Bildschirm mit den Health Connect-Berechtigungen wird angezeigt.

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

Da Nutzer Berechtigungen jederzeit erteilen oder widerrufen können, muss Ihre App regelmäßig prüfen, welche Berechtigungen erteilt wurden, und Szenarien berücksichtigen, in denen Berechtigungen verloren gehen.

In einem Trainingsaufzeichnung enthaltene Informationen

Jeder Trainingssitzungseintrag enthält die folgenden Informationen:

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

Unterstützte Aggregationen

Für diesen Datentyp sind keine unterstützten Aggregationen vorhanden.

Beispiel für die Verwendung

Die folgenden Code-Snippets zeigen, wie eine Trainingsroute gelesen und geschrieben wird.

Trainingsroute abrufen

Ihre App kann keine von anderen Apps erstellten Trainingsstreckendaten lesen, wenn sie im Hintergrund ausgeführt wird.

Wenn Ihre App im Hintergrund ausgeführt wird und versucht, eine von einer anderen App erstellte Trainingsroute zu lesen, gibt Health Connect eine ExerciseRouteResult.ConsentRequired-Antwort zurück, auch wenn Ihre App Immer zulassen-Zugriff auf Trainingsroutedaten hat.

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

Weitere Informationen zu Hintergrundlesevorgängen finden Sie unter Beispiel für Hintergrundlesevorgänge.

Das folgende Code-Snippet zeigt, wie eine Trainingseinheit in Health Connect gelesen und eine Route für diese Trainingseinheit angefordert wird:

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

Trainingsroute schreiben

Der folgende Code zeigt, wie Sie eine Trainingseinheit mit einer Trainingsstrecke aufzeichnen:

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

Trainingseinheiten

Zu den Trainingseinheiten können Aktivitäten wie Laufen oder Badminton gehören.

Trainingseinheiten schreiben

So erstellen Sie eine Einfügungsanfrage, 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"
            ),
            // ... other records
        )
    )
}

Trainingssitzung lesen

Hier ein Beispiel dafür, wie eine Trainingseinheit gelesen wird:

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

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

Trainingseinheit löschen

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

  1. Nach Zeitraum.
  2. Nach UID.

So löschen Sie Daten zu Untertypen 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 anhand der 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()
    )
}