Add exercise routes

Exercise routes allow users to track a GPS route for associated exercise activities and share maps of their workouts with other apps.

This guide outlines how apps receive permission to write route data as part of an exercise session.

Here's a brief summary of the read and write functionality for exercise routes:

  1. Apps create a new write permission for exercise routes.
  2. Insertion happens by writing an exercise session with a route as its field.
  3. Reading:
    1. For the session owner, data is accessed using a session read.
    2. From a third-party app, through a dialog that allows the user to grant a one-time read of a route.

Permissions

Exercise routes have their own runtime write permission (android.permission.health.WRITE_EXERCISE_ROUTE).

To add exercise route capability to your app, start by requesting write permissions for a specific data type.

Android 14 permission request

Android 14 permission request

Android 13 permission request

Android 13 permission request

You also have to declare an exercise permission, as each route is associated with an exercise session (one session = one workout).

Here's the permission you need to declare to be able to write exercise routes:

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

To read exercise routes, you need to request the following permissions:

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

To request permissions, use the PermissionController.createRequestPermissionResultContract() method when you first connect your app to Health Connect. Several permissions that you might want to request are:

  • Read health data, including route data: HealthPermission.getReadPermission(ExerciseSessionRecord::class)
  • Write health data, including route data: HealthPermission.getWritePermission(ExerciseSessionRecord::class)
  • Write exercise route data: HealthPermission.PERMISSION_WRITE_EXERCISE_ROUTE

Read and write route data

Apps insert a route by writing a session with a route as a field.

If the user doesn't have write permissions and the route is not set, the route doesn't update.

If your app has a route write permission and tries to update a session by passing in a session object without a route, the existing route is deleted.

Whenever your app needs to read route data provided by a third-party app, a dialog appears asking the user to allow the read operation.

Request a route from a session

Here's how to read a session in Health Connect and request a route from that session:

suspend fun readExerciseSessionAndRoute() {
    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()

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

Write a route from a session

The following code demonstrates how to record a session that includes an exercise route:

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.parse("2023-01-01T10:00:00.00Z")
    val sessionDuration = Duration.ofMinutes(20)

    val exerciseRoute =
      if (getPermissions.contains(PERMISSION_EXERCISE_ROUTE_WRITE) {
        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 = sessionStartTime.plus(sessionDuration)
              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 = /* starting time in milliseconds */,
            startZoneOffset = ZoneOffset.UTC,
            endTime = sessionStartTime.plus(sessionDuration),
            endZoneOffset = ZoneOffset.UTC,
            exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
            title = "Morning Bike Ride",
            exerciseRoute = exerciseRoute
        )

    healthConnectClient.insertRecords(listOf(exerciseSessionRecord))
}