This guide is compatible with Health Connect version 1.1.0-alpha11.
Health Connect provides a planned exercise data type to enable training apps to write training plans and enable workout apps to read training plans. Recorded exercises (workouts) can be read back for personalized performance analysis to help users achieve their training goals.
Feature availability
To determine whether a user's device supports training plans on Health Connect,
check the availability of FEATURE_PLANNED_EXERCISE
on the client:
if (healthConnectClient
.features
.getFeatureStatus(
HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {
// Feature is available
} else {
// Feature isn't available
}
See Check for feature availability to learn more.
Required permissions
Access to training plans is protected by the following permissions:
android.permission.health.READ_PLANNED_EXERCISE
android.permission.health.WRITE_PLANNED_EXERCISE
Declare these permissions in the Play Console for your app, as well as in your app's manifest:
<application>
<uses-permission
android:name="android.permission.health.READ_PLANNED_EXERCISE" />
<uses-permission
android:name="android.permission.health.WRITE_PLANNED_EXERCISE" />
...
</application>
You are responsible for declaring all the appropriate permissions you intend to use in your devices and apps. You should also check that each permission has been granted by the user before use.
Related permissions
Training plans are linked to exercise sessions. Therefore, the user must give permission to use each record type related to a training plan in order to fully utilize this feature of Health Connect.
For example, if a training plan measures a user's heart rate during a series of runs, the following permissions might need to be declared by the developer and granted by the user in order to write the exercise session and read the results for later evaluation:
android.permission.health.READ_EXERCISE
android.permission.health.READ_EXERCISE_ROUTE
android.permission.health.READ_HEART_RATE
android.permission.health.WRITE_EXERCISE
android.permission.health.WRITE_EXERCISE_ROUTE
android.permission.health.WRITE_HEART_RATE
However, often the app that creates training plans and evaluates performance against plans isn't the same as the app that consumes training plans and writes actual exercise data. Depending on the type of app, not all read and write permissions would be needed. For example, you may only need these permissions for each type of app:
Training plan app | Workout app |
---|---|
WRITE_PLANNED_EXERCISE |
READ_PLANNED_EXERCISE |
READ_EXERCISE |
WRITE_EXERCISE |
READ_EXERCISE_ROUTE |
WRITE_EXERCISE_ROUTE |
READ_HEART_RATE |
WRITE_HEART_RATE |
Information included in a planned exercise session record
- Title of the session.
- A list of planned exercise blocks.
- Start and end time of the session.
- Exercise type.
- Notes for the activity.
- Metadata.
- Completed exercise session ID — This is written automatically after an exercise session related to this planned exercise session is completed.
Information included in a planned exercise block record
A planned exercise block contains a list of exercise steps, to support repetition of different groups of steps (for example, do a sequence of arm curls, burpies, and crunches five times in a row).
- Description of the block.
- A list of planned exercise steps.
- Number of repetitions.
Information included in a planned exercise step record
- Description of the step.
- Exercise category.
- Exercise type.
- A list of performance goals.
- Completion goal.
Supported aggregations
There are no supported aggregations for this data type.
Example usage
Suppose a user plans a 90 minute run two days from now. This run will feature three laps around a lake with a target heart rate between 90 and 110 bpm.
- A planned exercise session with the following is defined by the user in
a training plan app:
- Planned start and end of the run
- The type of exercise (running)
- Number of laps (repetitions)
- Performance target for heart rate (between 90 and 110 bpm)
- This information is grouped into exercise blocks and steps and written
to Health Connect by the training plan app as a
PlannedExerciseSessionRecord
. - The user performs the planned session (running).
- Exercise data related to the session is recorded either:
- By a wearable during the session. For example, heart rate.
This data is written to Health Connect as the record type for the
activity. In this case,
HeartRateRecord
. - Manually by the user after the session. For example, indicating
the start and end of the actual run. This data is written to Health
Connect as an
ExerciseSessionRecord
.
- By a wearable during the session. For example, heart rate.
This data is written to Health Connect as the record type for the
activity. In this case,
- At a later time, the training plan app reads data from Health Connect to evaluate the actual performance against the targets set by the user in the planned exercise session.
Plan exercises and set targets
A user may plan their exercise in the future and set targets. Write this to Health Connect as a planned exercise session.
In the example described in Example usage, the user plans a 90 minute run two days from now. This run will feature three laps around a lake with a target heart rate between 90 and 110 bpm.
A snippet like this may be found in the form handler for an app that logs planned exercise sessions to Health Connect. It could also be found in the ingest point for integrations, say with a service that offers training.
// Ensure 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 = ExerciseSessionRecord.EXERCISE_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
)
val insertedPlannedExerciseSessions =
healthConnectClient.insertRecords(listOf(plannedExerciseSessionRecord)).recordIdsList
val insertedPlannedExerciseSessionId = insertedPlannedExerciseSessions.first()
Log exercise and activity data
Two days later, the user logs the actual exercise session. Write this to Health Connect as an exercise session.
In this example, the user's session duration matched the planned duration exactly.
The following snippet might be found in the form handler for an app that logs exercise sessions to Health Connect. It might also be found in data ingest and export handlers for a wearable capable of detecting and logging exercise sessions.
insertedPlannedExerciseSessionId
here is reused from the previous example. In
a real app, the ID would be determined by the user selecting a planned exercise
session from a list of existing sessions.
// Ensure 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,
)
val insertedExerciseSessions =
healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
A wearable also logs their heart rate throughout the run. The following snippet could be used to generate records within the target range.
In a real app, the primary pieces of this snippet might be found in the handler for a message from a wearable, which would write measurement to Health Connect upon collection.
// Ensure 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,
)
val insertedHeartRateRecords = healthConnectClient.insertRecords(listOf(heartRateRecord))
Evaluate performance targets
The day after the user's workout, you can retrieve the logged exercise, check for any planned exercise targets, and evaluate additional data types to determine if set targets were met.
A snippet like this would likely be found in a periodic job to evaluate performance targets or when loading a list of exercises and displaying a notification about performance targets in an app.
// Ensure 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
}
}
}
}
}
}
}
}