[go: nahoru, domu]

Add request exercise route contract and expose exercise route.

In more detail:
1. Create a new contracts package, which contains the new exercise route contract and a copy of the existing health permissions contract.
2. Add different subclasses for ExerciseRoute, depending on data and consent state. There are three possible states for the ExerciseRoute class: Data (for value returned in the record request), ConsentRequired (if the caller app doesn't yet have permission from the user to read the route), NoData (if there's no data to request permissions for).
3. Expose ExerciseRoute in the public API and the respective constructor of ExerciseSessionRecord that takes an ExerciseRoute.

Contracts package is inspired by a pattern set by contracts in Jetpack (see known direct subclasses):
https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract

Test: Added.
Relnote: Added ExerciseRoute, ExerciseRoute.Data, ExerciseRoute.ConsentRequired and ExerciseRoute.NoData, exposed ExerciseRoute in the public API, added ExerciseRouteRequestContract, added HealthPermissionsRequestContract.
Fix: 283450357
Fix: 286207148

Change-Id: Ief0e516cb08662c97cbee429bad1a3d284acb5b2
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 7985cd9..7403050 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -107,6 +107,22 @@
 
 }
 
+package androidx.health.connect.client.contracts {
+
+  public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute.Data> {
+    ctor public ExerciseRouteRequestContract();
+    method public android.content.Intent createIntent(android.content.Context context, String input);
+    method public androidx.health.connect.client.records.ExerciseRoute.Data? parseResult(int resultCode, android.content.Intent? intent);
+  }
+
+  public final class HealthPermissionsRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.util.Set<? extends java.lang.String>,java.util.Set<? extends java.lang.String>> {
+    ctor public HealthPermissionsRequestContract(optional String providerPackageName);
+    method public android.content.Intent createIntent(android.content.Context context, java.util.Set<java.lang.String> input);
+    method public java.util.Set<java.lang.String> parseResult(int resultCode, android.content.Intent? intent);
+  }
+
+}
+
 package androidx.health.connect.client.permission {
 
   public final class HealthPermission {
@@ -425,6 +441,43 @@
     property public final java.time.Instant startTime;
   }
 
+  public abstract class ExerciseRoute {
+  }
+
+  public static final class ExerciseRoute.ConsentRequired extends androidx.health.connect.client.records.ExerciseRoute {
+    ctor public ExerciseRoute.ConsentRequired();
+  }
+
+  public static final class ExerciseRoute.Data extends androidx.health.connect.client.records.ExerciseRoute {
+    ctor public ExerciseRoute.Data(java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route);
+    method public java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> getRoute();
+    property public final java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route;
+  }
+
+  public static final class ExerciseRoute.Location {
+    ctor public ExerciseRoute.Location(java.time.Instant time, double latitude, double longitude, optional androidx.health.connect.client.units.Length? horizontalAccuracy, optional androidx.health.connect.client.units.Length? verticalAccuracy, optional androidx.health.connect.client.units.Length? altitude);
+    method public androidx.health.connect.client.units.Length? getAltitude();
+    method public androidx.health.connect.client.units.Length? getHorizontalAccuracy();
+    method public double getLatitude();
+    method public double getLongitude();
+    method public java.time.Instant getTime();
+    method public androidx.health.connect.client.units.Length? getVerticalAccuracy();
+    property public final androidx.health.connect.client.units.Length? altitude;
+    property public final androidx.health.connect.client.units.Length? horizontalAccuracy;
+    property public final double latitude;
+    property public final double longitude;
+    property public final java.time.Instant time;
+    property public final androidx.health.connect.client.units.Length? verticalAccuracy;
+    field public static final androidx.health.connect.client.records.ExerciseRoute.Location.Companion Companion;
+  }
+
+  public static final class ExerciseRoute.Location.Companion {
+  }
+
+  public static final class ExerciseRoute.NoData extends androidx.health.connect.client.records.ExerciseRoute {
+    ctor public ExerciseRoute.NoData();
+  }
+
   public final class ExerciseSegment {
     ctor public ExerciseSegment(java.time.Instant startTime, java.time.Instant endTime, int segmentType, optional int repetitions);
     method public java.time.Instant getEndTime();
@@ -510,9 +563,16 @@
   }
 
   public final class ExerciseSessionRecord implements androidx.health.connect.client.records.Record {
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments);
     ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps, optional androidx.health.connect.client.records.ExerciseRoute.Data? exerciseRouteData);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
+    method public androidx.health.connect.client.records.ExerciseRoute getExerciseRoute();
     method public int getExerciseType();
     method public java.util.List<androidx.health.connect.client.records.ExerciseLap> getLaps();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
@@ -523,6 +583,7 @@
     method public String? getTitle();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
+    property public final androidx.health.connect.client.records.ExerciseRoute exerciseRoute;
     property public final int exerciseType;
     property public final java.util.List<androidx.health.connect.client.records.ExerciseLap> laps;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 4fc8f3b..7300554 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -107,6 +107,22 @@
 
 }
 
+package androidx.health.connect.client.contracts {
+
+  public final class ExerciseRouteRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.lang.String,androidx.health.connect.client.records.ExerciseRoute.Data> {
+    ctor public ExerciseRouteRequestContract();
+    method public android.content.Intent createIntent(android.content.Context context, String input);
+    method public androidx.health.connect.client.records.ExerciseRoute.Data? parseResult(int resultCode, android.content.Intent? intent);
+  }
+
+  public final class HealthPermissionsRequestContract extends androidx.activity.result.contract.ActivityResultContract<java.util.Set<? extends java.lang.String>,java.util.Set<? extends java.lang.String>> {
+    ctor public HealthPermissionsRequestContract(optional String providerPackageName);
+    method public android.content.Intent createIntent(android.content.Context context, java.util.Set<java.lang.String> input);
+    method public java.util.Set<java.lang.String> parseResult(int resultCode, android.content.Intent? intent);
+  }
+
+}
+
 package androidx.health.connect.client.permission {
 
   public final class HealthPermission {
@@ -425,6 +441,43 @@
     property public final java.time.Instant startTime;
   }
 
+  public abstract class ExerciseRoute {
+  }
+
+  public static final class ExerciseRoute.ConsentRequired extends androidx.health.connect.client.records.ExerciseRoute {
+    ctor public ExerciseRoute.ConsentRequired();
+  }
+
+  public static final class ExerciseRoute.Data extends androidx.health.connect.client.records.ExerciseRoute {
+    ctor public ExerciseRoute.Data(java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route);
+    method public java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> getRoute();
+    property public final java.util.List<androidx.health.connect.client.records.ExerciseRoute.Location> route;
+  }
+
+  public static final class ExerciseRoute.Location {
+    ctor public ExerciseRoute.Location(java.time.Instant time, double latitude, double longitude, optional androidx.health.connect.client.units.Length? horizontalAccuracy, optional androidx.health.connect.client.units.Length? verticalAccuracy, optional androidx.health.connect.client.units.Length? altitude);
+    method public androidx.health.connect.client.units.Length? getAltitude();
+    method public androidx.health.connect.client.units.Length? getHorizontalAccuracy();
+    method public double getLatitude();
+    method public double getLongitude();
+    method public java.time.Instant getTime();
+    method public androidx.health.connect.client.units.Length? getVerticalAccuracy();
+    property public final androidx.health.connect.client.units.Length? altitude;
+    property public final androidx.health.connect.client.units.Length? horizontalAccuracy;
+    property public final double latitude;
+    property public final double longitude;
+    property public final java.time.Instant time;
+    property public final androidx.health.connect.client.units.Length? verticalAccuracy;
+    field public static final androidx.health.connect.client.records.ExerciseRoute.Location.Companion Companion;
+  }
+
+  public static final class ExerciseRoute.Location.Companion {
+  }
+
+  public static final class ExerciseRoute.NoData extends androidx.health.connect.client.records.ExerciseRoute {
+    ctor public ExerciseRoute.NoData();
+  }
+
   public final class ExerciseSegment {
     ctor public ExerciseSegment(java.time.Instant startTime, java.time.Instant endTime, int segmentType, optional int repetitions);
     method public java.time.Instant getEndTime();
@@ -510,9 +563,16 @@
   }
 
   public final class ExerciseSessionRecord implements androidx.health.connect.client.records.IntervalRecord {
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments);
     ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps);
+    ctor public ExerciseSessionRecord(java.time.Instant startTime, java.time.ZoneOffset? startZoneOffset, java.time.Instant endTime, java.time.ZoneOffset? endZoneOffset, int exerciseType, optional String? title, optional String? notes, optional androidx.health.connect.client.records.metadata.Metadata metadata, optional java.util.List<androidx.health.connect.client.records.ExerciseSegment> segments, optional java.util.List<androidx.health.connect.client.records.ExerciseLap> laps, optional androidx.health.connect.client.records.ExerciseRoute.Data? exerciseRouteData);
     method public java.time.Instant getEndTime();
     method public java.time.ZoneOffset? getEndZoneOffset();
+    method public androidx.health.connect.client.records.ExerciseRoute getExerciseRoute();
     method public int getExerciseType();
     method public java.util.List<androidx.health.connect.client.records.ExerciseLap> getLaps();
     method public androidx.health.connect.client.records.metadata.Metadata getMetadata();
@@ -523,6 +583,7 @@
     method public String? getTitle();
     property public java.time.Instant endTime;
     property public java.time.ZoneOffset? endZoneOffset;
+    property public final androidx.health.connect.client.records.ExerciseRoute exerciseRoute;
     property public final int exerciseType;
     property public final java.util.List<androidx.health.connect.client.records.ExerciseLap> laps;
     property public androidx.health.connect.client.records.metadata.Metadata metadata;
diff --git a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
index 3f35587..8f5cca7 100644
--- a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
+++ b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/ReadRecordsSamples.kt
@@ -18,8 +18,11 @@
 
 package androidx.health.connect.client.samples
 
+import androidx.activity.result.ActivityResultCaller
 import androidx.annotation.Sampled
 import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.contracts.ExerciseRouteRequestContract
+import androidx.health.connect.client.records.ExerciseRoute
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.HeartRateRecord
 import androidx.health.connect.client.records.SleepSessionRecord
@@ -80,6 +83,37 @@
 }
 
 @Sampled
+suspend fun ReadExerciseRoute(
+    activityResultCaller: ActivityResultCaller,
+    healthConnectClient: HealthConnectClient,
+    displayExerciseRoute: (ExerciseRoute.Data) -> Unit,
+    recordId: String
+) {
+    // See https://developer.android.com/training/basics/intents/result#launch for appropriately
+    // handling ActivityResultContract.
+    val requestExerciseRoute =
+        activityResultCaller.registerForActivityResult(ExerciseRouteRequestContract()) {
+            exerciseRoute: ExerciseRoute.Data? ->
+            if (exerciseRoute != null) {
+                displayExerciseRoute(exerciseRoute)
+            } else {
+                // Consent was denied
+            }
+        }
+
+    // Show exercise route, based on user action
+    val exerciseSessionRecord =
+        healthConnectClient.readRecord(ExerciseSessionRecord::class, recordId).record
+
+    when (val exerciseRoute = exerciseSessionRecord.exerciseRoute) {
+        is ExerciseRoute.Data -> displayExerciseRoute(exerciseRoute)
+        is ExerciseRoute.ConsentRequired -> requestExerciseRoute.launch(recordId)
+        is ExerciseRoute.NoData -> Unit // No exercise route to show
+        else -> Unit
+    }
+}
+
+@Sampled
 suspend fun ReadSleepSessions(
     healthConnectClient: HealthConnectClient,
     startTime: Instant,
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContractTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContractTest.kt
new file mode 100644
index 0000000..293d577
--- /dev/null
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContractTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.connect.client.contracts
+
+import android.content.Context
+import android.health.connect.HealthConnectManager
+import android.os.Build
+import androidx.health.platform.client.service.HealthDataServiceConstants
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+@MediumTest
+class ExerciseRouteRequestContractTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    fun requestExerciseRoute_createIntent_hasPlatformIntentAction() {
+        val intent = ExerciseRouteRequestContract().createIntent(context, "sessionId")
+        assertThat(intent.action).isEqualTo(HealthConnectManager.ACTION_REQUEST_EXERCISE_ROUTE)
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    fun requestExerciseRoute_createIntent_hasApkIntentAction() {
+        val intent = ExerciseRouteRequestContract().createIntent(context, "sessionId")
+        assertThat(intent.action).isEqualTo(HealthDataServiceConstants.ACTION_REQUEST_ROUTE)
+    }
+}
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContractTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContractTest.kt
new file mode 100644
index 0000000..0c7cdb5d
--- /dev/null
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContractTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.connect.client.contracts
+
+import android.content.Context
+import android.os.Build
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.permission.HealthPermission
+import androidx.health.platform.client.service.HealthDataServiceConstants
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+@MediumTest
+class HealthPermissionsRequestContractTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    fun requestHealthPermissions_createIntent_hasPlatformIntentAction() {
+        val intent =
+            HealthPermissionsRequestContract()
+                .createIntent(context, setOf(HealthPermission.READ_STEPS))
+        assertThat(intent.action)
+            .isEqualTo(
+                ActivityResultContracts.RequestMultiplePermissions.ACTION_REQUEST_PERMISSIONS
+            )
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    fun requestHealthPermissions_createIntent_hasApkIntentAction() {
+        val intent =
+            HealthPermissionsRequestContract()
+                .createIntent(context, setOf(HealthPermission.READ_STEPS))
+        assertThat(intent.action).isEqualTo(HealthDataServiceConstants.ACTION_REQUEST_PERMISSIONS)
+        assertThat(intent.`package`).isEqualTo(HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME)
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    fun requestHealthPermissions_createIntent_hasApkIntentActionAndProvider() {
+        val intent =
+            HealthPermissionsRequestContract("some.provider")
+                .createIntent(context, setOf(HealthPermission.READ_STEPS))
+        assertThat(intent.action).isEqualTo(HealthDataServiceConstants.ACTION_REQUEST_PERMISSIONS)
+        assertThat(intent.`package`).isEqualTo("some.provider")
+    }
+}
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
index ea4548d..23160ad 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
@@ -80,7 +80,7 @@
         val intent = Intent()
         intent.putExtra(HealthConnectManager.EXTRA_EXERCISE_ROUTE, PlatformExerciseRoute(listOf()))
         val result = requestRouteContract.parseResult(0, intent)
-        assertThat(result).isEqualTo(ExerciseRoute(listOf()))
+        assertThat(result).isEqualTo(ExerciseRoute.Data(listOf()))
     }
 
     @Test
@@ -103,7 +103,7 @@
         val result = requestRouteContract.parseResult(0, intent)
         assertThat(result)
             .isEqualTo(
-                ExerciseRoute(
+                ExerciseRoute.Data(
                     listOf(
                         ExerciseRoute.Location(
                             time = Instant.ofEpochMilli(1234L),
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt
index 892ed70..d0cf773 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RecordConvertersTest.kt
@@ -407,8 +407,8 @@
                                 10
                             )
                         ),
-                    route =
-                        ExerciseRoute(
+                    exerciseRoute =
+                        ExerciseRoute.Data(
                             listOf(
                                 ExerciseRoute.Location(
                                     START_TIME,
@@ -1179,7 +1179,7 @@
 
     @Test
     fun exerciseSessionRecord_convertToSdk() {
-        val sdkExerciseSession =
+        val platformExerciseSessionBuilder =
             PlatformExerciseSessionRecordBuilder(
                     PLATFORM_METADATA,
                     START_TIME,
@@ -1230,8 +1230,9 @@
                         )
                     )
                 )
-                .build()
-                .toSdkRecord() as ExerciseSessionRecord
+
+        var sdkExerciseSession =
+            platformExerciseSessionBuilder.build().toSdkRecord() as ExerciseSessionRecord
 
         assertSdkRecord(sdkExerciseSession) {
             assertThat(title).isEqualTo("Training")
@@ -1260,9 +1261,9 @@
                         10
                     )
                 )
-            assertThat(route)
+            assertThat(exerciseRoute as ExerciseRoute.Data)
                 .isEqualTo(
-                    ExerciseRoute(
+                    ExerciseRoute.Data(
                         listOf(
                             ExerciseRoute.Location(
                                 time = START_TIME,
@@ -1276,6 +1277,14 @@
                     )
                 )
         }
+
+        sdkExerciseSession =
+            platformExerciseSessionBuilder.setRoute(null).build().toSdkRecord()
+                as ExerciseSessionRecord
+
+        assertSdkRecord(sdkExerciseSession) {
+            assertThat(exerciseRoute).isEqualTo(ExerciseRoute.NoData())
+        }
     }
 
     @Test
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
index 18ebf35..1a840d2 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
@@ -15,13 +15,12 @@
  */
 package androidx.health.connect.client
 
-import android.os.Build
 import androidx.activity.result.contract.ActivityResultContract
 import androidx.annotation.RestrictTo
 import androidx.health.connect.client.HealthConnectClient.Companion.DEFAULT_PROVIDER_PACKAGE_NAME
+import androidx.health.connect.client.contracts.HealthPermissionsRequestContract
 import androidx.health.connect.client.permission.HealthDataRequestPermissionsInternal
 import androidx.health.connect.client.permission.HealthPermission
-import androidx.health.connect.client.permission.platform.HealthDataRequestPermissionsUpsideDownCake
 
 @JvmDefaultWithCompatibility
 /** Interface for operations related to permissions. */
@@ -71,10 +70,7 @@
         fun createRequestPermissionResultContract(
             providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME
         ): ActivityResultContract<Set<String>, Set<String>> {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-                return HealthDataRequestPermissionsUpsideDownCake()
-            }
-            return HealthDataRequestPermissionsInternal(providerPackageName = providerPackageName)
+            return HealthPermissionsRequestContract(providerPackageName)
         }
     }
 }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
new file mode 100644
index 0000000..6e8e74b
--- /dev/null
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.connect.client.contracts
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.health.connect.client.permission.RequestExerciseRouteInternal
+import androidx.health.connect.client.permission.platform.RequestExerciseRouteUpsideDownCake
+import androidx.health.connect.client.records.ExerciseRoute
+
+/**
+ * An [ActivityResultContract] to request a route associated with an
+ * [androidx.health.connect.client.records.ExerciseSessionRecord].
+ *
+ * It receives the session id as input and returns an ExerciseRoute as output, if available.
+ *
+ * @sample androidx.health.connect.client.samples.ReadExerciseRoute
+ */
+class ExerciseRouteRequestContract : ActivityResultContract<String, ExerciseRoute.Data?>() {
+
+    private val delegate: ActivityResultContract<String, ExerciseRoute.Data?> =
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            RequestExerciseRouteUpsideDownCake()
+        } else {
+            RequestExerciseRouteInternal()
+        }
+
+    /**
+     * Creates an intent to request an [ExerciseRoute.Data]. It receives the exercise session id as
+     * [input].
+     *
+     * @param context the context
+     * @param input the exercise session id obtained via
+     *   [androidx.health.connect.client.records.ExerciseSessionRecord.metadata]
+     * @throws IllegalArgumentException if the [input] is an empty string.
+     * @see ActivityResultContract.createIntent
+     */
+    override fun createIntent(context: Context, input: String): Intent {
+        return delegate.createIntent(context, input)
+    }
+
+    /**
+     * Converts the activity result into [ExerciseRoute.Data], to return as output.
+     *
+     * @return null if the user didn't grant access to the exercise route or if there's no exercise
+     *   route for the session id passed on [createIntent].
+     * @see ActivityResultContract.parseResult
+     */
+    override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute.Data? {
+        return delegate.parseResult(resultCode, intent)
+    }
+}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
new file mode 100644
index 0000000..1251304
--- /dev/null
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.connect.client.contracts
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.permission.HealthDataRequestPermissionsInternal
+import androidx.health.connect.client.permission.platform.HealthDataRequestPermissionsUpsideDownCake
+
+/**
+ * An [ActivityResultContract] to request Health permissions.
+ *
+ * It receives a set of permissions as input and returns a set with the granted permissions as
+ * output.
+ */
+class HealthPermissionsRequestContract(
+    providerPackageName: String = HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
+) : ActivityResultContract<Set<String>, Set<String>>() {
+
+    private val delegate: ActivityResultContract<Set<String>, Set<String>> =
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            HealthDataRequestPermissionsUpsideDownCake()
+        } else {
+            HealthDataRequestPermissionsInternal(providerPackageName)
+        }
+
+    /**
+     * Creates an intent to request HealthConnect permissions. It receives as [input] a [Set] of
+     * HealthConnect permissions.
+     *
+     * @param context the context
+     * @param input the health permission strings to request permissions for
+     * @see ActivityResultContract.createIntent
+     */
+    override fun createIntent(context: Context, input: Set<String>): Intent {
+        return delegate.createIntent(context, input)
+    }
+
+    /**
+     * Converts the activity result into a [Set] of granted permissions. This will be a subset of
+     * [Set] passed in [createIntent].
+     *
+     * @see ActivityResultContract.parseResult
+     */
+    override fun parseResult(resultCode: Int, intent: Intent?): Set<String> {
+        return delegate.parseResult(resultCode, intent)
+    }
+}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
index a410c3f..dc60de8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -404,7 +404,7 @@
                     endZoneOffset = endZoneOffset,
                     metadata = metadata
                 )
-            "ActivitySession" ->
+            "ActivitySession" -> {
                 ExerciseSessionRecord(
                     exerciseType =
                         mapEnum(
@@ -421,12 +421,15 @@
                     metadata = metadata,
                     segments = subTypeDataListsMap["segments"]?.toSegmentList() ?: emptyList(),
                     laps = subTypeDataListsMap["laps"]?.toLapList() ?: emptyList(),
-                    route =
+                    exerciseRoute =
                         subTypeDataListsMap["route"]?.let {
-                            ExerciseRoute(route = it.toLocationList())
-                        },
-                    hasRoute = valuesMap["hasRoute"]?.booleanVal ?: false,
+                            ExerciseRoute.Data(route = it.toLocationList())
+                        }
+                            ?: if (valuesMap["hasRoute"]?.booleanVal == true)
+                                ExerciseRoute.ConsentRequired()
+                            else ExerciseRoute.NoData(),
                 )
+            }
             "Distance" ->
                 DistanceRecord(
                     distance = getDouble("distance").meters,
@@ -582,10 +585,10 @@
         }
     }
 
-fun toExerciseRoute(
+fun toExerciseRouteData(
     protoWrapper: androidx.health.platform.client.exerciseroute.ExerciseRoute
-): ExerciseRoute {
-    return ExerciseRoute(
+): ExerciseRoute.Data {
+    return ExerciseRoute.Data(
         protoWrapper.proto.valuesList.map { value ->
             ExerciseRoute.Location(
                 time = Instant.ofEpochMilli(value.startTimeMillis),
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
index f986b53..e9e29d8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
@@ -34,6 +34,7 @@
 import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
 import androidx.health.connect.client.records.DistanceRecord
 import androidx.health.connect.client.records.ElevationGainedRecord
+import androidx.health.connect.client.records.ExerciseRoute
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.FloorsClimbedRecord
 import androidx.health.connect.client.records.HeartRateRecord
@@ -75,41 +76,38 @@
                 .apply {
                     putValues("temperature", doubleVal(temperature.inCelsius))
                     enumValFromInt(
-                        measurementLocation,
-                        BodyTemperatureMeasurementLocation
-                            .MEASUREMENT_LOCATION_INT_TO_STRING_MAP,
-                    )
+                            measurementLocation,
+                            BodyTemperatureMeasurementLocation
+                                .MEASUREMENT_LOCATION_INT_TO_STRING_MAP,
+                        )
                         ?.let { putValues("measurementLocation", it) }
                 }
                 .build()
-
         is BasalMetabolicRateRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("BasalMetabolicRate"))
                 .apply { putValues("bmr", doubleVal(basalMetabolicRate.inKilocaloriesPerDay)) }
                 .build()
-
         is BloodGlucoseRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("BloodGlucose"))
                 .apply {
                     putValues("level", doubleVal(level.inMillimolesPerLiter))
                     enumValFromInt(
-                        specimenSource,
-                        BloodGlucoseRecord.SPECIMEN_SOURCE_INT_TO_STRING_MAP
-                    )
+                            specimenSource,
+                            BloodGlucoseRecord.SPECIMEN_SOURCE_INT_TO_STRING_MAP
+                        )
                         ?.let { putValues("specimenSource", it) }
                     enumValFromInt(mealType, MealType.MEAL_TYPE_INT_TO_STRING_MAP)?.let {
                         putValues("mealType", it)
                     }
                     enumValFromInt(
-                        relationToMeal,
-                        BloodGlucoseRecord.RELATION_TO_MEAL_INT_TO_STRING_MAP,
-                    )
+                            relationToMeal,
+                            BloodGlucoseRecord.RELATION_TO_MEAL_INT_TO_STRING_MAP,
+                        )
                         ?.let { putValues("relationToMeal", it) }
                 }
                 .build()
-
         is BloodPressureRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("BloodPressure"))
@@ -117,50 +115,45 @@
                     putValues("systolic", doubleVal(systolic.inMillimetersOfMercury))
                     putValues("diastolic", doubleVal(diastolic.inMillimetersOfMercury))
                     enumValFromInt(
-                        bodyPosition,
-                        BloodPressureRecord.BODY_POSITION_INT_TO_STRING_MAP
-                    )
+                            bodyPosition,
+                            BloodPressureRecord.BODY_POSITION_INT_TO_STRING_MAP
+                        )
                         ?.let { putValues("bodyPosition", it) }
                     enumValFromInt(
-                        measurementLocation,
-                        BloodPressureRecord.MEASUREMENT_LOCATION_INT_TO_STRING_MAP
-                    )
+                            measurementLocation,
+                            BloodPressureRecord.MEASUREMENT_LOCATION_INT_TO_STRING_MAP
+                        )
                         ?.let { putValues("measurementLocation", it) }
                 }
                 .build()
-
         is BodyFatRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("BodyFat"))
                 .apply { putValues("percentage", doubleVal(percentage.value)) }
                 .build()
-
         is BodyTemperatureRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("BodyTemperature"))
                 .apply {
                     putValues("temperature", doubleVal(temperature.inCelsius))
                     enumValFromInt(
-                        measurementLocation,
-                        BodyTemperatureMeasurementLocation
-                            .MEASUREMENT_LOCATION_INT_TO_STRING_MAP,
-                    )
+                            measurementLocation,
+                            BodyTemperatureMeasurementLocation
+                                .MEASUREMENT_LOCATION_INT_TO_STRING_MAP,
+                        )
                         ?.let { putValues("measurementLocation", it) }
                 }
                 .build()
-
         is BodyWaterMassRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("BodyWaterMass"))
                 .apply { putValues("mass", doubleVal(mass.inKilograms)) }
                 .build()
-
         is BoneMassRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("BoneMass"))
                 .apply { putValues("mass", doubleVal(mass.inKilograms)) }
                 .build()
-
         is CervicalMucusRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("CervicalMucus"))
@@ -173,7 +166,6 @@
                     }
                 }
                 .build()
-
         is CyclingPedalingCadenceRecord ->
             toProto(dataTypeName = "CyclingPedalingCadenceSeries") { sample ->
                 DataProto.SeriesValue.newBuilder()
@@ -181,7 +173,6 @@
                     .setInstantTimeMillis(sample.time.toEpochMilli())
                     .build()
             }
-
         is HeartRateRecord ->
             toProto(dataTypeName = "HeartRateSeries") { sample ->
                 DataProto.SeriesValue.newBuilder()
@@ -189,28 +180,23 @@
                     .setInstantTimeMillis(sample.time.toEpochMilli())
                     .build()
             }
-
         is HeightRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("Height"))
                 .apply { putValues("height", doubleVal(height.inMeters)) }
                 .build()
-
         is HeartRateVariabilityRmssdRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("HeartRateVariabilityRmssd"))
                 .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
                 .build()
-
         is IntermenstrualBleedingRecord ->
             instantaneousProto().setDataType(protoDataType("IntermenstrualBleeding")).build()
-
         is LeanBodyMassRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("LeanBodyMass"))
                 .apply { putValues("mass", doubleVal(mass.inKilograms)) }
                 .build()
-
         is MenstruationFlowRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("Menstruation"))
@@ -220,10 +206,8 @@
                     }
                 }
                 .build()
-
         is MenstruationPeriodRecord ->
             intervalProto().setDataType(protoDataType("MenstruationPeriod")).build()
-
         is OvulationTestRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("OvulationTest"))
@@ -233,13 +217,11 @@
                     }
                 }
                 .build()
-
         is OxygenSaturationRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("OxygenSaturation"))
                 .apply { putValues("percentage", doubleVal(percentage.value)) }
                 .build()
-
         is PowerRecord ->
             toProto(dataTypeName = "PowerSeries") { sample ->
                 DataProto.SeriesValue.newBuilder()
@@ -247,31 +229,27 @@
                     .setInstantTimeMillis(sample.time.toEpochMilli())
                     .build()
             }
-
         is RespiratoryRateRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("RespiratoryRate"))
                 .apply { putValues("rate", doubleVal(rate)) }
                 .build()
-
         is RestingHeartRateRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("RestingHeartRate"))
                 .apply { putValues("bpm", longVal(beatsPerMinute)) }
                 .build()
-
         is SexualActivityRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("SexualActivity"))
                 .apply {
                     enumValFromInt(
-                        protectionUsed,
-                        SexualActivityRecord.PROTECTION_USED_INT_TO_STRING_MAP
-                    )
+                            protectionUsed,
+                            SexualActivityRecord.PROTECTION_USED_INT_TO_STRING_MAP
+                        )
                         ?.let { putValues("protectionUsed", it) }
                 }
                 .build()
-
         is SpeedRecord ->
             toProto(dataTypeName = "SpeedSeries") { sample ->
                 DataProto.SeriesValue.newBuilder()
@@ -279,7 +257,6 @@
                     .setInstantTimeMillis(sample.time.toEpochMilli())
                     .build()
             }
-
         is StepsCadenceRecord ->
             toProto(dataTypeName = "StepsCadenceSeries") { sample ->
                 DataProto.SeriesValue.newBuilder()
@@ -287,36 +264,32 @@
                     .setInstantTimeMillis(sample.time.toEpochMilli())
                     .build()
             }
-
         is Vo2MaxRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("Vo2Max"))
                 .apply {
                     putValues("vo2", doubleVal(vo2MillilitersPerMinuteKilogram))
                     enumValFromInt(
-                        measurementMethod,
-                        Vo2MaxRecord.MEASUREMENT_METHOD_INT_TO_STRING_MAP
-                    )
+                            measurementMethod,
+                            Vo2MaxRecord.MEASUREMENT_METHOD_INT_TO_STRING_MAP
+                        )
                         ?.let { putValues("measurementMethod", it) }
                 }
                 .build()
-
         is WeightRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("Weight"))
                 .apply { putValues("weight", doubleVal(weight.inKilograms)) }
                 .build()
-
         is ActiveCaloriesBurnedRecord ->
             intervalProto()
                 .setDataType(protoDataType("ActiveCaloriesBurned"))
                 .apply { putValues("energy", doubleVal(energy.inKilocalories)) }
                 .build()
-
         is ExerciseSessionRecord ->
             intervalProto()
                 .setDataType(protoDataType("ActivitySession"))
-                .putValues("hasRoute", boolVal(hasRoute))
+                .putValues("hasRoute", boolVal(exerciseRoute !is ExerciseRoute.NoData))
                 .apply {
                     val exerciseType =
                         enumValFromInt(
@@ -331,50 +304,48 @@
                         putSubTypeDataLists(
                             "segments",
                             DataProto.DataPoint.SubTypeDataList.newBuilder()
-                                .addAllValues(segments.map { it.toProto() }).build()
+                                .addAllValues(segments.map { it.toProto() })
+                                .build()
                         )
                     }
                     if (laps.isNotEmpty()) {
                         putSubTypeDataLists(
                             "laps",
                             DataProto.DataPoint.SubTypeDataList.newBuilder()
-                                .addAllValues(laps.map { it.toProto() }).build()
+                                .addAllValues(laps.map { it.toProto() })
+                                .build()
                         )
                     }
-                    route?.let { exerciseRoute ->
+                    if (exerciseRoute is ExerciseRoute.Data) {
                         putSubTypeDataLists(
                             "route",
                             DataProto.DataPoint.SubTypeDataList.newBuilder()
-                                .addAllValues(exerciseRoute.route.map { it.toProto() }).build()
+                                .addAllValues(exerciseRoute.route.map { it.toProto() })
+                                .build()
                         )
                     }
                 }
                 .build()
-
         is DistanceRecord ->
             intervalProto()
                 .setDataType(protoDataType("Distance"))
                 .apply { putValues("distance", doubleVal(distance.inMeters)) }
                 .build()
-
         is ElevationGainedRecord ->
             intervalProto()
                 .setDataType(protoDataType("ElevationGained"))
                 .apply { putValues("elevation", doubleVal(elevation.inMeters)) }
                 .build()
-
         is FloorsClimbedRecord ->
             intervalProto()
                 .setDataType(protoDataType("FloorsClimbed"))
                 .apply { putValues("floors", doubleVal(floors)) }
                 .build()
-
         is HydrationRecord ->
             intervalProto()
                 .setDataType(protoDataType("Hydration"))
                 .apply { putValues("volume", doubleVal(volume.inLiters)) }
                 .build()
-
         is NutritionRecord ->
             intervalProto()
                 .setDataType(protoDataType("Nutrition"))
@@ -511,7 +482,6 @@
                     name?.let { putValues("name", stringVal(it)) }
                 }
                 .build()
-
         is SleepSessionRecord ->
             intervalProto()
                 .setDataType(protoDataType("SleepSession"))
@@ -520,14 +490,14 @@
                         putSubTypeDataLists(
                             "stages",
                             DataProto.DataPoint.SubTypeDataList.newBuilder()
-                                .addAllValues(stages.map { it.toProto() }).build()
+                                .addAllValues(stages.map { it.toProto() })
+                                .build()
                         )
                     }
                     title?.let { putValues("title", stringVal(it)) }
                     notes?.let { putValues("notes", stringVal(it)) }
                 }
                 .build()
-
         is SleepStageRecord ->
             intervalProto()
                 .setDataType(protoDataType("SleepStage"))
@@ -537,25 +507,21 @@
                     }
                 }
                 .build()
-
         is StepsRecord ->
             intervalProto()
                 .setDataType(protoDataType("Steps"))
                 .apply { putValues("count", longVal(count)) }
                 .build()
-
         is TotalCaloriesBurnedRecord ->
             intervalProto()
                 .setDataType(protoDataType("TotalCaloriesBurned"))
                 .apply { putValues("energy", doubleVal(energy.inKilocalories)) }
                 .build()
-
         is WheelchairPushesRecord ->
             intervalProto()
                 .setDataType(protoDataType("WheelchairPushes"))
                 .apply { putValues("count", longVal(count)) }
                 .build()
-
         else -> throw RuntimeException("Unsupported yet!")
     }
 
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt
index e82005f..4da86be 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RecordConverters.kt
@@ -290,11 +290,11 @@
         exerciseType = exerciseType.toSdkExerciseSessionType(),
         title = title?.toString(),
         notes = notes?.toString(),
-        route = route?.toSdkExerciseRoute(),
         laps = laps.map { it.toSdkExerciseLap() }.sortedBy { it.startTime },
         segments = segments.map { it.toSdkExerciseSegment() }.sortedBy { it.startTime },
-        hasRoute = hasRoute(),
         metadata = metadata.toSdkMetadata(),
+        exerciseRoute = route?.toSdkExerciseRouteData()
+                ?: if (hasRoute()) ExerciseRoute.ConsentRequired() else ExerciseRoute.NoData(),
     )
 
 private fun PlatformFloorsClimbedRecord.toSdkFloorsClimbedRecord() =
@@ -706,9 +706,11 @@
             endZoneOffset?.let { setEndZoneOffset(it) }
             notes?.let { setNotes(it) }
             title?.let { setTitle(it) }
-            route?.let { setRoute(it.toPlatformExerciseRoute()) }
             setLaps(laps.map { it.toPlatformExerciseLap() })
             setSegments(segments.map { it.toPlatformExerciseSegment() })
+            if (exerciseRoute is ExerciseRoute.Data) {
+                setRoute(exerciseRoute.toPlatformExerciseRoute())
+            }
         }
         .build()
 
@@ -717,7 +719,7 @@
         .apply { length?.let { setLength(it.toPlatformLength()) } }
         .build()
 
-private fun ExerciseRoute.toPlatformExerciseRoute() =
+private fun ExerciseRoute.Data.toPlatformExerciseRoute() =
     PlatformExerciseRoute(
         route.map { location ->
             PlatformExerciseRouteLocationBuilder(
@@ -1031,8 +1033,8 @@
 private fun PlatformSleepSessionStage.toSdkSleepSessionStage() =
     SleepSessionRecord.Stage(startTime, endTime, type.toSdkSleepStageType())
 
-internal fun PlatformExerciseRoute.toSdkExerciseRoute() =
-    ExerciseRoute(
+internal fun PlatformExerciseRoute.toSdkExerciseRouteData() =
+    ExerciseRoute.Data(
         routeLocations.map { value ->
             ExerciseRoute.Location(
                 time = value.time,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
index 4417c02..1fdba9c 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
@@ -21,7 +21,7 @@
 import androidx.activity.result.contract.ActivityResultContract
 import androidx.annotation.RestrictTo
 import androidx.health.connect.client.HealthConnectClient
-import androidx.health.connect.client.impl.converters.records.toExerciseRoute
+import androidx.health.connect.client.impl.converters.records.toExerciseRouteData
 import androidx.health.connect.client.records.ExerciseRoute
 import androidx.health.platform.client.impl.logger.Logger
 import androidx.health.platform.client.service.HealthDataServiceConstants
@@ -32,16 +32,17 @@
  * @see androidx.activity.ComponentActivity.registerForActivityResult
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-internal class RequestExerciseRouteInternal : ActivityResultContract<String?, ExerciseRoute?>() {
-    override fun createIntent(context: Context, input: String?): Intent {
-        require(!input.isNullOrEmpty()) { "Session identifier is required" }
+internal class RequestExerciseRouteInternal :
+    ActivityResultContract<String, ExerciseRoute.Data?>() {
+    override fun createIntent(context: Context, input: String): Intent {
+        require(input.isNotEmpty()) { "Session identifier can't be empty" }
         return Intent(HealthDataServiceConstants.ACTION_REQUEST_ROUTE).apply {
             putExtra(HealthDataServiceConstants.EXTRA_SESSION_ID, input)
         }
     }
 
     @Suppress("DEPRECATION") // getParcelableExtra
-    override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute? {
+    override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute.Data? {
         val route =
             intent?.getParcelableExtra<androidx.health.platform.client.exerciseroute.ExerciseRoute>(
                 HealthDataServiceConstants.EXTRA_EXERCISE_ROUTE
@@ -51,6 +52,6 @@
             return null
         }
         Logger.debug(HealthConnectClient.HEALTH_CONNECT_CLIENT_TAG, "Returned a route.")
-        return toExerciseRoute(route)
+        return toExerciseRouteData(route)
     }
 }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
index 8582568..f1a435a 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
@@ -24,7 +24,7 @@
 import androidx.annotation.RestrictTo
 import androidx.health.connect.client.HealthConnectClient
 import androidx.health.connect.client.impl.platform.records.PlatformExerciseRoute
-import androidx.health.connect.client.impl.platform.records.toSdkExerciseRoute
+import androidx.health.connect.client.impl.platform.records.toSdkExerciseRouteData
 import androidx.health.connect.client.records.ExerciseRoute
 import androidx.health.platform.client.impl.logger.Logger
 
@@ -36,15 +36,15 @@
 @RequiresApi(34)
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 internal class RequestExerciseRouteUpsideDownCake :
-    ActivityResultContract<String?, ExerciseRoute?>() {
-    override fun createIntent(context: Context, input: String?): Intent {
-        require(!input.isNullOrEmpty()) { "Session identifier is required" }
+    ActivityResultContract<String, ExerciseRoute.Data?>() {
+    override fun createIntent(context: Context, input: String): Intent {
+        require(input.isNotEmpty()) { "Session identifier can't be empty" }
         return Intent(HealthConnectManager.ACTION_REQUEST_EXERCISE_ROUTE).apply {
             putExtra(HealthConnectManager.EXTRA_SESSION_ID, input)
         }
     }
 
-    override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute? {
+    override fun parseResult(resultCode: Int, intent: Intent?): ExerciseRoute.Data? {
         val route =
             intent?.getParcelableExtra(
                 HealthConnectManager.EXTRA_EXERCISE_ROUTE,
@@ -55,6 +55,6 @@
             return null
         }
         Logger.debug(HealthConnectClient.HEALTH_CONNECT_CLIENT_TAG, "Returned a route.")
-        return route.toSdkExerciseRoute()
+        return route.toSdkExerciseRouteData()
     }
 }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt
index 26f9961..6f36983 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseRoute.kt
@@ -16,43 +16,66 @@
 
 package androidx.health.connect.client.records
 
-import androidx.annotation.RestrictTo
 import androidx.health.connect.client.units.Length
 import java.time.Instant
 
-/**
- * Captures a route associated with an exercise session a user does.
- *
- * Contains a sequence of location points, with timestamps, which do not have to be in order.
- *
- * Location points contain a timestamp, longitude, latitude, and optionally altitude, horizontal and
- * vertical accuracy.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class ExerciseRoute(public val route: List<Location>) {
+/** Captures a route associated with an exercise session a user does. */
+abstract class ExerciseRoute internal constructor() {
 
-    init {
-        val sortedRoute: List<Location> = route.sortedWith { a, b -> a.time.compareTo(b.time) }
-        for (i in 0 until sortedRoute.lastIndex) {
-            require(sortedRoute[i].time.isBefore(sortedRoute[i + 1].time))
+    /**
+     * Class containing data of an exercise route.
+     *
+     * Contains a sequence of location points, with timestamps, which do not have to be in order.
+     *
+     * Location points contain a timestamp, longitude, latitude, and optionally altitude, horizontal
+     * and vertical accuracy.
+     */
+    class Data constructor(val route: List<Location>) : ExerciseRoute() {
+        init {
+            val sortedRoute: List<Location> = route.sortedBy { it.time }
+            for (i in 0 until sortedRoute.lastIndex) {
+                require(sortedRoute[i].time.isBefore(sortedRoute[i + 1].time))
+            }
+        }
+
+        internal fun isWithin(startTime: Instant, endTime: Instant): Boolean {
+            val minTime = route.minBy { it.time }.time
+            val maxTime = route.maxBy { it.time }.time
+            return !minTime.isBefore(startTime) && maxTime.isBefore(endTime)
+        }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Data) return false
+
+            return route == other.route
+        }
+
+        override fun hashCode(): Int {
+            return route.hashCode()
         }
     }
-    internal fun isWithin(startTime: Instant, endTime: Instant): Boolean {
-        // startTime is inclusive, endTime is exclusive
-        val sortedRoute: List<Location> = route.sortedWith { a, b -> a.time.compareTo(b.time) }
-        return !sortedRoute.first().time.isBefore(startTime) &&
-            sortedRoute.last().time.isBefore(endTime)
+
+    /** Class indicating that a permission hasn't been granted and a value couldn't be returned. */
+    class ConsentRequired : ExerciseRoute() {
+        override fun equals(other: Any?): Boolean {
+            return other is ConsentRequired
+        }
+
+        override fun hashCode(): Int {
+            return 0
+        }
     }
 
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ExerciseRoute) return false
+    /** Class indicating that there's no data to request permissions for. */
+    class NoData : ExerciseRoute() {
+        override fun equals(other: Any?): Boolean {
+            return other is NoData
+        }
 
-        return route == other.route
-    }
-
-    override fun hashCode(): Int {
-        return route.hashCode()
+        override fun hashCode(): Int {
+            return 0
+        }
     }
 
     /**
@@ -67,7 +90,7 @@
      * @param verticalAccuracy in [Length] unit. Optional field. Valid range: non-negative numbers.
      * @see ExerciseRoute
      */
-    public class Location(
+    class Location(
         val time: Instant,
         val latitude: Double,
         val longitude: Double,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
index 1709165..1a3747c 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/ExerciseSessionRecord.kt
@@ -34,42 +34,37 @@
  *
  * @sample androidx.health.connect.client.samples.ReadExerciseSessions
  */
-public class ExerciseSessionRecord
+class ExerciseSessionRecord
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-constructor(
+internal constructor(
     override val startTime: Instant,
     override val startZoneOffset: ZoneOffset?,
     override val endTime: Instant,
     override val endZoneOffset: ZoneOffset?,
     /** Type of exercise (e.g. walking, swimming). Required field. */
-    @property:ExerciseTypes public val exerciseType: Int,
+    @property:ExerciseTypes val exerciseType: Int,
     /** Title of the session. Optional field. */
-    public val title: String? = null,
+    val title: String? = null,
     /** Additional notes for the session. Optional field. */
-    public val notes: String? = null,
+    val notes: String? = null,
     override val metadata: Metadata = Metadata.EMPTY,
     /**
      * [ExerciseSegment]s of the session. Optional field. Time in segments should be within the
      * parent session, and should not overlap with each other.
      */
-    public val segments: List<ExerciseSegment> = emptyList(),
+    val segments: List<ExerciseSegment> = emptyList(),
     /**
      * [ExerciseLap]s of the session. Optional field. Time in laps should be within the parent
      * session, and should not overlap with each other.
      */
-    public val laps: List<ExerciseLap> = emptyList(),
-    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
-    /** [ExerciseRoute]s of the session. Optional field. */
-    public val route: ExerciseRoute? = null,
-    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
-    /**
-     * Indicates whether or not the underlying [ExerciseSessionRecord] has a route, even it's not
-     * present because lack of permission.
-     */
-    public val hasRoute: Boolean = false,
+    val laps: List<ExerciseLap> = emptyList(),
+
+    /** [ExerciseRoute] [ExerciseRoute] of the session. */
+    val exerciseRoute: ExerciseRoute = ExerciseRoute.NoData(),
 ) : IntervalRecord {
 
-    public constructor(
+    @JvmOverloads
+    constructor(
         startTime: Instant,
         startZoneOffset: ZoneOffset?,
         endTime: Instant,
@@ -83,6 +78,7 @@
         metadata: Metadata = Metadata.EMPTY,
         segments: List<ExerciseSegment> = emptyList(),
         laps: List<ExerciseLap> = emptyList(),
+        exerciseRouteData: ExerciseRoute.Data? = null,
     ) : this(
         startTime,
         startZoneOffset,
@@ -94,38 +90,7 @@
         metadata,
         segments,
         laps,
-        null
-    )
-
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public constructor(
-        startTime: Instant,
-        startZoneOffset: ZoneOffset?,
-        endTime: Instant,
-        endZoneOffset: ZoneOffset?,
-        /** Type of exercise (e.g. walking, swimming). Required field. */
-        exerciseType: Int,
-        /** Title of the session. Optional field. */
-        title: String? = null,
-        /** Additional notes for the session. Optional field. */
-        notes: String? = null,
-        metadata: Metadata = Metadata.EMPTY,
-        segments: List<ExerciseSegment> = emptyList(),
-        laps: List<ExerciseLap> = emptyList(),
-        route: ExerciseRoute? = null,
-    ) : this(
-        startTime,
-        startZoneOffset,
-        endTime,
-        endZoneOffset,
-        exerciseType,
-        title,
-        notes,
-        metadata,
-        segments,
-        laps,
-        route,
-        route != null
+        exerciseRouteData ?: ExerciseRoute.NoData()
     )
 
     init {
@@ -165,9 +130,8 @@
                 "laps can not be out of parent time range."
             }
         }
-        require(route == null || hasRoute) { "hasRoute must be true if the route is not null" }
-        if (route != null) {
-            require(route.isWithin(startTime, endTime)) {
+        if (exerciseRoute is ExerciseRoute.Data) {
+            require(exerciseRoute.isWithin(startTime, endTime)) {
                 "route can not be out of parent time range."
             }
         }
@@ -187,21 +151,20 @@
         if (metadata != other.metadata) return false
         if (segments != other.segments) return false
         if (laps != other.laps) return false
-        if (route != other.route) return false
-        if (hasRoute != other.hasRoute) return false
+        if (exerciseRoute != other.exerciseRoute) return false
 
         return true
     }
 
     override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + exerciseType.hashCode()
+        var result = exerciseType.hashCode()
         result = 31 * result + title.hashCode()
         result = 31 * result + notes.hashCode()
         result = 31 * result + (startZoneOffset?.hashCode() ?: 0)
         result = 31 * result + endTime.hashCode()
         result = 31 * result + (endZoneOffset?.hashCode() ?: 0)
         result = 31 * result + metadata.hashCode()
+        result = 31 * result + exerciseRoute.hashCode()
         return result
     }
 
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index 21b3465..ac86a98 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -125,7 +125,7 @@
             BasalBodyTemperatureRecord(
                 temperature = 1.celsius,
                 measurementLocation =
-                BodyTemperatureMeasurementLocation.MEASUREMENT_LOCATION_ARMPIT,
+                    BodyTemperatureMeasurementLocation.MEASUREMENT_LOCATION_ARMPIT,
                 time = START_TIME,
                 zoneOffset = END_ZONE_OFFSET,
                 metadata = TEST_METADATA
@@ -259,16 +259,16 @@
                 endTime = END_TIME,
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
-                listOf(
-                    CyclingPedalingCadenceRecord.Sample(
-                        time = START_TIME,
-                        revolutionsPerMinute = 1.0,
+                    listOf(
+                        CyclingPedalingCadenceRecord.Sample(
+                            time = START_TIME,
+                            revolutionsPerMinute = 1.0,
+                        ),
+                        CyclingPedalingCadenceRecord.Sample(
+                            time = START_TIME,
+                            revolutionsPerMinute = 2.0,
+                        ),
                     ),
-                    CyclingPedalingCadenceRecord.Sample(
-                        time = START_TIME,
-                        revolutionsPerMinute = 2.0,
-                    ),
-                ),
                 metadata = TEST_METADATA,
             )
 
@@ -285,20 +285,20 @@
                 endTime = END_TIME,
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
-                listOf(
-                    HeartRateRecord.Sample(
-                        time = START_TIME,
-                        beatsPerMinute = 100L,
+                    listOf(
+                        HeartRateRecord.Sample(
+                            time = START_TIME,
+                            beatsPerMinute = 100L,
+                        ),
+                        HeartRateRecord.Sample(
+                            time = START_TIME,
+                            beatsPerMinute = 110L,
+                        ),
+                        HeartRateRecord.Sample(
+                            time = START_TIME,
+                            beatsPerMinute = 120L,
+                        ),
                     ),
-                    HeartRateRecord.Sample(
-                        time = START_TIME,
-                        beatsPerMinute = 110L,
-                    ),
-                    HeartRateRecord.Sample(
-                        time = START_TIME,
-                        beatsPerMinute = 120L,
-                    ),
-                ),
                 metadata = TEST_METADATA,
             )
 
@@ -448,16 +448,16 @@
                 endTime = END_TIME,
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
-                listOf(
-                    PowerRecord.Sample(
-                        time = START_TIME,
-                        power = 1.watts,
+                    listOf(
+                        PowerRecord.Sample(
+                            time = START_TIME,
+                            power = 1.watts,
+                        ),
+                        PowerRecord.Sample(
+                            time = START_TIME,
+                            power = 2.watts,
+                        ),
                     ),
-                    PowerRecord.Sample(
-                        time = START_TIME,
-                        power = 2.watts,
-                    ),
-                ),
                 metadata = TEST_METADATA,
             )
 
@@ -516,16 +516,16 @@
                 endTime = END_TIME,
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
-                listOf(
-                    SpeedRecord.Sample(
-                        time = START_TIME,
-                        speed = 1.metersPerSecond,
+                    listOf(
+                        SpeedRecord.Sample(
+                            time = START_TIME,
+                            speed = 1.metersPerSecond,
+                        ),
+                        SpeedRecord.Sample(
+                            time = START_TIME,
+                            speed = 2.metersPerSecond,
+                        ),
                     ),
-                    SpeedRecord.Sample(
-                        time = START_TIME,
-                        speed = 2.metersPerSecond,
-                    ),
-                ),
                 metadata = TEST_METADATA,
             )
 
@@ -542,16 +542,16 @@
                 endTime = END_TIME,
                 endZoneOffset = END_ZONE_OFFSET,
                 samples =
-                listOf(
-                    StepsCadenceRecord.Sample(
-                        time = START_TIME,
-                        rate = 1.0,
+                    listOf(
+                        StepsCadenceRecord.Sample(
+                            time = START_TIME,
+                            rate = 1.0,
+                        ),
+                        StepsCadenceRecord.Sample(
+                            time = START_TIME,
+                            rate = 2.0,
+                        ),
                     ),
-                    StepsCadenceRecord.Sample(
-                        time = START_TIME,
-                        rate = 2.0,
-                    ),
-                ),
                 metadata = TEST_METADATA,
             )
 
@@ -616,48 +616,51 @@
                 endTime = END_TIME,
                 endZoneOffset = END_ZONE_OFFSET,
                 metadata = TEST_METADATA,
-                segments = listOf(
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1235L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_CRUNCH,
-                        repetitions = 10
-                    ),
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1235L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_CRUNCH,
-                    )
-                ),
-                laps = listOf(
-                    ExerciseLap(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1235L),
-                        length = 1.meters
-                    ),
-                    ExerciseLap(
-                        startTime = Instant.ofEpochMilli(1235L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                    )
-                ),
-                route = ExerciseRoute(
-                    route = listOf(
-                        ExerciseRoute.Location(
-                            time = Instant.ofEpochMilli(1234L),
-                            latitude = 34.5,
-                            longitude = -34.5,
-                            horizontalAccuracy = Length.meters(0.4),
-                            verticalAccuracy = Length.meters(1.3),
-                            altitude = Length.meters(23.4)
+                segments =
+                    listOf(
+                        ExerciseSegment(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1235L),
+                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_CRUNCH,
+                            repetitions = 10
                         ),
-                        ExerciseRoute.Location(
-                            time = Instant.ofEpochMilli(1235L),
-                            latitude = 34.5,
-                            longitude = -34.5,
+                        ExerciseSegment(
+                            startTime = Instant.ofEpochMilli(1235L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_CRUNCH,
+                        )
+                    ),
+                laps =
+                    listOf(
+                        ExerciseLap(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1235L),
+                            length = 1.meters
                         ),
-                    )
-                ),
-                hasRoute = true,
+                        ExerciseLap(
+                            startTime = Instant.ofEpochMilli(1235L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                        )
+                    ),
+                exerciseRoute =
+                    ExerciseRoute.Data(
+                        route =
+                            listOf(
+                                ExerciseRoute.Location(
+                                    time = Instant.ofEpochMilli(1234L),
+                                    latitude = 34.5,
+                                    longitude = -34.5,
+                                    horizontalAccuracy = Length.meters(0.4),
+                                    verticalAccuracy = Length.meters(1.3),
+                                    altitude = Length.meters(23.4)
+                                ),
+                                ExerciseRoute.Location(
+                                    time = Instant.ofEpochMilli(1235L),
+                                    latitude = 34.5,
+                                    longitude = -34.5,
+                                ),
+                            )
+                    ),
             )
 
         checkProtoAndRecordTypeNameMatch(data)
@@ -673,6 +676,7 @@
                 startZoneOffset = null,
                 endTime = END_TIME,
                 endZoneOffset = null,
+                exerciseRouteData = null,
             )
 
         checkProtoAndRecordTypeNameMatch(data)
@@ -813,13 +817,14 @@
                 endTime = END_TIME,
                 endZoneOffset = END_ZONE_OFFSET,
                 metadata = TEST_METADATA,
-                stages = listOf(
-                    SleepSessionRecord.Stage(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                        stage = SleepSessionRecord.STAGE_TYPE_DEEP,
+                stages =
+                    listOf(
+                        SleepSessionRecord.Stage(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                            stage = SleepSessionRecord.STAGE_TYPE_DEEP,
+                        )
                     )
-                )
             )
 
         checkProtoAndRecordTypeNameMatch(data)
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
index b650485..ffa47f9 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
@@ -73,7 +73,7 @@
             )
         )
         val result = requestRouteContract.parseResult(0, intent)
-        assertThat(result).isEqualTo(ExerciseRoute(listOf()))
+        assertThat(result).isEqualTo(ExerciseRoute.Data(listOf()))
     }
 
     @Test
@@ -114,7 +114,7 @@
         val result = requestRouteContract.parseResult(0, intent)
         assertThat(result)
             .isEqualTo(
-                ExerciseRoute(
+                ExerciseRoute.Data(
                     listOf(
                         ExerciseRoute.Location(
                             time = Instant.ofEpochMilli(1234L),
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt
index d31308f..46afdf5 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseRouteTest.kt
@@ -27,15 +27,15 @@
     @Test
     fun validLocation_equals() {
         assertThat(
-            ExerciseRoute.Location(
-                time = Instant.ofEpochMilli(1234L),
-                latitude = 34.5,
-                longitude = -34.5,
-                horizontalAccuracy = Length.meters(0.4),
-                verticalAccuracy = Length.meters(1.3),
-                altitude = Length.meters(23.4)
+                ExerciseRoute.Location(
+                    time = Instant.ofEpochMilli(1234L),
+                    latitude = 34.5,
+                    longitude = -34.5,
+                    horizontalAccuracy = Length.meters(0.4),
+                    verticalAccuracy = Length.meters(1.3),
+                    altitude = Length.meters(23.4)
+                )
             )
-        )
             .isEqualTo(
                 ExerciseRoute.Location(
                     time = Instant.ofEpochMilli(1234L),
@@ -74,7 +74,7 @@
 
     @Test
     fun emptyRoute() {
-        assertThat(ExerciseRoute(listOf())).isEqualTo(ExerciseRoute(listOf()))
+        assertThat(ExerciseRoute.Data(listOf())).isEqualTo(ExerciseRoute.Data(listOf()))
     }
 
     @Test
@@ -94,23 +94,26 @@
                 latitude = 34.8,
                 longitude = -34.8,
             )
-        assertThat(ExerciseRoute(listOf(location1, location2)))
-            .isEqualTo(ExerciseRoute(listOf(location1, location2)))
+        assertThat(ExerciseRoute.Data(listOf(location1, location2)))
+            .isEqualTo(ExerciseRoute.Data(listOf(location1, location2)))
     }
 
     @Test
     fun locationTimeOverlap_throws() {
-        val location1 = ExerciseRoute.Location(
-            time = Instant.ofEpochMilli(1234L),
-            latitude = 34.5,
-            longitude = -34.5,
-        )
+        val location1 =
+            ExerciseRoute.Location(
+                time = Instant.ofEpochMilli(1234L),
+                latitude = 34.5,
+                longitude = -34.5,
+            )
         val location2 =
             ExerciseRoute.Location(
                 time = Instant.ofEpochMilli(1234L),
                 latitude = 34.8,
                 longitude = -34.8,
             )
-        assertFailsWith<IllegalArgumentException> { ExerciseRoute(listOf(location1, location2)) }
+        assertFailsWith<IllegalArgumentException> {
+            ExerciseRoute.Data(listOf(location1, location2))
+        }
     }
 }
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt
index 57efb01..6adb8bb 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/records/ExerciseSessionRecordTest.kt
@@ -37,43 +37,46 @@
     @Test
     fun validRecord_equals() {
         assertThat(
-            ExerciseSessionRecord(
-                startTime = Instant.ofEpochMilli(1234L),
-                startZoneOffset = null,
-                endTime = Instant.ofEpochMilli(1236L),
-                endZoneOffset = null,
-                exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
-                title = "title",
-                notes = "notes",
-                segments = listOf(
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1235L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
-                    )
-                ),
-                laps = listOf(
-                    ExerciseLap(
-                        startTime = Instant.ofEpochMilli(1235L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                        length = 10.meters,
-                    )
-                ),
-                route = ExerciseRoute(
-                    route = listOf(
-                        ExerciseRoute.Location(
-                            time = Instant.ofEpochMilli(1234L),
-                            latitude = 34.5,
-                            longitude = -34.5,
-                            horizontalAccuracy = 0.4.meters,
-                            verticalAccuracy = 1.3.meters,
-                            altitude = 23.4.meters
-                        )
-                    )
-                ),
-                hasRoute = true,
+                ExerciseSessionRecord(
+                    startTime = Instant.ofEpochMilli(1234L),
+                    startZoneOffset = null,
+                    endTime = Instant.ofEpochMilli(1236L),
+                    endZoneOffset = null,
+                    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
+                    title = "title",
+                    notes = "notes",
+                    segments =
+                        listOf(
+                            ExerciseSegment(
+                                startTime = Instant.ofEpochMilli(1234L),
+                                endTime = Instant.ofEpochMilli(1235L),
+                                segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
+                            )
+                        ),
+                    laps =
+                        listOf(
+                            ExerciseLap(
+                                startTime = Instant.ofEpochMilli(1235L),
+                                endTime = Instant.ofEpochMilli(1236L),
+                                length = 10.meters,
+                            )
+                        ),
+                    exerciseRoute =
+                        ExerciseRoute.Data(
+                            route =
+                                listOf(
+                                    ExerciseRoute.Location(
+                                        time = Instant.ofEpochMilli(1234L),
+                                        latitude = 34.5,
+                                        longitude = -34.5,
+                                        horizontalAccuracy = 0.4.meters,
+                                        verticalAccuracy = 1.3.meters,
+                                        altitude = 23.4.meters
+                                    )
+                                )
+                        ),
+                )
             )
-        )
             .isEqualTo(
                 ExerciseSessionRecord(
                     startTime = Instant.ofEpochMilli(1234L),
@@ -83,33 +86,36 @@
                     exerciseType = EXERCISE_TYPE_BIKING,
                     title = "title",
                     notes = "notes",
-                    segments = listOf(
-                        ExerciseSegment(
-                            startTime = Instant.ofEpochMilli(1234L),
-                            endTime = Instant.ofEpochMilli(1235L),
-                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
-                        )
-                    ),
-                    laps = listOf(
-                        ExerciseLap(
-                            startTime = Instant.ofEpochMilli(1235L),
-                            endTime = Instant.ofEpochMilli(1236L),
-                            length = 10.meters,
-                        )
-                    ),
-                    route = ExerciseRoute(
-                        route = listOf(
-                            ExerciseRoute.Location(
-                                time = Instant.ofEpochMilli(1234L),
-                                latitude = 34.5,
-                                longitude = -34.5,
-                                horizontalAccuracy = 0.4.meters,
-                                verticalAccuracy = 1.3.meters,
-                                altitude = 23.4.meters
+                    segments =
+                        listOf(
+                            ExerciseSegment(
+                                startTime = Instant.ofEpochMilli(1234L),
+                                endTime = Instant.ofEpochMilli(1235L),
+                                segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
                             )
-                        )
-                    ),
-                    hasRoute = true,
+                        ),
+                    laps =
+                        listOf(
+                            ExerciseLap(
+                                startTime = Instant.ofEpochMilli(1235L),
+                                endTime = Instant.ofEpochMilli(1236L),
+                                length = 10.meters,
+                            )
+                        ),
+                    exerciseRoute =
+                        ExerciseRoute.Data(
+                            route =
+                                listOf(
+                                    ExerciseRoute.Location(
+                                        time = Instant.ofEpochMilli(1234L),
+                                        latitude = 34.5,
+                                        longitude = -34.5,
+                                        horizontalAccuracy = 0.4.meters,
+                                        verticalAccuracy = 1.3.meters,
+                                        altitude = 23.4.meters
+                                    )
+                                )
+                        ),
                 )
             )
     }
@@ -125,6 +131,7 @@
                 exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_EXERCISE_CLASS,
                 title = "title",
                 notes = "notes",
+                exerciseRouteData = null,
             )
         }
     }
@@ -166,13 +173,15 @@
                 endTime = Instant.ofEpochMilli(1235L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                segments = listOf(
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1233L),
-                        endTime = Instant.ofEpochMilli(1235L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
-                    )
-                )
+                segments =
+                    listOf(
+                        ExerciseSegment(
+                            startTime = Instant.ofEpochMilli(1233L),
+                            endTime = Instant.ofEpochMilli(1235L),
+                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
+                        )
+                    ),
+                exerciseRouteData = null,
             )
         }
 
@@ -183,13 +192,15 @@
                 endTime = Instant.ofEpochMilli(1235L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                segments = listOf(
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
-                    )
-                )
+                segments =
+                    listOf(
+                        ExerciseSegment(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
+                        )
+                    ),
+                exerciseRouteData = null,
             )
         }
     }
@@ -203,12 +214,14 @@
                 endTime = Instant.ofEpochMilli(1235L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                laps = listOf(
-                    ExerciseLap(
-                        startTime = Instant.ofEpochMilli(1233L),
-                        endTime = Instant.ofEpochMilli(1235L),
-                    )
-                )
+                laps =
+                    listOf(
+                        ExerciseLap(
+                            startTime = Instant.ofEpochMilli(1233L),
+                            endTime = Instant.ofEpochMilli(1235L),
+                        )
+                    ),
+                exerciseRouteData = null,
             )
         }
 
@@ -219,12 +232,14 @@
                 endTime = Instant.ofEpochMilli(1235L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                laps = listOf(
-                    ExerciseLap(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                    )
-                )
+                laps =
+                    listOf(
+                        ExerciseLap(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                        )
+                    ),
+                exerciseRouteData = null,
             )
         }
     }
@@ -238,15 +253,17 @@
                 endTime = Instant.ofEpochMilli(1235L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                route = ExerciseRoute(
-                    route = listOf(
-                        ExerciseRoute.Location(
-                            time = Instant.ofEpochMilli(1233L),
-                            latitude = 34.5,
-                            longitude = -34.5
-                        )
+                exerciseRoute =
+                    ExerciseRoute.Data(
+                        route =
+                            listOf(
+                                ExerciseRoute.Location(
+                                    time = Instant.ofEpochMilli(1233L),
+                                    latitude = 34.5,
+                                    longitude = -34.5
+                                )
+                            )
                     )
-                )
             )
         }
 
@@ -257,15 +274,17 @@
                 endTime = Instant.ofEpochMilli(1235L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                route = ExerciseRoute(
-                    route = listOf(
-                        ExerciseRoute.Location(
-                            time = Instant.ofEpochMilli(1235L),
-                            latitude = 34.5,
-                            longitude = -34.5
-                        )
+                exerciseRoute =
+                    ExerciseRoute.Data(
+                        route =
+                            listOf(
+                                ExerciseRoute.Location(
+                                    time = Instant.ofEpochMilli(1235L),
+                                    latitude = 34.5,
+                                    longitude = -34.5
+                                )
+                            )
                     )
-                )
             )
         }
     }
@@ -279,18 +298,20 @@
                 endTime = Instant.ofEpochMilli(1236L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                segments = listOf(
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
+                segments =
+                    listOf(
+                        ExerciseSegment(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
+                        ),
+                        ExerciseSegment(
+                            startTime = Instant.ofEpochMilli(1235L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
+                        ),
                     ),
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1235L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_BIKING
-                    ),
-                )
+                exerciseRouteData = null,
             )
         }
     }
@@ -304,16 +325,18 @@
                 endTime = Instant.ofEpochMilli(1236L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                laps = listOf(
-                    ExerciseLap(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1236L),
+                laps =
+                    listOf(
+                        ExerciseLap(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                        ),
+                        ExerciseLap(
+                            startTime = Instant.ofEpochMilli(1235L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                        ),
                     ),
-                    ExerciseLap(
-                        startTime = Instant.ofEpochMilli(1235L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                    ),
-                )
+                exerciseRouteData = null,
             )
         }
     }
@@ -327,36 +350,15 @@
                 endTime = Instant.ofEpochMilli(1236L),
                 endZoneOffset = null,
                 exerciseType = EXERCISE_TYPE_BIKING,
-                segments = listOf(
-                    ExerciseSegment(
-                        startTime = Instant.ofEpochMilli(1234L),
-                        endTime = Instant.ofEpochMilli(1236L),
-                        segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_PLANK
+                segments =
+                    listOf(
+                        ExerciseSegment(
+                            startTime = Instant.ofEpochMilli(1234L),
+                            endTime = Instant.ofEpochMilli(1236L),
+                            segmentType = ExerciseSegment.EXERCISE_SEGMENT_TYPE_PLANK
+                        ),
                     ),
-                )
-            )
-        }
-    }
-
-    @Test
-    fun exerciseRouteSet_hasRouteSetToFalse_throws() {
-        assertFailsWith<IllegalArgumentException> {
-            ExerciseSessionRecord(
-                startTime = Instant.ofEpochMilli(1234L),
-                startZoneOffset = null,
-                endTime = Instant.ofEpochMilli(1236L),
-                endZoneOffset = null,
-                exerciseType = EXERCISE_TYPE_BIKING,
-                route = ExerciseRoute(
-                    route = listOf(
-                        ExerciseRoute.Location(
-                            time = Instant.ofEpochMilli(1235L),
-                            latitude = 34.5,
-                            longitude = -34.5
-                        )
-                    )
-                ),
-                hasRoute = false,
+                exerciseRouteData = null,
             )
         }
     }
@@ -364,31 +366,49 @@
     @Test
     fun secondConstructor_hasRouteSetCorrectly() {
         assertThat(
-            ExerciseSessionRecord(
-                startTime = Instant.ofEpochMilli(1234L),
-                startZoneOffset = null,
-                endTime = Instant.ofEpochMilli(1236L),
-                endZoneOffset = null,
-                exerciseType = EXERCISE_TYPE_BIKING,
-                route = ExerciseRoute(
-                    route = listOf(
-                        ExerciseRoute.Location(
-                            time = Instant.ofEpochMilli(1235L),
-                            latitude = 34.5,
-                            longitude = -34.5
-                        )
+                ExerciseSessionRecord(
+                        startTime = Instant.ofEpochMilli(1234L),
+                        startZoneOffset = null,
+                        endTime = Instant.ofEpochMilli(1236L),
+                        endZoneOffset = null,
+                        exerciseType = EXERCISE_TYPE_BIKING,
+                        exerciseRoute =
+                            ExerciseRoute.Data(
+                                route =
+                                    listOf(
+                                        ExerciseRoute.Location(
+                                            time = Instant.ofEpochMilli(1235L),
+                                            latitude = 34.5,
+                                            longitude = -34.5
+                                        )
+                                    )
+                            ),
                     )
-                ),
-            ).hasRoute
-        ).isTrue()
+                    .exerciseRoute
+            )
+            .isEqualTo(
+                ExerciseRoute.Data(
+                    route =
+                        listOf(
+                            ExerciseRoute.Location(
+                                time = Instant.ofEpochMilli(1235L),
+                                latitude = 34.5,
+                                longitude = -34.5
+                            )
+                        )
+                )
+            )
         assertThat(
-            ExerciseSessionRecord(
-                startTime = Instant.ofEpochMilli(1234L),
-                startZoneOffset = null,
-                endTime = Instant.ofEpochMilli(1236L),
-                endZoneOffset = null,
-                exerciseType = EXERCISE_TYPE_BIKING,
-            ).hasRoute
-        ).isFalse()
+                ExerciseSessionRecord(
+                        startTime = Instant.ofEpochMilli(1234L),
+                        startZoneOffset = null,
+                        endTime = Instant.ofEpochMilli(1236L),
+                        endZoneOffset = null,
+                        exerciseType = EXERCISE_TYPE_BIKING,
+                        exerciseRouteData = null
+                    )
+                    .exerciseRoute
+            )
+            .isEqualTo(ExerciseRoute.NoData())
     }
 }