Merge "Add request exercise route contract and expose exercise route." into androidx-main
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())
}
}