Merge "Rename publish = Publish.SNAPSHOT_AND_RELEASE to type = LibraryType.PublishedLibrary" into androidx-main
diff --git a/camera/camera-viewfinder-compose/build.gradle b/camera/camera-viewfinder-compose/build.gradle
index 90af1cd..60ed2b7 100644
--- a/camera/camera-viewfinder-compose/build.gradle
+++ b/camera/camera-viewfinder-compose/build.gradle
@@ -54,6 +54,7 @@
     name = "androidx.camera:camera-viewfinder-compose"
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2023"
+    mavenVersion = LibraryVersions.CAMERA_VIEWFINDER_COMPOSE
     description = "Composable ViewFinder implementation for CameraX"
     metalavaK2UastEnabled = true
 }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/SubcompositionReusableContentHost.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/SubcompositionReusableContentHost.kt
index 91aa166..bdb1329 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/SubcompositionReusableContentHost.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/SubcompositionReusableContentHost.kt
@@ -36,7 +36,10 @@
         } else {
             emptyList()
         }
-        layout(0, 0) {
+        layout(
+            placeable.maxOfOrNull { it.width } ?: 0,
+            placeable.maxOfOrNull { it.height } ?: 0,
+        ) {
             placeable.forEach { it.place(0, 0) }
         }
     }
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt
index 868bf43..3b0cfc8 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/draw/DrawingPrebuiltGraphicsLayerTest.kt
@@ -28,6 +28,7 @@
 import androidx.compose.testutils.assertPixels
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.SubcompositionReusableContentHost
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.GraphicsContext
 import androidx.compose.ui.graphics.compositeOver
@@ -48,6 +49,7 @@
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
+import org.junit.Assume
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -258,11 +260,10 @@
             .assertPixels(expectedSize) { Color.Red }
     }
 
-    // TODO remove sdk suppress when we start using new layers as Modifier.graphicsLayer() on
-    //  older versions.
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @Test
     fun keepDrawingNestedLayers_graphicsLayerModifier() {
+        // TODO remove this after we start using new layers on P
+        Assume.assumeTrue(Build.VERSION.SDK_INT != Build.VERSION_CODES.P)
         rule.setContent {
             if (!drawPrebuiltLayer) {
                 Box(Modifier.drawIntoLayer()) {
@@ -293,6 +294,42 @@
     }
 
     @Test
+    fun keepDrawingNestedLayers_deactivatedGraphicsLayerModifierScheduledForInvalidation() {
+        val counter = mutableStateOf(0)
+        // TODO remove this after we start using new layers on P
+        Assume.assumeTrue(Build.VERSION.SDK_INT != Build.VERSION_CODES.P)
+        rule.setContent {
+            SubcompositionReusableContentHost(active = !drawPrebuiltLayer) {
+                Box(
+                    Modifier
+                        .drawIntoLayer()
+                        .graphicsLayer()
+                ) {
+                    Canvas(Modifier.size(sizeDp)) {
+                        counter.value
+                        drawRect(Color.Red)
+                    }
+                }
+            }
+            if (drawPrebuiltLayer) {
+                LayerDrawingBox()
+            }
+        }
+
+        rule.runOnIdle {
+            drawPrebuiltLayer = true
+
+            // changing the counter to trigger the layer invalidation. the invalidation should
+            // be ignored in the end as we will release the layer before it will be drawn
+            counter.value++
+        }
+
+        rule.onNodeWithTag(LayerDrawingBoxTag)
+            .captureToImage()
+            .assertPixels(expectedSize) { Color.Red }
+    }
+
+    @Test
     fun keepDrawingLayerFromANodeScheduledForInvalidation() {
         val counter = mutableStateOf(0)
         rule.setContent {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
index 779c2d1..3460d1a 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/modifier/ModifierNodeReuseAndDeactivationTest.kt
@@ -28,6 +28,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.SubcompositionReusableContentHost
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.layout.Layout
@@ -441,7 +442,7 @@
         }
 
         rule.setContent {
-            ReusableContentHost(active) {
+            SubcompositionReusableContentHost(active) {
                 ReusableContent(0) {
                     Layout(
                         modifier = LayerElement(layerBlock),
@@ -618,7 +619,7 @@
         }
 
         rule.setContent {
-            ReusableContentHost(active) {
+            SubcompositionReusableContentHost(active) {
                 ReusableContent(0) {
                     Layout(
                         modifier = DrawElement(drawBlock),
@@ -665,7 +666,7 @@
         }
 
         rule.setContent {
-            ReusableContentHost(active) {
+            SubcompositionReusableContentHost(active) {
                 ReusableContent(0) {
                     Layout(
                         modifier = OldDrawModifier(drawBlock),
@@ -872,7 +873,9 @@
             ReusableContentHost(active) {
                 Layout(content = {
                     Layout(
-                        modifier = Modifier.size(50.dp).testTag("child"),
+                        modifier = Modifier
+                            .size(50.dp)
+                            .testTag("child"),
                         measurePolicy = MeasurePolicy
                     )
                 }) { measurables, constraints ->
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index d4a106f..c6e5c7a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -1474,14 +1474,9 @@
      * Returns `true` if it was recycled or `false` if it will be discarded.
      */
     internal fun recycle(layer: OwnedLayer): Boolean {
-        // L throws during RenderThread when reusing the Views. The stack trace
-        // wasn't easy to decode, so this work-around keeps up to 10 Views active
-        // only for L. On other versions, it uses the WeakHashMap to retain as many
-        // as are convenient.
         val cacheValue = viewLayersContainer == null ||
             ViewLayer.shouldUseDispatchDraw ||
-            SDK_INT >= M ||
-            layerCache.size < MaximumLayerCacheSize
+            SDK_INT >= M // L throws during RenderThread when reusing the Views.
         if (cacheValue) {
             layerCache.push(layer)
         }
@@ -2346,7 +2341,6 @@
     override fun shouldDelayChildPressedState(): Boolean = false
 
     companion object {
-        private const val MaximumLayerCacheSize = 10
         private var systemPropertiesClass: Class<*>? = null
         private var getBooleanMethod: Method? = null
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
index 6e820de..824f2ce 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt
@@ -340,11 +340,6 @@
         drawBlock = null
         invalidateParentLayer = null
 
-        // L throws during RenderThread when reusing the Views. The stack trace
-        // wasn't easy to decode, so this work-around keeps up to 10 Views active
-        // only for L. On other versions, it uses the WeakHashMap to retain as many
-        // as are convenient.
-
         val recycle = ownerView.recycle(this@ViewLayer)
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M || shouldUseDispatchDraw || !recycle) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 1deef93..67bcc2f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -1393,7 +1393,6 @@
             // we don't need to reset state as it was done when deactivated
         } else {
             resetModifierState()
-            resetExplicitLayers()
         }
         // resetModifierState detaches all nodes, so we need to re-attach them upon reuse.
         semanticsId = generateSemanticsId()
@@ -1411,12 +1410,12 @@
         if (isAttached) {
             invalidateSemantics()
         }
-        resetExplicitLayers()
+        releaseLayers()
     }
 
-    private fun resetExplicitLayers() {
+    private fun releaseLayers() {
         forEachCoordinatorIncludingInner {
-            it.releaseExplicitLayer()
+            it.releaseLayer()
         }
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index c3aa5ec..fb567761d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -371,7 +371,9 @@
                 invalidateParentLayer()
             }
         } else {
-            releaseExplicitLayer()
+            if (this.explicitLayer != null) {
+                this.explicitLayer = null
+            }
             updateLayerBlock(layerBlock)
         }
         if (this.position != position) {
@@ -393,9 +395,11 @@
         }
     }
 
-    fun releaseExplicitLayer() {
-        if (explicitLayer != null) {
-            explicitLayer = null
+    fun releaseLayer() {
+        if (layer != null) {
+            if (explicitLayer != null) {
+                explicitLayer = null
+            }
             updateLayerBlock(null)
         }
     }
@@ -1058,7 +1062,6 @@
      * released or when the [NodeCoordinator] is released (will not be used anymore).
      */
     fun onRelease() {
-        releaseExplicitLayer()
         released = true
         // It is important to call invalidateParentLayer() here, even though updateLayerBlock() may
         // call it. The reason is because we end up calling this from the bottom up, which means
@@ -1067,9 +1070,7 @@
         // no layers invalidated. By always calling this, we ensure that after all nodes are
         // removed at least one layer is invalidated.
         invalidateParentLayer()
-        if (layer != null) {
-            updateLayerBlock(null)
-        }
+        releaseLayer()
     }
 
     /**
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
index f25c14e..926ed7a 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
@@ -64,8 +64,7 @@
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 @MediumTest
 @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-// Comment the SDK suppress to run on emulators lower than U.
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
 class HealthConnectClientUpsideDownImplTest {
 
     private companion object {
@@ -88,7 +87,6 @@
             .filter { it.startsWith(PERMISSION_PREFIX) }
             .toTypedArray()
 
-    // Grant every permission as deletion by id checks for every permission
     @get:Rule
     val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(*allHealthPermissions)
 
@@ -167,10 +165,10 @@
         )
 
         assertThat(
-                healthConnectClient
-                    .readRecords(ReadRecordsRequest(StepsRecord::class, TimeRangeFilter.none()))
-                    .records
-            )
+            healthConnectClient
+                .readRecords(ReadRecordsRequest(StepsRecord::class, TimeRangeFilter.none()))
+                .records
+        )
             .containsExactly(initialRecords[0])
     }
 
@@ -208,10 +206,10 @@
         )
 
         assertThat(
-                healthConnectClient
-                    .readRecords(ReadRecordsRequest(StepsRecord::class, TimeRangeFilter.none()))
-                    .records
-            )
+            healthConnectClient
+                .readRecords(ReadRecordsRequest(StepsRecord::class, TimeRangeFilter.none()))
+                .records
+        )
             .containsExactly(initialRecords[1])
     }
 
@@ -336,10 +334,10 @@
                     endTime = START_TIME + 30.seconds,
                     endZoneOffset = ZoneOffset.UTC,
                     samples =
-                        listOf(
-                            HeartRateRecord.Sample(START_TIME, 57L),
-                            HeartRateRecord.Sample(START_TIME + 15.seconds, 120L)
-                        )
+                    listOf(
+                        HeartRateRecord.Sample(START_TIME, 57L),
+                        HeartRateRecord.Sample(START_TIME + 15.seconds, 120L)
+                    )
                 ),
                 HeartRateRecord(
                     startTime = START_TIME + 1.minutes,
@@ -347,10 +345,10 @@
                     endTime = START_TIME + 1.minutes + 30.seconds,
                     endZoneOffset = ZoneOffset.UTC,
                     samples =
-                        listOf(
-                            HeartRateRecord.Sample(START_TIME + 1.minutes, 47L),
-                            HeartRateRecord.Sample(START_TIME + 1.minutes + 15.seconds, 48L)
-                        )
+                    listOf(
+                        HeartRateRecord.Sample(START_TIME + 1.minutes, 47L),
+                        HeartRateRecord.Sample(START_TIME + 1.minutes + 15.seconds, 48L)
+                    )
                 ),
                 NutritionRecord(
                     startTime = START_TIME,
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensionsTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensionsTest.kt
new file mode 100644
index 0000000..75435a1
--- /dev/null
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensionsTest.kt
@@ -0,0 +1,548 @@
+/*
+ * Copyright 2024 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.impl.platform.aggregate
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.impl.HealthConnectClientUpsideDownImpl
+import androidx.health.connect.client.permission.HealthPermission
+import androidx.health.connect.client.records.NutritionRecord
+import androidx.health.connect.client.records.StepsRecord
+import androidx.health.connect.client.records.metadata.DataOrigin
+import androidx.health.connect.client.time.TimeRangeFilter
+import androidx.health.connect.client.units.Mass
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.GrantPermissionRule
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.ZoneOffset
+import kotlinx.coroutines.flow.fold
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+@MediumTest
+@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class HealthConnectClientAggregationExtensionsTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val healthConnectClient: HealthConnectClient =
+        HealthConnectClientUpsideDownImpl(context)
+
+    private companion object {
+        private val START_TIME =
+            LocalDate.now().minusDays(5).atStartOfDay().toInstant(ZoneOffset.UTC)
+    }
+
+    @get:Rule
+    val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+        HealthPermission.getWritePermission(NutritionRecord::class),
+        HealthPermission.getReadPermission(NutritionRecord::class),
+        HealthPermission.getWritePermission(StepsRecord::class),
+        HealthPermission.getReadPermission(StepsRecord::class)
+    )
+
+    @After
+    fun tearDown() = runTest {
+        healthConnectClient.deleteRecords(NutritionRecord::class, TimeRangeFilter.none())
+        healthConnectClient.deleteRecords(StepsRecord::class, TimeRangeFilter.none())
+    }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_noFilters() = runTest {
+        healthConnectClient.insertRecords(
+            listOf(
+                NutritionRecord(
+                    startTime = START_TIME,
+                    endTime = START_TIME + 1.minutes,
+                    transFat = Mass.grams(0.3),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 2.minutes,
+                    endTime = START_TIME + 3.minutes,
+                    transFat = null,
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 4.minutes,
+                    endTime = START_TIME + 5.minutes,
+                    transFat = Mass.grams(0.4),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 6.minutes,
+                    endTime = START_TIME + 7.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 8.minutes,
+                    endTime = START_TIME + 9.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                )
+            )
+        )
+
+        val aggregationResult =
+            healthConnectClient.aggregateNutritionTransFatTotal(TimeRangeFilter.none(), emptySet())
+
+        assertThat(aggregationResult[NutritionRecord.TRANS_FAT_TOTAL]).isEqualTo(Mass.grams(1.7))
+        assertThat(aggregationResult.dataOrigins).containsExactly(DataOrigin(context.packageName))
+    }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_instantTimeRangeFilter() = runTest {
+        healthConnectClient.insertRecords(
+            listOf(
+                NutritionRecord(
+                    startTime = START_TIME,
+                    endTime = START_TIME + 1.minutes,
+                    transFat = Mass.grams(0.3),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 2.minutes,
+                    endTime = START_TIME + 3.minutes,
+                    transFat = null,
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 4.minutes,
+                    endTime = START_TIME + 5.minutes,
+                    transFat = Mass.grams(0.4),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 6.minutes,
+                    endTime = START_TIME + 7.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 8.minutes,
+                    endTime = START_TIME + 9.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                )
+            )
+        )
+
+        val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+            TimeRangeFilter.between(
+                START_TIME + 30.seconds,
+                START_TIME + 6.minutes + 45.seconds
+            ), emptySet()
+        )
+
+        assertThat(aggregationResult[NutritionRecord.TRANS_FAT_TOTAL])
+            .isEqualTo(Mass.grams(0.15 + 0.4 + 0.375))
+        assertThat(aggregationResult.dataOrigins).containsExactly(DataOrigin(context.packageName))
+    }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_instantTimeRangeFilter_filterStartTimeRecordEndTime() =
+        runTest {
+            healthConnectClient.insertRecords(
+                listOf(
+                    NutritionRecord(
+                        startTime = START_TIME,
+                        endTime = START_TIME + 1.minutes,
+                        transFat = Mass.grams(0.3),
+                        startZoneOffset = ZoneOffset.UTC,
+                        endZoneOffset = ZoneOffset.UTC
+                    ),
+                    NutritionRecord(
+                        startTime = START_TIME + 2.minutes,
+                        endTime = START_TIME + 3.minutes,
+                        transFat = Mass.grams(0.4),
+                        startZoneOffset = ZoneOffset.UTC,
+                        endZoneOffset = ZoneOffset.UTC
+                    )
+                )
+            )
+
+            val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+                TimeRangeFilter.between(
+                    START_TIME + 1.minutes,
+                    START_TIME + 2.minutes
+                ), emptySet()
+            )
+
+            assertThat(NutritionRecord.TRANS_FAT_TOTAL in aggregationResult).isFalse()
+            assertThat(aggregationResult.dataOrigins).isEmpty()
+        }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_instantTimeRangeFilter_filterStartTimeRecordStartTime() =
+        runTest {
+            healthConnectClient.insertRecords(
+                listOf(
+                    NutritionRecord(
+                        startTime = START_TIME,
+                        endTime = START_TIME + 1.minutes,
+                        transFat = Mass.grams(0.3),
+                        startZoneOffset = ZoneOffset.UTC,
+                        endZoneOffset = ZoneOffset.UTC
+                    ),
+                    NutritionRecord(
+                        startTime = START_TIME + 2.minutes,
+                        endTime = START_TIME + 3.minutes,
+                        transFat = Mass.grams(0.4),
+                        startZoneOffset = ZoneOffset.UTC,
+                        endZoneOffset = ZoneOffset.UTC
+                    )
+                )
+            )
+
+            val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+                TimeRangeFilter.between(
+                    START_TIME,
+                    START_TIME + 2.minutes
+                ), emptySet()
+            )
+
+            assertThat(aggregationResult[NutritionRecord.TRANS_FAT_TOTAL])
+                .isEqualTo(Mass.grams(0.3))
+            assertThat(aggregationResult.dataOrigins)
+                .containsExactly(DataOrigin(context.packageName))
+        }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_instantTimeRangeFilter_recordRangeLargerThanQuery() =
+        runTest {
+            healthConnectClient.insertRecords(
+                listOf(
+                    NutritionRecord(
+                        startTime = START_TIME,
+                        endTime = START_TIME + 1.minutes,
+                        transFat = Mass.grams(0.5),
+                        startZoneOffset = ZoneOffset.UTC,
+                        endZoneOffset = ZoneOffset.UTC
+                    ),
+                )
+            )
+
+            val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+                TimeRangeFilter.between(
+                    START_TIME + 15.seconds,
+                    START_TIME + 45.seconds
+                ), emptySet()
+            )
+
+            assertThat(aggregationResult[NutritionRecord.TRANS_FAT_TOTAL])
+                .isEqualTo(Mass.grams(0.25))
+            assertThat(aggregationResult.dataOrigins)
+                .containsExactly(DataOrigin(context.packageName))
+        }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_localTimeRangeFilter() = runTest {
+        healthConnectClient.insertRecords(
+            listOf(
+                NutritionRecord(
+                    startTime = START_TIME,
+                    endTime = START_TIME + 1.minutes,
+                    transFat = Mass.grams(0.3),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 2.minutes,
+                    endTime = START_TIME + 3.minutes,
+                    transFat = null,
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME - 2.hours + 4.minutes,
+                    endTime = START_TIME + 5.minutes,
+                    transFat = Mass.grams(0.4),
+                    startZoneOffset = ZoneOffset.ofHours(2),
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+                NutritionRecord(
+                    startTime = START_TIME + 3.hours + 6.minutes,
+                    endTime = START_TIME + 3.hours + 7.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.ofHours(-3),
+                    endZoneOffset = ZoneOffset.ofHours(-3)
+                ),
+                NutritionRecord(
+                    startTime = START_TIME - 4.hours + 8.minutes,
+                    endTime = START_TIME - 4.hours + 9.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.ofHours(4),
+                    endZoneOffset = ZoneOffset.ofHours(4)
+                )
+            )
+        )
+
+        val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+            TimeRangeFilter.between(
+                LocalDateTime.ofInstant(START_TIME + 30.seconds, ZoneOffset.UTC),
+                LocalDateTime.ofInstant(START_TIME + 6.minutes + 45.seconds, ZoneOffset.UTC)
+            ), emptySet()
+        )
+
+        assertThat(aggregationResult[NutritionRecord.TRANS_FAT_TOTAL])
+            .isEqualTo(Mass.grams(0.15 + 0.4 + 0.375))
+        assertThat(aggregationResult.dataOrigins).containsExactly(DataOrigin(context.packageName))
+    }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_localTimeRangeFilter_recordRangeLargerThanQuery() =
+        runTest {
+            healthConnectClient.insertRecords(
+                listOf(
+                    NutritionRecord(
+                        startTime = START_TIME,
+                        endTime = START_TIME + 1.minutes,
+                        transFat = Mass.grams(0.5),
+                        startZoneOffset = ZoneOffset.UTC,
+                        endZoneOffset = ZoneOffset.UTC
+                    ),
+                )
+            )
+
+            val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+                TimeRangeFilter.between(
+                    LocalDateTime.ofInstant(
+                        START_TIME - 2.hours + 15.seconds,
+                        ZoneOffset.ofHours(2)
+                    ),
+                    LocalDateTime.ofInstant(
+                        START_TIME - 2.hours + 45.seconds,
+                        ZoneOffset.ofHours(2)
+                    )
+                ), emptySet()
+            )
+
+            assertThat(aggregationResult[NutritionRecord.TRANS_FAT_TOTAL])
+                .isEqualTo(Mass.grams(0.25))
+            assertThat(aggregationResult.dataOrigins)
+                .containsExactly(DataOrigin(context.packageName))
+        }
+
+    // TODO(b/337195270): Test with data origins from multiple apps
+    @Test
+    fun aggregateNutritionTransFatTotal_insertedDataOriginFilter() = runTest {
+        healthConnectClient.insertRecords(
+            listOf(
+                NutritionRecord(
+                    startTime = START_TIME,
+                    endTime = START_TIME + 1.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+            )
+        )
+
+        val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+            TimeRangeFilter.none(),
+            setOf(DataOrigin(context.packageName))
+        )
+
+        assertThat(aggregationResult[NutritionRecord.TRANS_FAT_TOTAL])
+            .isEqualTo(Mass.grams(0.5))
+        assertThat(aggregationResult.dataOrigins).containsExactly(DataOrigin(context.packageName))
+    }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_timeRangeFilterOutOfBounds() = runTest {
+        healthConnectClient.insertRecords(
+            listOf(
+                NutritionRecord(
+                    startTime = START_TIME,
+                    endTime = START_TIME + 1.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+            )
+        )
+
+        val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+            TimeRangeFilter.after(START_TIME + 2.minutes),
+            emptySet()
+        )
+
+        assertThat(NutritionRecord.TRANS_FAT_TOTAL in aggregationResult).isFalse()
+        assertThat(aggregationResult.dataOrigins).isEmpty()
+    }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_recordStartTimeWithNegativeZoneOffset() = runTest {
+        healthConnectClient.insertRecords(
+            listOf(
+                NutritionRecord(
+                    startTime = START_TIME,
+                    endTime = START_TIME + 60.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.ofHours(-2),
+                    endZoneOffset = ZoneOffset.UTC
+                )
+            )
+        )
+
+        val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+            TimeRangeFilter.between(
+                LocalDateTime.ofInstant(START_TIME, ZoneOffset.UTC),
+                LocalDateTime.ofInstant(START_TIME + 60.minutes, ZoneOffset.UTC)
+            ), emptySet()
+        )
+
+        assertThat(NutritionRecord.TRANS_FAT_TOTAL in aggregationResult).isFalse()
+        assertThat(aggregationResult.dataOrigins).isEmpty()
+    }
+
+    @Test
+    fun aggregateNutritionTransFatTotal_nonExistingDataOriginFilter() = runTest {
+        healthConnectClient.insertRecords(
+            listOf(
+                NutritionRecord(
+                    startTime = START_TIME,
+                    endTime = START_TIME + 1.minutes,
+                    transFat = Mass.grams(0.5),
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                ),
+            )
+        )
+
+        val aggregationResult = healthConnectClient.aggregateNutritionTransFatTotal(
+            TimeRangeFilter.none(),
+            setOf(DataOrigin("some random package name"))
+        )
+
+        assertThat(NutritionRecord.TRANS_FAT_TOTAL in aggregationResult).isFalse()
+        assertThat(aggregationResult.dataOrigins).isEmpty()
+    }
+
+    @Test
+    fun readRecordsFlow_noFilters_readsAllInsertedRecords() = runTest {
+        insertManyStepsRecords()
+
+        val count = healthConnectClient.readRecordsFlow(
+            StepsRecord::class,
+            TimeRangeFilter.none(),
+            emptySet()
+        ).fold(0) { currentCount, records ->
+            currentCount + records.size
+        }
+
+        assertThat(count).isEqualTo(10_000L)
+    }
+
+    @Test
+    fun readRecordsFlow_timeRangeFilter_readsFilteredRecords() = runTest {
+        assumeTrue(SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10)
+        insertManyStepsRecords()
+
+        val count = healthConnectClient.readRecordsFlow(
+            StepsRecord::class,
+            TimeRangeFilter.between(START_TIME + 10_000.seconds, START_TIME + 90_000.seconds),
+            emptySet()
+        ).fold(0) { currentCount, records ->
+            currentCount + records.size
+        }
+
+        assertThat(count).isEqualTo(8_000L)
+    }
+
+    // TODO(b/337195270): Test with data origins from multiple apps
+    @Test
+    fun readRecordsFlow_insertedDataOriginFilter_readsAllInsertedRecords() = runTest {
+        insertManyStepsRecords()
+
+        val count = healthConnectClient.readRecordsFlow(
+            StepsRecord::class,
+            TimeRangeFilter.none(),
+            setOf(DataOrigin(context.packageName))
+        ).fold(0) { currentCount, records ->
+            currentCount + records.size
+        }
+
+        assertThat(count).isEqualTo(10_000L)
+    }
+
+    @Test
+    fun readRecordsFlow_nonExistingDataOriginFilter_doesNotReadAnyRecord() = runTest {
+        insertManyStepsRecords()
+
+        val count = healthConnectClient.readRecordsFlow(
+            StepsRecord::class,
+            TimeRangeFilter.none(),
+            setOf(DataOrigin("some random package name"))
+        ).fold(0) { currentCount, records ->
+            currentCount + records.size
+        }
+
+        assertThat(count).isEqualTo(0L)
+    }
+
+    private suspend fun insertManyStepsRecords() {
+        // Insert a large number of step records, bigger than the default page size
+        for (i in 0..9) {
+            healthConnectClient.insertRecords(List(1000) {
+                val startTime = START_TIME + (i * 10_000 + it * 10).seconds
+                StepsRecord(
+                    startTime = startTime,
+                    endTime = startTime + 5.seconds,
+                    count = 10L,
+                    startZoneOffset = ZoneOffset.UTC,
+                    endZoneOffset = ZoneOffset.UTC
+                )
+            })
+        }
+    }
+
+    private val Int.seconds: Duration
+        get() = Duration.ofSeconds(this.toLong())
+
+    private val Int.minutes: Duration
+        get() = Duration.ofMinutes(this.toLong())
+
+    private val Int.hours: Duration
+        get() = Duration.ofHours(this.toLong())
+}
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
index d2edf4b..859cbbc 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/RequestConvertersTest.kt
@@ -25,6 +25,9 @@
 import android.health.connect.datatypes.StepsRecord as PlatformStepsRecord
 import android.health.connect.datatypes.WheelchairPushesRecord as PlatformWheelchairPushesRecord
 import android.os.Build
+import androidx.health.connect.client.impl.platform.request.toAggregationType
+import androidx.health.connect.client.impl.platform.request.toPlatformRequest
+import androidx.health.connect.client.impl.platform.request.toPlatformTimeRangeFilter
 import androidx.health.connect.client.records.HeartRateRecord
 import androidx.health.connect.client.records.NutritionRecord
 import androidx.health.connect.client.records.StepsRecord
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/ResponseConvertersTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/ResponseConvertersTest.kt
index fffd43a..b7bbc3d 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/ResponseConvertersTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/platform/records/ResponseConvertersTest.kt
@@ -23,8 +23,13 @@
 import android.health.connect.datatypes.units.Power as PlatformPower
 import android.health.connect.datatypes.units.Volume as PlatformVolume
 import android.os.Build
+import android.os.ext.SdkExtensions
 import androidx.health.connect.client.aggregate.AggregateMetric
+import androidx.health.connect.client.impl.platform.response.buildAggregationResult
+import androidx.health.connect.client.impl.platform.response.getDoubleMetricValues
+import androidx.health.connect.client.impl.platform.response.getLongMetricValues
 import androidx.health.connect.client.records.BasalMetabolicRateRecord
+import androidx.health.connect.client.records.BloodPressureRecord
 import androidx.health.connect.client.records.DistanceRecord
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.FloorsClimbedRecord
@@ -32,6 +37,8 @@
 import androidx.health.connect.client.records.HydrationRecord
 import androidx.health.connect.client.records.NutritionRecord
 import androidx.health.connect.client.records.PowerRecord
+import androidx.health.connect.client.records.SpeedRecord
+import androidx.health.connect.client.records.WeightRecord
 import androidx.health.connect.client.records.metadata.DataOrigin
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
@@ -39,6 +46,7 @@
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
 import java.time.Duration
+import org.junit.Assume.assumeTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -56,8 +64,10 @@
     fun buildAggregationResult() {
         val aggregationResult =
             buildAggregationResult(
-                metrics =
-                    setOf(HeartRateRecord.BPM_MIN, ExerciseSessionRecord.EXERCISE_DURATION_TOTAL),
+                metrics = setOf(
+                    HeartRateRecord.BPM_MIN,
+                    ExerciseSessionRecord.EXERCISE_DURATION_TOTAL
+                ),
                 aggregationValueGetter = { aggregationType ->
                     when (aggregationType) {
                         PlatformHeartRateRecord.BPM_MIN -> 53L
@@ -72,8 +82,10 @@
                                 PlatformDataOriginBuilder().setPackageName("HR App1").build(),
                                 PlatformDataOriginBuilder().setPackageName("HR App2").build()
                             )
+
                         PlatformExerciseSessionRecord.EXERCISE_DURATION_TOTAL ->
                             setOf(PlatformDataOriginBuilder().setPackageName("Workout app").build())
+
                         else -> emptySet()
                     }
                 }
@@ -159,6 +171,17 @@
     }
 
     @Test
+    fun getDoubleMetricValue_convertsMassToKilograms() {
+        val metricValues = getDoubleMetricValues(
+            mapOf(
+                WeightRecord.WEIGHT_MAX as AggregateMetric<Any> to PlatformMass.fromGrams(100_000.0)
+            )
+        )
+
+        assertThat(metricValues).containsExactly(WeightRecord.WEIGHT_MAX.metricKey, 100.0)
+    }
+
+    @Test
     fun getDoubleMetricValues_convertsPowerToWatts() {
         val metricValues =
             getDoubleMetricValues(
@@ -170,6 +193,34 @@
     }
 
     @Test
+    fun getDoubleMetricValues_convertsPressureToMillimetersOfMercury() {
+        assumeTrue(SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10)
+        val metricValues = getDoubleMetricValues(
+            mapOf(
+                BloodPressureRecord.SYSTOLIC_MAX as AggregateMetric<Any> to
+                    PlatformPressure.fromMillimetersOfMercury(
+                        120.0
+                    )
+            )
+        )
+
+        assertThat(metricValues).containsExactly(BloodPressureRecord.SYSTOLIC_MAX.metricKey, 120.0)
+    }
+
+    @Test
+    fun getDoubleMetricValues_convertsVelocityToMetersPerSecond() {
+        assumeTrue(SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10)
+        val metricValues = getDoubleMetricValues(
+            mapOf(
+                SpeedRecord.SPEED_AVG as AggregateMetric<Any> to
+                    PlatformVelocity.fromMetersPerSecond(2.8)
+            )
+        )
+
+        assertThat(metricValues).containsExactly(SpeedRecord.SPEED_AVG.metricKey, 2.8)
+    }
+
+    @Test
     fun getDoubleMetricValues_convertsVolumeToLiters() {
         val metricValues =
             getDoubleMetricValues(
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
index eac2033..5b6d17e 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImpl.kt
@@ -38,14 +38,16 @@
 import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod
 import androidx.health.connect.client.changes.DeletionChange
 import androidx.health.connect.client.changes.UpsertionChange
-import androidx.health.connect.client.impl.platform.records.toPlatformLocalTimeRangeFilter
+import androidx.health.connect.client.impl.platform.aggregate.aggregateFallback
+import androidx.health.connect.client.impl.platform.aggregate.plus
 import androidx.health.connect.client.impl.platform.records.toPlatformRecord
 import androidx.health.connect.client.impl.platform.records.toPlatformRecordClass
-import androidx.health.connect.client.impl.platform.records.toPlatformRequest
-import androidx.health.connect.client.impl.platform.records.toPlatformTimeRangeFilter
 import androidx.health.connect.client.impl.platform.records.toSdkRecord
-import androidx.health.connect.client.impl.platform.records.toSdkResponse
+import androidx.health.connect.client.impl.platform.request.toPlatformLocalTimeRangeFilter
+import androidx.health.connect.client.impl.platform.request.toPlatformRequest
+import androidx.health.connect.client.impl.platform.request.toPlatformTimeRangeFilter
 import androidx.health.connect.client.impl.platform.response.toKtResponse
+import androidx.health.connect.client.impl.platform.response.toSdkResponse
 import androidx.health.connect.client.impl.platform.toKtException
 import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_PREFIX
 import androidx.health.connect.client.records.Record
@@ -201,31 +203,33 @@
     }
 
     override suspend fun aggregate(request: AggregateRequest): AggregationResult {
-        return wrapPlatformException {
-                suspendCancellableCoroutine { continuation ->
-                    healthConnectManager.aggregate(
-                        request.toPlatformRequest(),
-                        executor,
-                        continuation.asOutcomeReceiver()
-                    )
-                }
+        val platformResponse = wrapPlatformException {
+            suspendCancellableCoroutine { continuation ->
+                healthConnectManager.aggregate(
+                    request.toPlatformRequest(),
+                    executor,
+                    continuation.asOutcomeReceiver()
+                )
             }
+        }
             .toSdkResponse(request.metrics)
+        val fallbackResponse = aggregateFallback(request)
+        return platformResponse + fallbackResponse
     }
 
     override suspend fun aggregateGroupByDuration(
         request: AggregateGroupByDurationRequest
     ): List<AggregationResultGroupedByDuration> {
         return wrapPlatformException {
-                suspendCancellableCoroutine { continuation ->
-                    healthConnectManager.aggregateGroupByDuration(
-                        request.toPlatformRequest(),
-                        request.timeRangeSlicer,
-                        executor,
-                        continuation.asOutcomeReceiver()
-                    )
-                }
+            suspendCancellableCoroutine { continuation ->
+                healthConnectManager.aggregateGroupByDuration(
+                    request.toPlatformRequest(),
+                    request.timeRangeSlicer,
+                    executor,
+                    continuation.asOutcomeReceiver()
+                )
             }
+        }
             .map { it.toSdkResponse(request.metrics) }
     }
 
@@ -233,19 +237,19 @@
         request: AggregateGroupByPeriodRequest
     ): List<AggregationResultGroupedByPeriod> {
         return wrapPlatformException {
-                suspendCancellableCoroutine { continuation ->
-                    healthConnectManager.aggregateGroupByPeriod(
-                        request.toPlatformRequest(),
-                        request.timeRangeSlicer,
-                        executor,
-                        continuation.asOutcomeReceiver()
-                    )
-                }
+            suspendCancellableCoroutine { continuation ->
+                healthConnectManager.aggregateGroupByPeriod(
+                    request.toPlatformRequest(),
+                    request.timeRangeSlicer,
+                    executor,
+                    continuation.asOutcomeReceiver()
+                )
             }
+        }
             .mapIndexed { index, platformResponse ->
                 if (
                     SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10 ||
-                        (request.timeRangeSlicer.months == 0 && request.timeRangeSlicer.years == 0)
+                    (request.timeRangeSlicer.months == 0 && request.timeRangeSlicer.years == 0)
                 ) {
                     platformResponse.toSdkResponse(request.metrics)
                 } else {
@@ -261,11 +265,11 @@
                         metrics = request.metrics,
                         bucketStartTime = bucketStartTime,
                         bucketEndTime =
-                            if (requestTimeRangeFilter.endTime!!.isBefore(bucketEndTime)) {
-                                requestTimeRangeFilter.endTime!!
-                            } else {
-                                bucketEndTime
-                            }
+                        if (requestTimeRangeFilter.endTime!!.isBefore(bucketEndTime)) {
+                            requestTimeRangeFilter.endTime!!
+                        } else {
+                            bucketEndTime
+                        }
                     )
                 }
             }
@@ -273,14 +277,14 @@
 
     override suspend fun getChangesToken(request: ChangesTokenRequest): String {
         return wrapPlatformException {
-                suspendCancellableCoroutine { continuation ->
-                    healthConnectManager.getChangeLogToken(
-                        request.toPlatformRequest(),
-                        executor,
-                        continuation.asOutcomeReceiver()
-                    )
-                }
+            suspendCancellableCoroutine { continuation ->
+                healthConnectManager.getChangeLogToken(
+                    request.toPlatformRequest(),
+                    executor,
+                    continuation.asOutcomeReceiver()
+                )
             }
+        }
             .token
     }
 
@@ -324,7 +328,7 @@
                     for (i in it.requestedPermissions.indices) {
                         if (
                             it.requestedPermissions[i].startsWith(PERMISSION_PREFIX) &&
-                                it.requestedPermissionsFlags[i] and REQUESTED_PERMISSION_GRANTED > 0
+                            it.requestedPermissionsFlags[i] and REQUESTED_PERMISSION_GRANTED > 0
                         ) {
                             add(it.requestedPermissions[i])
                         }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/TimeExtensions.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/TimeExtensions.kt
new file mode 100644
index 0000000..a8705ff
--- /dev/null
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/TimeExtensions.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 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.impl.platform
+
+import androidx.health.connect.client.records.IntervalRecord
+import androidx.health.connect.client.time.TimeRangeFilter
+import java.time.Duration
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.ZoneOffset
+
+internal operator fun Duration.div(divisor: Duration): Double {
+    if (divisor.isZero) {
+        return 0.0
+    }
+    return toMillis().toDouble() / divisor.toMillis()
+}
+
+internal operator fun Instant.minus(other: Instant): Duration {
+    return Duration.between(other, this)
+}
+
+internal fun TimeRangeFilter.useLocalTime(): Boolean {
+    return localStartTime != null || localEndTime != null
+}
+
+internal fun LocalDateTime.toInstantWithDefaultZoneFallback(zoneOffset: ZoneOffset?): Instant {
+    return atZone(zoneOffset ?: ZoneId.systemDefault()).toInstant()
+}
+
+internal val IntervalRecord.duration: Duration
+    get() = endTime - startTime
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationExtensions.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationExtensions.kt
new file mode 100644
index 0000000..f323864
--- /dev/null
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationExtensions.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024 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.
+ */
+
+@file:RequiresApi(api = 34)
+
+package androidx.health.connect.client.impl.platform.aggregate
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresApi
+import androidx.health.connect.client.aggregate.AggregateMetric
+import androidx.health.connect.client.aggregate.AggregationResult
+import androidx.health.connect.client.records.BloodPressureRecord
+import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
+import androidx.health.connect.client.records.NutritionRecord
+import androidx.health.connect.client.records.SpeedRecord
+import androidx.health.connect.client.records.StepsCadenceRecord
+import androidx.health.connect.client.request.AggregateRequest
+
+internal val AggregateRequest.platformMetrics: Set<AggregateMetric<*>>
+    get() {
+        if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
+            return metrics
+        }
+        return metrics.filterNot { it in SDK_EXT_10_AGGREGATE_METRICS }.toSet()
+    }
+
+internal val AggregateRequest.fallbackMetrics: Set<AggregateMetric<*>>
+    get() {
+        if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
+            return emptySet()
+        }
+        return metrics.filter { it in SDK_EXT_10_AGGREGATE_METRICS }.toSet()
+    }
+
+internal operator fun AggregationResult.plus(other: AggregationResult): AggregationResult {
+    return AggregationResult(
+        longValues + other.longValues,
+        doubleValues + other.doubleValues,
+        dataOrigins + other.dataOrigins
+    )
+}
+
+internal val SDK_EXT_10_AGGREGATE_METRICS: Set<AggregateMetric<*>> =
+    setOf(
+        BloodPressureRecord.DIASTOLIC_AVG,
+        BloodPressureRecord.DIASTOLIC_MAX,
+        BloodPressureRecord.DIASTOLIC_MIN,
+        BloodPressureRecord.SYSTOLIC_AVG,
+        BloodPressureRecord.SYSTOLIC_MAX,
+        BloodPressureRecord.SYSTOLIC_MIN,
+        CyclingPedalingCadenceRecord.RPM_AVG,
+        CyclingPedalingCadenceRecord.RPM_MAX,
+        CyclingPedalingCadenceRecord.RPM_MIN,
+        NutritionRecord.TRANS_FAT_TOTAL,
+        SpeedRecord.SPEED_AVG,
+        SpeedRecord.SPEED_MAX,
+        SpeedRecord.SPEED_MIN,
+        StepsCadenceRecord.RATE_AVG,
+        StepsCadenceRecord.RATE_MAX,
+        StepsCadenceRecord.RATE_MIN
+    )
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/AggregationMappings.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt
similarity index 74%
rename from health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/AggregationMappings.kt
rename to health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt
index 42d97d3..1d45df8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/AggregationMappings.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/AggregationMappings.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 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.
@@ -17,7 +17,7 @@
 @file:RestrictTo(RestrictTo.Scope.LIBRARY)
 @file:RequiresApi(api = 34)
 
-package androidx.health.connect.client.impl.platform.records
+package androidx.health.connect.client.impl.platform.aggregate
 
 import android.health.connect.datatypes.ActiveCaloriesBurnedRecord as PlatformActiveCaloriesBurnedRecord
 import android.health.connect.datatypes.AggregationType as PlatformAggregateMetric
@@ -39,11 +39,24 @@
 import android.health.connect.datatypes.units.Mass as PlatformMass
 import android.health.connect.datatypes.units.Power as PlatformPower
 import android.health.connect.datatypes.units.Volume as PlatformVolume
+import android.os.Build
+import android.os.ext.SdkExtensions
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.health.connect.client.aggregate.AggregateMetric
+import androidx.health.connect.client.impl.platform.records.PlatformBloodPressureRecord
+import androidx.health.connect.client.impl.platform.records.PlatformCyclingPedalingCadenceRecord
+import androidx.health.connect.client.impl.platform.records.PlatformExerciseSessionRecord
+import androidx.health.connect.client.impl.platform.records.PlatformPressure
+import androidx.health.connect.client.impl.platform.records.PlatformRestingHeartRateRecord
+import androidx.health.connect.client.impl.platform.records.PlatformSleepSessionRecord
+import androidx.health.connect.client.impl.platform.records.PlatformSpeedRecord
+import androidx.health.connect.client.impl.platform.records.PlatformStepsCadenceRecord
+import androidx.health.connect.client.impl.platform.records.PlatformVelocity
 import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
 import androidx.health.connect.client.records.BasalMetabolicRateRecord
+import androidx.health.connect.client.records.BloodPressureRecord
+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.ExerciseSessionRecord
@@ -55,6 +68,8 @@
 import androidx.health.connect.client.records.PowerRecord
 import androidx.health.connect.client.records.RestingHeartRateRecord
 import androidx.health.connect.client.records.SleepSessionRecord
+import androidx.health.connect.client.records.SpeedRecord
+import androidx.health.connect.client.records.StepsCadenceRecord
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
 import androidx.health.connect.client.records.WeightRecord
@@ -63,14 +78,31 @@
 import androidx.health.connect.client.units.Length
 import androidx.health.connect.client.units.Mass
 import androidx.health.connect.client.units.Power
+import androidx.health.connect.client.units.Pressure
+import androidx.health.connect.client.units.Velocity
 import androidx.health.connect.client.units.Volume
 import java.time.Duration
 
+private val DOUBLE_AGGREGATION_METRIC_TYPE_SDK_EXT_10_PAIRS =
+    if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
+        arrayOf(
+            CyclingPedalingCadenceRecord.RPM_AVG to PlatformCyclingPedalingCadenceRecord.RPM_AVG,
+            CyclingPedalingCadenceRecord.RPM_MAX to PlatformCyclingPedalingCadenceRecord.RPM_MAX,
+            CyclingPedalingCadenceRecord.RPM_MIN to PlatformCyclingPedalingCadenceRecord.RPM_MIN,
+            StepsCadenceRecord.RATE_AVG to PlatformStepsCadenceRecord.STEPS_CADENCE_RATE_AVG,
+            StepsCadenceRecord.RATE_MAX to PlatformStepsCadenceRecord.STEPS_CADENCE_RATE_MAX,
+            StepsCadenceRecord.RATE_MIN to PlatformStepsCadenceRecord.STEPS_CADENCE_RATE_MIN
+        )
+    } else {
+        emptyArray()
+    }
+
 internal val DOUBLE_AGGREGATION_METRIC_TYPE_MAP:
     Map<AggregateMetric<Double>, PlatformAggregateMetric<Double>> =
     mapOf(
         FloorsClimbedRecord.FLOORS_CLIMBED_TOTAL to
             PlatformFloorsClimbedRecord.FLOORS_CLIMBED_TOTAL,
+        *DOUBLE_AGGREGATION_METRIC_TYPE_SDK_EXT_10_PAIRS
     )
 
 internal val DURATION_AGGREGATION_METRIC_TYPE_MAP:
@@ -163,7 +195,14 @@
         NutritionRecord.VITAMIN_D_TOTAL to PlatformNutritionRecord.VITAMIN_D_TOTAL,
         NutritionRecord.VITAMIN_E_TOTAL to PlatformNutritionRecord.VITAMIN_E_TOTAL,
         NutritionRecord.VITAMIN_K_TOTAL to PlatformNutritionRecord.VITAMIN_K_TOTAL,
-        NutritionRecord.ZINC_TOTAL to PlatformNutritionRecord.ZINC_TOTAL
+        NutritionRecord.ZINC_TOTAL to PlatformNutritionRecord.ZINC_TOTAL,
+        *if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
+            arrayOf(
+                NutritionRecord.TRANS_FAT_TOTAL to PlatformNutritionRecord.TRANS_FAT_TOTAL
+            )
+        } else {
+            emptyArray()
+        }
     )
 
 internal val KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP:
@@ -182,6 +221,33 @@
         PowerRecord.POWER_MIN to PlatformPowerRecord.POWER_MIN,
     )
 
+internal val PRESSURE_AGGREGATION_METRIC_TYPE_MAP:
+    Map<AggregateMetric<Pressure>, PlatformAggregateMetric<PlatformPressure>> =
+    if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
+        arrayOf(
+            BloodPressureRecord.DIASTOLIC_AVG to PlatformBloodPressureRecord.DIASTOLIC_AVG,
+            BloodPressureRecord.DIASTOLIC_MAX to PlatformBloodPressureRecord.DIASTOLIC_MAX,
+            BloodPressureRecord.DIASTOLIC_MIN to PlatformBloodPressureRecord.DIASTOLIC_MIN,
+            BloodPressureRecord.SYSTOLIC_AVG to PlatformBloodPressureRecord.SYSTOLIC_AVG,
+            BloodPressureRecord.SYSTOLIC_MAX to PlatformBloodPressureRecord.SYSTOLIC_MAX,
+            BloodPressureRecord.SYSTOLIC_MIN to PlatformBloodPressureRecord.SYSTOLIC_MIN
+        )
+    } else {
+        emptyArray()
+    }.toMap()
+
+internal val VELOCITY_AGGREGATION_METRIC_TYPE_MAP:
+    Map<AggregateMetric<Velocity>, PlatformAggregateMetric<PlatformVelocity>> =
+    if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 10) {
+        arrayOf(
+            SpeedRecord.SPEED_AVG to PlatformSpeedRecord.SPEED_AVG,
+            SpeedRecord.SPEED_MAX to PlatformSpeedRecord.SPEED_MAX,
+            SpeedRecord.SPEED_MIN to PlatformSpeedRecord.SPEED_MIN
+        )
+    } else {
+        emptyArray()
+    }.toMap()
+
 internal val VOLUME_AGGREGATION_METRIC_TYPE_MAP:
     Map<AggregateMetric<Volume>, PlatformAggregateMetric<PlatformVolume>> =
     mapOf(
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensions.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensions.kt
new file mode 100644
index 0000000..e8ea7d7
--- /dev/null
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/aggregate/HealthConnectClientAggregationExtensions.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2024 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.
+ */
+
+@file:RequiresApi(api = 34)
+
+package androidx.health.connect.client.impl.platform.aggregate
+
+import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+import androidx.health.connect.client.HealthConnectClient
+import androidx.health.connect.client.aggregate.AggregateMetric
+import androidx.health.connect.client.aggregate.AggregationResult
+import androidx.health.connect.client.impl.platform.div
+import androidx.health.connect.client.impl.platform.duration
+import androidx.health.connect.client.impl.platform.minus
+import androidx.health.connect.client.impl.platform.toInstantWithDefaultZoneFallback
+import androidx.health.connect.client.impl.platform.useLocalTime
+import androidx.health.connect.client.records.BloodPressureRecord
+import androidx.health.connect.client.records.CyclingPedalingCadenceRecord
+import androidx.health.connect.client.records.IntervalRecord
+import androidx.health.connect.client.records.NutritionRecord
+import androidx.health.connect.client.records.Record
+import androidx.health.connect.client.records.SpeedRecord
+import androidx.health.connect.client.records.StepsCadenceRecord
+import androidx.health.connect.client.records.metadata.DataOrigin
+import androidx.health.connect.client.request.AggregateRequest
+import androidx.health.connect.client.request.ReadRecordsRequest
+import androidx.health.connect.client.time.TimeRangeFilter
+import java.time.Duration
+import java.time.Instant
+import kotlin.math.max
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.fold
+
+// Max buffer to account for overlapping records that have startTime < timeRangeFilter.startTime
+val RECORD_START_TIME_BUFFER: Duration = Duration.ofDays(1)
+
+internal suspend fun HealthConnectClient.aggregateFallback(request: AggregateRequest):
+    AggregationResult {
+    return request.fallbackMetrics.fold(
+        AggregationResult(
+            longValues = mapOf(),
+            doubleValues = mapOf(),
+            dataOrigins = setOf()
+        )
+    ) { currentAggregateResult, metric ->
+        currentAggregateResult + aggregate(
+            metric,
+            request.timeRangeFilter,
+            request.dataOriginFilter
+        )
+    }
+}
+
+private suspend fun <T : Any> HealthConnectClient.aggregate(
+    metric: AggregateMetric<T>,
+    timeRangeFilter: TimeRangeFilter,
+    dataOriginFilter: Set<DataOrigin>
+): AggregationResult {
+    return when (metric) {
+        NutritionRecord.TRANS_FAT_TOTAL -> aggregateNutritionTransFatTotal(
+            timeRangeFilter,
+            dataOriginFilter
+        )
+
+        BloodPressureRecord.DIASTOLIC_AVG -> TODO(reason = "b/326414908")
+        BloodPressureRecord.DIASTOLIC_MAX -> TODO(reason = "b/326414908")
+        BloodPressureRecord.DIASTOLIC_MIN -> TODO(reason = "b/326414908")
+        BloodPressureRecord.SYSTOLIC_AVG -> TODO(reason = "b/326414908")
+        BloodPressureRecord.SYSTOLIC_MAX -> TODO(reason = "b/326414908")
+        BloodPressureRecord.SYSTOLIC_MIN -> TODO(reason = "b/326414908")
+        CyclingPedalingCadenceRecord.RPM_AVG -> TODO(reason = "b/326414908")
+        CyclingPedalingCadenceRecord.RPM_MAX -> TODO(reason = "b/326414908")
+        CyclingPedalingCadenceRecord.RPM_MIN -> TODO(reason = "b/326414908")
+        SpeedRecord.SPEED_AVG -> TODO(reason = "b/326414908")
+        SpeedRecord.SPEED_MAX -> TODO(reason = "b/326414908")
+        SpeedRecord.SPEED_MIN -> TODO(reason = "b/326414908")
+        StepsCadenceRecord.RATE_AVG -> TODO(reason = "b/326414908")
+        StepsCadenceRecord.RATE_MAX -> TODO(reason = "b/326414908")
+        StepsCadenceRecord.RATE_MIN -> TODO(reason = "b/326414908")
+        else -> error("Invalid fallback aggregation type ${metric.metricKey}")
+    }
+}
+
+@VisibleForTesting
+internal suspend fun HealthConnectClient.aggregateNutritionTransFatTotal(
+    timeRangeFilter: TimeRangeFilter,
+    dataOriginFilter: Set<DataOrigin>
+): AggregationResult {
+    val readRecordsFlow = readRecordsFlow(
+        NutritionRecord::class,
+        timeRangeFilter.withBufferedStart(),
+        dataOriginFilter
+    )
+
+    val aggregatedData = readRecordsFlow
+        .fold(AggregatedData(0.0)) { currentAggregatedData, records ->
+            val filteredRecords = records.filter {
+                it.overlaps(timeRangeFilter) && it.transFat != null &&
+                    sliceFactor(it, timeRangeFilter) > 0
+            }
+
+            filteredRecords.forEach {
+                currentAggregatedData.value +=
+                    it.transFat!!.inGrams * sliceFactor(it, timeRangeFilter)
+            }
+
+            filteredRecords.mapTo(currentAggregatedData.dataOrigins) { it.metadata.dataOrigin }
+            currentAggregatedData
+        }
+
+    if (aggregatedData.dataOrigins.isEmpty()) {
+        return emptyAggregationResult()
+    }
+
+    return AggregationResult(
+        longValues = mapOf(),
+        doubleValues = mapOf(NutritionRecord.TRANS_FAT_TOTAL.metricKey to aggregatedData.value),
+        dataOrigins = aggregatedData.dataOrigins
+    )
+}
+
+/** Reads all existing records that satisfy [timeRangeFilter] and [dataOriginFilter]. */
+@VisibleForTesting
+suspend fun <T : Record> HealthConnectClient.readRecordsFlow(
+    recordType: KClass<T>,
+    timeRangeFilter: TimeRangeFilter,
+    dataOriginFilter: Set<DataOrigin>
+): Flow<List<T>> {
+    return flow {
+        var pageToken: String? = null
+        do {
+            val response = readRecords(
+                ReadRecordsRequest(
+                    recordType = recordType,
+                    timeRangeFilter = timeRangeFilter,
+                    dataOriginFilter = dataOriginFilter,
+                    pageToken = pageToken
+                )
+            )
+            emit(response.records)
+            pageToken = response.pageToken
+        } while (pageToken != null)
+    }
+}
+
+private fun IntervalRecord.overlaps(timeRangeFilter: TimeRangeFilter): Boolean {
+    val startTimeOverlaps: Boolean
+    val endTimeOverlaps: Boolean
+    if (timeRangeFilter.useLocalTime()) {
+        startTimeOverlaps = timeRangeFilter.localEndTime == null ||
+            startTime.isBefore(
+                timeRangeFilter.localEndTime.toInstantWithDefaultZoneFallback(startZoneOffset)
+            )
+        endTimeOverlaps = timeRangeFilter.localStartTime == null ||
+            endTime.isAfter(
+                timeRangeFilter.localStartTime.toInstantWithDefaultZoneFallback(endZoneOffset)
+            )
+    } else {
+        startTimeOverlaps = timeRangeFilter.endTime == null ||
+            startTime.isBefore(timeRangeFilter.endTime)
+        endTimeOverlaps = timeRangeFilter.startTime == null ||
+            endTime.isAfter(timeRangeFilter.startTime)
+    }
+    return startTimeOverlaps && endTimeOverlaps
+}
+
+private fun TimeRangeFilter.withBufferedStart(): TimeRangeFilter {
+    return TimeRangeFilter(
+        startTime = startTime?.minus(RECORD_START_TIME_BUFFER),
+        endTime = endTime,
+        localStartTime = localStartTime?.minus(RECORD_START_TIME_BUFFER),
+        localEndTime = localEndTime
+    )
+}
+
+private fun sliceFactor(record: NutritionRecord, timeRangeFilter: TimeRangeFilter): Double {
+    val startTime: Instant
+    val endTime: Instant
+
+    if (timeRangeFilter.useLocalTime()) {
+        val requestStartTime =
+            timeRangeFilter.localStartTime?.toInstantWithDefaultZoneFallback(record.startZoneOffset)
+        val requestEndTime =
+            timeRangeFilter.localEndTime?.toInstantWithDefaultZoneFallback(record.endZoneOffset)
+        startTime = maxOf(record.startTime, requestStartTime ?: record.startTime)
+        endTime = minOf(record.endTime, requestEndTime ?: record.endTime)
+    } else {
+        startTime = maxOf(record.startTime, timeRangeFilter.startTime ?: record.startTime)
+        endTime = minOf(record.endTime, timeRangeFilter.endTime ?: record.endTime)
+    }
+
+    return max(0.0, (endTime - startTime) / record.duration)
+}
+
+private fun emptyAggregationResult() =
+    AggregationResult(longValues = mapOf(), doubleValues = mapOf(), dataOrigins = setOf())
+
+private data class AggregatedData<T>(
+    var value: T,
+    var dataOrigins: MutableSet<DataOrigin> = mutableSetOf()
+)
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt
similarity index 78%
rename from health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
rename to health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt
index 34699be..ac3fd89 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/RequestConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/request/RequestConverters.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2024 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.
@@ -17,7 +17,7 @@
 @file:RestrictTo(RestrictTo.Scope.LIBRARY)
 @file:RequiresApi(api = 34)
 
-package androidx.health.connect.client.impl.platform.records
+package androidx.health.connect.client.impl.platform.request
 
 import android.health.connect.AggregateRecordsRequest
 import android.health.connect.LocalTimeRangeFilter
@@ -30,6 +30,20 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.health.connect.client.aggregate.AggregateMetric
+import androidx.health.connect.client.impl.platform.aggregate.DOUBLE_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.DURATION_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.ENERGY_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.GRAMS_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.LENGTH_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.LONG_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.POWER_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.PRESSURE_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.VELOCITY_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.VOLUME_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.platformMetrics
+import androidx.health.connect.client.impl.platform.records.toPlatformDataOrigin
+import androidx.health.connect.client.impl.platform.records.toPlatformRecordClass
 import androidx.health.connect.client.records.Record
 import androidx.health.connect.client.request.AggregateGroupByDurationRequest
 import androidx.health.connect.client.request.AggregateGroupByPeriodRequest
@@ -101,7 +115,7 @@
     return AggregateRecordsRequest.Builder<Any>(timeRangeFilter.toPlatformTimeRangeFilter())
         .apply {
             dataOriginFilter.forEach { addDataOriginsFilter(it.toPlatformDataOrigin()) }
-            metrics.forEach { addAggregationType(it.toAggregationType()) }
+            platformMetrics.forEach { addAggregationType(it.toAggregationType()) }
         }
         .build()
 }
@@ -132,11 +146,13 @@
     return DOUBLE_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: DURATION_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: ENERGY_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
+        ?: GRAMS_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: LENGTH_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: LONG_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
-        ?: GRAMS_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: POWER_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
+        ?: PRESSURE_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
+        ?: VELOCITY_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: VOLUME_AGGREGATION_METRIC_TYPE_MAP[this] as AggregationType<Any>?
         ?: throw IllegalArgumentException("Unsupported aggregation type $metricKey")
 }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/ResponseConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt
similarity index 70%
rename from health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/ResponseConverters.kt
rename to health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt
index 30e2279..a6ff703 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/ResponseConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/ResponseConverters.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * Copyright 2024 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.
@@ -17,7 +17,7 @@
 @file:RestrictTo(RestrictTo.Scope.LIBRARY)
 @file:RequiresApi(api = 34)
 
-package androidx.health.connect.client.impl.platform.records
+package androidx.health.connect.client.impl.platform.response
 
 import android.health.connect.AggregateRecordsGroupedByDurationResponse
 import android.health.connect.AggregateRecordsGroupedByPeriodResponse
@@ -32,6 +32,25 @@
 import androidx.health.connect.client.aggregate.AggregationResult
 import androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration
 import androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod
+import androidx.health.connect.client.impl.platform.aggregate.DOUBLE_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.DURATION_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.ENERGY_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.GRAMS_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.LENGTH_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.LONG_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.POWER_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.PRESSURE_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.VELOCITY_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.aggregate.VOLUME_AGGREGATION_METRIC_TYPE_MAP
+import androidx.health.connect.client.impl.platform.records.PlatformDataOrigin
+import androidx.health.connect.client.impl.platform.records.PlatformLength
+import androidx.health.connect.client.impl.platform.records.PlatformMass
+import androidx.health.connect.client.impl.platform.records.PlatformPower
+import androidx.health.connect.client.impl.platform.records.PlatformPressure
+import androidx.health.connect.client.impl.platform.records.PlatformVelocity
+import androidx.health.connect.client.impl.platform.records.toSdkDataOrigin
+import androidx.health.connect.client.impl.platform.request.toAggregationType
 import androidx.health.connect.client.units.Energy
 import androidx.health.connect.client.units.Mass
 import java.time.LocalDateTime
@@ -99,7 +118,7 @@
         metricValueMap.forEach { (key, value) ->
             if (
                 key in DURATION_AGGREGATION_METRIC_TYPE_MAP ||
-                    key in LONG_AGGREGATION_METRIC_TYPE_MAP
+                key in LONG_AGGREGATION_METRIC_TYPE_MAP
             ) {
                 this[key.metricKey] = value as Long
             }
@@ -117,22 +136,36 @@
                 in DOUBLE_AGGREGATION_METRIC_TYPE_MAP -> {
                     this[key.metricKey] = value as Double
                 }
+
                 in ENERGY_AGGREGATION_METRIC_TYPE_MAP -> {
                     this[key.metricKey] =
                         Energy.calories((value as PlatformEnergy).inCalories).inKilocalories
                 }
-                in LENGTH_AGGREGATION_METRIC_TYPE_MAP -> {
-                    this[key.metricKey] = (value as PlatformLength).inMeters
-                }
+
                 in GRAMS_AGGREGATION_METRIC_TYPE_MAP -> {
                     this[key.metricKey] = (value as PlatformMass).inGrams
                 }
+
+                in LENGTH_AGGREGATION_METRIC_TYPE_MAP -> {
+                    this[key.metricKey] = (value as PlatformLength).inMeters
+                }
+
                 in KILOGRAMS_AGGREGATION_METRIC_TYPE_MAP -> {
                     this[key.metricKey] = Mass.grams((value as PlatformMass).inGrams).inKilograms
                 }
+
+                in PRESSURE_AGGREGATION_METRIC_TYPE_MAP -> {
+                    this[key.metricKey] = (value as PlatformPressure).inMillimetersOfMercury
+                }
+
                 in POWER_AGGREGATION_METRIC_TYPE_MAP -> {
                     this[key.metricKey] = (value as PlatformPower).inWatts
                 }
+
+                in VELOCITY_AGGREGATION_METRIC_TYPE_MAP -> {
+                    this[key.metricKey] = (value as PlatformVelocity).inMetersPerSecond
+                }
+
                 in VOLUME_AGGREGATION_METRIC_TYPE_MAP -> {
                     this[key.metricKey] = (value as PlatformVolume).inLiters
                 }
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/TimeExtensionsTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/TimeExtensionsTest.kt
new file mode 100644
index 0000000..a6cdd65
--- /dev/null
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/platform/TimeExtensionsTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 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.impl.platform
+
+import androidx.health.connect.client.records.NutritionRecord
+import androidx.health.connect.client.time.TimeRangeFilter
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneOffset
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TimeExtensionsTest {
+
+    @Test
+    fun div() {
+        val dividend = Duration.ofHours(1)
+        val divisor = Duration.ofHours(4)
+        assertThat(dividend / divisor).isEqualTo(0.25)
+    }
+
+    @Test
+    fun dibByZero_returnsZero() {
+        val dividend = Duration.ofHours(1)
+        val divisor = Duration.ofSeconds(0)
+        assertThat(dividend / divisor).isEqualTo(0.0)
+    }
+
+    @Test
+    fun minus() {
+        val a = Instant.now()
+        val b = a.plusSeconds(5)
+        assertThat(b - a).isEqualTo(Duration.ofSeconds(5))
+    }
+
+    @Test
+    fun useLocalTime() {
+        assertThat(TimeRangeFilter.none().useLocalTime()).isFalse()
+        assertThat(
+            TimeRangeFilter.between(Instant.now(), Instant.now().plusSeconds(2)).useLocalTime()
+        ).isFalse()
+        assertThat(TimeRangeFilter.after(Instant.now()).useLocalTime()).isFalse()
+        assertThat(TimeRangeFilter.before(Instant.now()).useLocalTime()).isFalse()
+
+        assertThat(
+            TimeRangeFilter.between(LocalDateTime.now(), LocalDateTime.now().plusSeconds(2))
+                .useLocalTime()
+        ).isTrue()
+        assertThat(TimeRangeFilter.after(LocalDateTime.now()).useLocalTime()).isTrue()
+        assertThat(TimeRangeFilter.before(LocalDateTime.now()).useLocalTime()).isTrue()
+    }
+
+    @Test
+    fun toInstantWithDefaultZoneFallback() {
+        val instant = Instant.now()
+        val localDateTime = LocalDateTime.ofInstant(instant, ZoneOffset.UTC)
+
+        assertThat(localDateTime.toInstantWithDefaultZoneFallback(ZoneOffset.UTC))
+        .isEqualTo(instant)
+        assertThat(localDateTime.toInstantWithDefaultZoneFallback(ZoneOffset.ofHours(2)))
+            .isEqualTo(instant - Duration.ofHours(2))
+    }
+
+    @Test
+    fun intervalRecord_duration() {
+        val startTime = Instant.now()
+        val nutritionRecord = NutritionRecord(
+            startTime = startTime,
+            endTime = startTime.plusSeconds(10),
+            startZoneOffset = null,
+            endZoneOffset = null
+        )
+        assertThat(nutritionRecord.duration).isEqualTo(Duration.ofSeconds(10))
+    }
+}
diff --git a/libraryversions.toml b/libraryversions.toml
index e366aa0..71941f9 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -194,7 +194,6 @@
 CAMERA_PIPE = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_PIPE", overrideInclude = [ ":camera:camera-camera2-pipe", ":camera:camera-camera2-pipe-integration" ] }
 CAMERA_TESTING = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_TESTING", overrideInclude = [ ":camera:camera-testing" ] }
 CAMERA_VIEWFINDER = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_VIEWFINDER", overrideInclude = [ ":camera:camera-viewfinder", ":camera:camera-viewfinder-core", ":camera:camera-viewfinder-core:camera-viewfinder-core-samples" ] }
-CAMERA_VIEWFINDER_COMPOSE = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_VIEWFINDER_COMPOSE", overrideInclude = [ ":camera:camera-viewfinder-compose" ] }
 CARDVIEW = { group = "androidx.cardview", atomicGroupVersion = "versions.CARDVIEW" }
 CAR_APP = { group = "androidx.car.app", atomicGroupVersion = "versions.CAR_APP" }
 COLLECTION = { group = "androidx.collection", atomicGroupVersion = "versions.COLLECTION" }
diff --git a/wear/compose/compose-foundation/api/1.4.0-beta01.txt b/wear/compose/compose-foundation/api/1.4.0-beta01.txt
index 23f1328..d2d5799 100644
--- a/wear/compose/compose-foundation/api/1.4.0-beta01.txt
+++ b/wear/compose/compose-foundation/api/1.4.0-beta01.txt
@@ -145,7 +145,7 @@
   }
 
   public final class CurvedParentDataKt {
-    method public static androidx.wear.compose.foundation.CurvedModifier parentDataModifier(androidx.wear.compose.foundation.CurvedModifier, kotlin.jvm.functions.Function1<java.lang.Object?,?> modifyParentData);
+    method public static androidx.wear.compose.foundation.CurvedModifier parentDataModifier(androidx.wear.compose.foundation.CurvedModifier, kotlin.jvm.functions.Function1<java.lang.Object?,? extends java.lang.Object?> modifyParentData);
     method public static androidx.wear.compose.foundation.CurvedModifier weight(androidx.wear.compose.foundation.CurvedModifier, @FloatRange(from=0.0, fromInclusive=false) float weight);
   }
 
@@ -426,7 +426,7 @@
     method public int getCenterItemScrollOffset();
     method public androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo getLayoutInfo();
     method public boolean isScrollInProgress();
-    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public boolean canScrollBackward;
     property public boolean canScrollForward;
diff --git a/wear/compose/compose-foundation/api/restricted_1.4.0-beta01.txt b/wear/compose/compose-foundation/api/restricted_1.4.0-beta01.txt
index 23f1328..d2d5799 100644
--- a/wear/compose/compose-foundation/api/restricted_1.4.0-beta01.txt
+++ b/wear/compose/compose-foundation/api/restricted_1.4.0-beta01.txt
@@ -145,7 +145,7 @@
   }
 
   public final class CurvedParentDataKt {
-    method public static androidx.wear.compose.foundation.CurvedModifier parentDataModifier(androidx.wear.compose.foundation.CurvedModifier, kotlin.jvm.functions.Function1<java.lang.Object?,?> modifyParentData);
+    method public static androidx.wear.compose.foundation.CurvedModifier parentDataModifier(androidx.wear.compose.foundation.CurvedModifier, kotlin.jvm.functions.Function1<java.lang.Object?,? extends java.lang.Object?> modifyParentData);
     method public static androidx.wear.compose.foundation.CurvedModifier weight(androidx.wear.compose.foundation.CurvedModifier, @FloatRange(from=0.0, fromInclusive=false) float weight);
   }
 
@@ -426,7 +426,7 @@
     method public int getCenterItemScrollOffset();
     method public androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo getLayoutInfo();
     method public boolean isScrollInProgress();
-    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToItem(int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public boolean canScrollBackward;
     property public boolean canScrollForward;
diff --git a/wear/compose/compose-material/api/1.4.0-beta01.txt b/wear/compose/compose-material/api/1.4.0-beta01.txt
index 2ef61d9..8ff9ff1 100644
--- a/wear/compose/compose-material/api/1.4.0-beta01.txt
+++ b/wear/compose/compose-material/api/1.4.0-beta01.txt
@@ -347,7 +347,7 @@
     method public boolean getRepeatItems();
     method public int getSelectedOption();
     method public boolean isScrollInProgress();
-    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToOption(int index, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void setNumberOfOptions(int);
     property public boolean canScrollBackward;
@@ -582,7 +582,7 @@
     method @Deprecated public int getCenterItemScrollOffset();
     method @Deprecated public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
     method @Deprecated public boolean isScrollInProgress();
-    method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @Deprecated public suspend Object? scrollToItem(int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property @Deprecated public boolean canScrollBackward;
     property @Deprecated public boolean canScrollForward;
diff --git a/wear/compose/compose-material/api/restricted_1.4.0-beta01.txt b/wear/compose/compose-material/api/restricted_1.4.0-beta01.txt
index 2ef61d9..8ff9ff1 100644
--- a/wear/compose/compose-material/api/restricted_1.4.0-beta01.txt
+++ b/wear/compose/compose-material/api/restricted_1.4.0-beta01.txt
@@ -347,7 +347,7 @@
     method public boolean getRepeatItems();
     method public int getSelectedOption();
     method public boolean isScrollInProgress();
-    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? scrollToOption(int index, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void setNumberOfOptions(int);
     property public boolean canScrollBackward;
@@ -582,7 +582,7 @@
     method @Deprecated public int getCenterItemScrollOffset();
     method @Deprecated public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
     method @Deprecated public boolean isScrollInProgress();
-    method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @Deprecated public suspend Object? scrollToItem(int index, optional int scrollOffset, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property @Deprecated public boolean canScrollBackward;
     property @Deprecated public boolean canScrollForward;
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PickerGroup.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PickerGroup.kt
index fbeb706..61a2bcf 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PickerGroup.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/PickerGroup.kt
@@ -16,7 +16,6 @@
 
 package androidx.wear.compose.material
 
-import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
@@ -137,8 +136,8 @@
                             if (pickerSelected && autoCenter) Modifier.autoCenteringTarget()
                             else Modifier
                         )
-                        .focusRequester(focusRequester)
-                        .focusable(),
+                        // Do not need focusable as it's already set in ScalingLazyColumn
+                        .focusRequester(focusRequester),
                     readOnlyLabel = pickerData.readOnlyLabel,
                     flingBehavior = flingBehavior,
                     onSelected = pickerData.onSelected,