Планы тренировок

Health Connect предоставляет тип данных «запланированные упражнения» , позволяющий тренировочным приложениям создавать планы тренировок, а также считывать эти планы. Записанные упражнения (тренировки) можно просмотреть для персонализированного анализа результатов, чтобы помочь пользователям достичь своих тренировочных целей.

Проверьте доступность Health Connect.

Перед использованием Health Connect ваше приложение должно убедиться, что Health Connect доступен на устройстве пользователя. Health Connect может быть не предустановлен на всех устройствах или отключен. Проверить доступность можно с помощью метода HealthConnectClient.getSdkStatus() .

Как проверить доступность 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
}

В зависимости от статуса, возвращаемого функцией getSdkStatus() , вы можете предложить пользователю установить или обновить Health Connect из магазина Google Play, если это необходимо.

Доступность функций

Чтобы определить, поддерживает ли устройство пользователя планы тренировок в Health Connect, проверьте наличие параметра FEATURE_PLANNED_EXERCISE на клиентском устройстве:

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

  // Feature is available
} else {
  // Feature isn't available
}
Для получения более подробной информации см. раздел «Проверка доступности функции» .

Необходимые разрешения

Доступ к запланированным тренировкам обеспечивается следующими разрешениями:

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

Чтобы добавить в ваше приложение возможность планирования тренировок, для начала запросите разрешения для типа данных PlannedExerciseSession .

Вот какие разрешения вам необходимо предоставить, чтобы иметь возможность писать план упражнений:

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

Для ознакомления с запланированными упражнениями необходимо запросить следующие разрешения:

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

Запросить у пользователя разрешения

После создания экземпляра клиента ваше приложение должно запросить у пользователя разрешения. Пользователи должны иметь возможность предоставлять или отклонять разрешения в любое время.

Для этого создайте набор разрешений для необходимых типов данных. Убедитесь, что разрешения в этом наборе сначала объявлены в вашем 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)
)

Используйте getGrantedPermissions , чтобы проверить, предоставлены ли вашему приложению уже необходимые разрешения. Если нет, используйте createRequestPermissionResultContract для запроса этих разрешений. После этого отобразится экран разрешений 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)
  }
}

Поскольку пользователи могут предоставлять или отзывать разрешения в любое время, вашему приложению необходимо периодически проверять наличие предоставленных разрешений и обрабатывать ситуации, когда разрешение утрачивается.

Планы тренировок привязаны к тренировочным занятиям . Поэтому для полноценного использования этой функции Health Connect пользователь должен предоставить разрешение на использование каждого типа записей, связанных с планом тренировок.

Например, если план тренировок измеряет частоту сердечных сокращений пользователя во время серии пробежек, разработчику может потребоваться указать следующие разрешения, которые пользователь должен будет предоставить для записи данных о тренировке и чтения результатов для последующей оценки:

  • 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

Однако зачастую приложение, которое создает планы тренировок и оценивает результаты в соответствии с планами, отличается от приложения, которое использует планы тренировок и записывает фактические данные об упражнениях. В зависимости от типа приложения, не все разрешения на чтение и запись могут быть необходимы. Например, для каждого типа приложения могут потребоваться только следующие разрешения:

Приложение для составления плана обучения Приложение для тренировок
WRITE_PLANNED_EXERCISE READ_PLANNED_EXERCISE
READ_EXERCISE WRITE_EXERCISE
READ_EXERCISE_ROUTES WRITE_EXERCISE_ROUTE
READ_HEART_RATE WRITE_HEART_RATE

Информация, содержащаяся в протоколе запланированной тренировки.

  • Название сессии.
  • Список запланированных блоков упражнений .
  • Время начала и окончания занятия.
  • Тип упражнений.
  • Примечания к заданию.
  • Метаданные.
  • Идентификатор завершенной тренировочной сессии — это значение автоматически записывается после завершения тренировочной сессии, связанной с данной запланированной тренировочной сессией.

Информация, содержащаяся в протоколе запланированного блока упражнений.

Запланированный тренировочный блок содержит список упражнений, позволяющий повторять различные группы упражнений (например, выполнить последовательность сгибаний рук, берпи и скручиваний пять раз подряд).

Информация, содержащаяся в записи о запланированных шагах во время упражнений.

Поддерживаемые агрегации

Для данного типа данных не поддерживаются никакие агрегации.

Пример использования

Предположим, пользователь планирует 90-минутную пробежку через два дня. Эта пробежка будет включать три круга вокруг озера с целевой частотой сердечных сокращений от 90 до 110 ударов в минуту.

  1. В приложении для составления тренировочного плана пользователь определяет запланированную тренировку со следующими параметрами:
    1. Запланированные начало и конец забега
    2. Вид упражнений (бег)
    3. Количество кругов (повторений)
    4. Целевой показатель частоты сердечных сокращений (от 90 до 110 ударов в минуту)
  2. Эта информация группируется в блоки упражнений и этапы и записывается в Health Connect приложением для планирования тренировок в виде записи PlannedExerciseSessionRecord .
  3. Пользователь выполняет запланированную сессию (запуск).
  4. Данные об упражнениях, относящиеся к тренировке, записываются одним из следующих способов:
    1. Данные о частоте сердечных сокращений записываются в Health Connect в качестве типа записи для данной активности. В данном случае это HeartRateRecord .
    2. Пользователь вручную записывает эти данные после завершения сессии. Например, указывает начало и конец фактического забега. Эти данные записываются в Health Connect в виде записи ExerciseSessionRecord .
  5. Позже приложение для составления плана тренировок считывает данные из Health Connect, чтобы оценить фактическую эффективность по сравнению с целевыми показателями, установленными пользователем в запланированной тренировке.

Планируйте упражнения и устанавливайте цели.

Пользователь может планировать свои тренировки на будущее и устанавливать цели. Запишите это в Health Connect как запланированную тренировку .

В примере, описанном в разделе «Пример использования» , пользователь планирует 90-минутную пробежку через два дня. Эта пробежка будет включать три круга вокруг озера с целевой частотой сердечных сокращений от 90 до 110 ударов в минуту.

Подобный фрагмент кода можно найти в обработчике форм приложения, которое регистрирует запланированные тренировки в Health Connect. Он также может находиться в точке приема данных для интеграций, например, с сервисом, предлагающим тренировки.

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

Регистрируйте данные об упражнениях и физической активности.

Через два дня пользователь регистрирует фактическую тренировку. Запишите это в Health Connect как тренировку .

В этом примере продолжительность сеанса пользователя точно совпала с запланированной.

Приведенный ниже фрагмент кода может находиться в обработчике формы приложения, которое регистрирует тренировочные сессии в Health Connect. Он также может встречаться в обработчиках ввода и экспорта данных носимого устройства, способного обнаруживать и регистрировать тренировочные сессии.

Здесь используется значение insertedPlannedExerciseSessionId из предыдущего примера. В реальном приложении идентификатор определялся бы выбором пользователем запланированной тренировки из списка существующих.

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

Носимое устройство также регистрирует частоту сердечных сокращений на протяжении всей пробежки. Следующий фрагмент кода можно использовать для получения записей в целевом диапазоне.

В реальном приложении основные части этого фрагмента кода могут находиться в обработчике сообщения от носимого устройства, которое будет записывать данные измерений в Health Connect после их сбора.

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

Оцените целевые показатели эффективности.

На следующий день после тренировки пользователя вы можете получить доступ к зарегистрированной тренировке, проверить наличие запланированных целевых показателей и проанализировать дополнительные типы данных, чтобы определить, были ли достигнуты установленные цели.

Подобный фрагмент кода, скорее всего, можно встретить в периодических заданиях по оценке целевых показателей производительности или при загрузке списка упражнений и отображении уведомления о целевых показателях производительности в приложении.

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

Занятия спортом

В программу тренировок может входить все что угодно, от бега до бадминтона.

Напишите упражнения для тренировки.

Вот как создать запрос на вставку, включающий сессию:

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

Прочитайте описание упражнения

Вот пример того, как читать описание тренировки:

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

Запись данных подтипа

Сессии также могут включать в себя необязательные данные подтипа, которые обогащают сессию дополнительной информацией.

Например, тренировочные занятия могут включать в себя классы ExerciseSegment , ExerciseLap и 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,
  metadata = Metadata.manualEntry()
)