Dodaj trasy ćwiczeń

Ten przewodnik jest zgodny z wersją Health Connect 1.1.0-alpha12.

Trasy ćwiczeń umożliwiają użytkownikom śledzenie trasy GPS powiązanych aktywności i udostępnianie map treningów innym aplikacjom.

W tym przewodniku znajdziesz informacje o tym, jak prosić użytkownika o uprawnienia, a także jak aplikacje otrzymują uprawnienia do zapisywania danych o trasie w ramach sesji ćwiczeń.

Funkcje odczytu i zapisu tras ćwiczeń obejmują:

  1. Aplikacje tworzą nowe uprawnienia do zapisu tras ćwiczeń.
  2. Wstawianie odbywa się przez zapisanie sesji ćwiczeń z trasą jako polem.
  3. Odczyt:
    1. W przypadku właściciela sesji dostęp do danych uzyskuje się za pomocą odczytu sesji.
    2. z aplikacji innej firmy za pomocą okna, które umożliwia użytkownikowi jednorazowe odczytanie trasy;

Jeśli użytkownik nie ma uprawnień do zapisu, a trasa nie jest ustawiona, trasa nie zostanie zaktualizowana.

Jeśli aplikacja ma uprawnienia do zapisu trasy i próbuje zaktualizować sesję, przekazując obiekt sesji bez trasy, dotychczasowa trasa zostanie usunięta.

Sprawdzanie dostępności Health Connect

Zanim spróbujesz użyć Health Connect, sprawdź, czy jest on dostępny na urządzeniu użytkownika. Aplikacja Health Connect może nie być wstępnie zainstalowana na wszystkich urządzeniach lub może być wyłączona. Dostępność możesz sprawdzić za pomocą HealthConnectClient.getSdkStatus()metody.

Jak sprawdzić dostępność 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
}

W zależności od stanu zwróconego przez getSdkStatus() możesz w razie potrzeby poprosić użytkownika o zainstalowanie lub zaktualizowanie Health Connect ze Sklepu Google Play.

Dostępność funkcji

Aby sprawdzić, czy urządzenie użytkownika obsługuje zaplanowane ćwiczenia w Health Connect, sprawdź dostępność FEATURE_PLANNED_EXERCISE w aplikacji:

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

  // Feature is available
} else {
  // Feature isn't available
}
Więcej informacji znajdziesz w artykule Sprawdzanie dostępności funkcji.

Wymagane uprawnienia

Dostęp do trasy ćwiczeń jest chroniony przez te uprawnienia:

  • android.permission.health.READ_EXERCISE_ROUTES
  • android.permission.health.WRITE_EXERCISE_ROUTE
Uwaga: w przypadku tego typu uprawnień READ_EXERCISE_ROUTES jest liczbą mnogą, a  WRITE_EXERCISE_ROUTE liczbą pojedynczą.

Aby dodać do aplikacji funkcję trasy ćwiczeń, zacznij od poproszenia o uprawnienia do zapisu w przypadku typu danych ExerciseSession.

Aby móc zapisywać trasę ćwiczeń, musisz zadeklarować to uprawnienie:

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

Aby odczytać trasę ćwiczeń, musisz poprosić o te uprawnienia:

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

Musisz też zadeklarować uprawnienia do ćwiczeń, ponieważ każda trasa jest powiązana z sesją ćwiczeń (1 sesja = 1 trening).

Aby poprosić o uprawnienia, użyj metody PermissionController.createRequestPermissionResultContract(), gdy po raz pierwszy połączysz aplikację z Health Connect. Oto kilka uprawnień, o które możesz poprosić:

  • Odczytywanie danych o zdrowiu i aktywności fizycznej, w tym danych o trasie: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Zapisywanie danych o zdrowiu i aktywności fizycznej, w tym danych o trasie: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • Zapisywanie danych trasy ćwiczeń: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

Prośba użytkownika o uprawnienia

Po utworzeniu instancji klienta aplikacja musi poprosić użytkownika o uprawnienia. Użytkownicy muszą mieć możliwość przyznania lub odmowy przyznania uprawnień w dowolnym momencie.

Aby to zrobić, utwórz zestaw uprawnień dla wymaganych typów danych. Sprawdź, czy uprawnienia w zestawie są najpierw zadeklarowane w pliku manifestu Androida.

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

Użyj getGrantedPermissions, aby sprawdzić, czy Twoja aplikacja ma już przyznane wymagane uprawnienia. Jeśli nie, użyj createRequestPermissionResultContract, aby poprosić o te uprawnienia. Wyświetli się ekran uprawnień 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)
  }
}

Użytkownicy mogą w dowolnym momencie przyznawać i wycofywać uprawnienia, dlatego aplikacja musi okresowo sprawdzać, czy uprawnienia zostały przyznane, i obsługiwać sytuacje, w których uprawnienia zostaną utracone.

Informacje zawarte w rekordzie sesji ćwiczeń

Każdy rekord sesji ćwiczeń zawiera te informacje:

  • Rodzaj ćwiczenia, np. jazda na rowerze.
  • Trasa ćwiczeń, która zawiera informacje takie jak szerokość i długość geograficzna oraz wysokość.

Obsługiwane agregacje

Dostępne są te wartości zagregowane elementu ExerciseSessionRecord:

Przykład użycia

Poniższe fragmenty kodu pokazują, jak odczytywać i zapisywać trasę ćwiczeń.

Odczytuj trasę ćwiczeń

Gdy aplikacja działa w tle, nie może odczytywać danych o trasie ćwiczeń utworzonych przez inne aplikacje.

Gdy aplikacja działa w tle i próbuje odczytać trasę ćwiczeń utworzoną przez inną aplikację, Health Connect zwraca odpowiedź ExerciseRouteResult.ConsentRequired, nawet jeśli aplikacja ma dostęp Zawsze zezwalaj do danych trasy ćwiczeń.

Z tego powodu zdecydowanie zalecamy wysyłanie żądań tras w momencie, gdy użytkownik wchodzi w interakcję z aplikacją, czyli gdy aktywnie korzysta z jej interfejsu.

Więcej informacji o odczytach w tle znajdziesz w przykładzie odczytu w tle.

Ten fragment kodu pokazuje, jak odczytać sesję w Health Connect i poprosić o trasę z tej sesji:

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

Zapisywanie trasy ćwiczeń

Poniższy kod pokazuje, jak zarejestrować sesję, która obejmuje trasę ćwiczeń:

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

Sesje ćwiczeń

Sesje ćwiczeń mogą obejmować wszystko, od biegania po badmintona.

Zapisywanie sesji ćwiczeń

Oto jak utworzyć żądanie wstawienia, które zawiera sesję:

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

Odczytywanie sesji ćwiczeń

Oto przykład odczytywania sesji ćwiczeń:

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

Zapisywanie danych podtypu

Sesje mogą też zawierać opcjonalne dane podtypu, które wzbogacają sesję o dodatkowe informacje.

Na przykład sesje ćwiczeń mogą obejmować zajęcia ExerciseSegment, ExerciseLapExerciseRoute:

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
)

Usuwanie sesji ćwiczeń

Sesję ćwiczeń możesz usunąć na 2 sposoby:

  1. według zakresu czasowego,
  2. według identyfikatora UID,

Aby usunąć dane podtypu według zakresu czasu:

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

Możesz też usuwać dane podtypu według identyfikatora UID. Spowoduje to usunięcie tylko sesji ćwiczeń, a nie powiązanych z nią danych:

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