Merge "Bump to Kotlin 1.6.21" into androidx-main
diff --git a/buildSrc/remoteBuildCache.gradle b/buildSrc/remoteBuildCache.gradle
index d3965ce..6a5e7ea 100644
--- a/buildSrc/remoteBuildCache.gradle
+++ b/buildSrc/remoteBuildCache.gradle
@@ -1,31 +1,63 @@
-def useRemoteBuildCache = System.getenv("USE_ANDROIDX_REMOTE_BUILD_CACHE") == "true"
+import androidx.build.gradle.gcpbuildcache.GcpBuildCache
+import androidx.build.gradle.gcpbuildcache.GcpBuildCacheServiceFactory
 
-if (useRemoteBuildCache) {
-    gradle.settingsEvaluated { settings ->
-        settings.buildCache {
-            remote(HttpBuildCache) {
-                def osName = System.getProperty("os.name").toLowerCase()
-                switch (osName) {
-                    case { it.contains("mac os x") }:
-                    case { it.contains("darwin") }:
-                    case { it.contains("osx") }:
-                        url = "http://gradle-remote-cache.uplink2.goog:999/cache/"
-                        break
-                    default:
-                        url = "http://gradle-remote-cache.uplink.goog:999/cache/"
-                        break
+buildscript {
+    ext.supportRootFolder = buildscript.sourceFile.getParentFile().getParentFile()
+    apply(from: "repos.gradle")
+    repos.addMavenRepositories(repositories)
+
+    dependencies {
+        classpath("androidx.build.gradle.gcpbuildcache:gcpbuildcache:1.0.0-alpha02")
+    }
+}
+
+def cacheSetting = System.getenv("USE_ANDROIDX_REMOTE_BUILD_CACHE")
+def BUILD_NUMBER = System.getenv("BUILD_NUMBER")
+
+switch (cacheSetting) {
+    case "true":
+    case "uplink": // legacy build cache
+        gradle.settingsEvaluated { settings ->
+            settings.buildCache {
+                remote(HttpBuildCache) {
+                    def osName = System.getProperty("os.name").toLowerCase()
+                    switch (osName) {
+                        case { it.contains("mac os x") }:
+                        case { it.contains("darwin") }:
+                        case { it.contains("osx") }:
+                            url = "http://gradle-remote-cache.uplink2.goog:999/cache/"
+                            break
+                        default:
+                            url = "http://gradle-remote-cache.uplink.goog:999/cache/"
+                            break
+                    }
+                    allowInsecureProtocol = true
+                    push = true
                 }
-                allowInsecureProtocol = true
-                push = true
             }
         }
-    }
-} else {
-    def uplinkLinux = new File("/usr/bin/uplink-helper")
-    def uplinkMac = new File("/usr/local/bin/uplink-helper")
-    if (uplinkLinux.exists() || uplinkMac.exists()) {
-        logger.warn("\u001B[31m\nIt looks like you are a Googler running without remote build "
-                + "cache. Enable it for faster builds, see " +
-                "http://go/androidx-dev#remote-build-cache\u001B[0m\n")
-    }
+        break
+    case "gcp":
+        gradle.settingsEvaluated { settings ->
+            buildCache {
+              registerBuildCacheService(GcpBuildCache, GcpBuildCacheServiceFactory)
+            }
+
+            settings.buildCache {
+                remote(GcpBuildCache) {
+                    projectId = "androidx-ge"
+                    bucketName = "androidx-gradle-build-cache"
+                    push = (BUILD_NUMBER != null && !BUILD_NUMBER.startsWith("P"))
+                }
+            }
+        }
+        break
+    default:
+        def uplinkLinux = new File("/usr/bin/uplink-helper")
+        def uplinkMac = new File("/usr/local/bin/uplink-helper")
+        if (uplinkLinux.exists() || uplinkMac.exists()) {
+            logger.warn("\u001B[31m\nIt looks like you are a Googler running without remote build "
+                    + "cache. Enable it for faster builds, see " +
+                    "http://go/androidx-dev#remote-build-cache\u001B[0m\n")
+        }
 }
diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt
index ec3582e..1f5a59d 100644
--- a/compose/foundation/foundation-layout/api/current.txt
+++ b/compose/foundation/foundation-layout/api/current.txt
@@ -217,6 +217,9 @@
   public static final class WindowInsets.Companion {
   }
 
+  public final class WindowInsetsConnection_androidKt {
+  }
+
   public final class WindowInsetsKt {
     method public static androidx.compose.foundation.layout.WindowInsets WindowInsets(optional int left, optional int top, optional int right, optional int bottom);
     method public static androidx.compose.foundation.layout.WindowInsets WindowInsets(optional float left, optional float top, optional float right, optional float bottom);
diff --git a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
index 2e12685..9e0d3ee 100644
--- a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
@@ -220,6 +220,10 @@
   public static final class WindowInsets.Companion {
   }
 
+  public final class WindowInsetsConnection_androidKt {
+    method @androidx.compose.foundation.layout.ExperimentalLayoutApi public static androidx.compose.ui.Modifier imeNestedScroll(androidx.compose.ui.Modifier);
+  }
+
   public final class WindowInsetsKt {
     method public static androidx.compose.foundation.layout.WindowInsets WindowInsets(optional int left, optional int top, optional int right, optional int bottom);
     method public static androidx.compose.foundation.layout.WindowInsets WindowInsets(optional float left, optional float top, optional float right, optional float bottom);
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index 585f2e0..e1603e2 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -222,6 +222,9 @@
   public static final class WindowInsets.Companion {
   }
 
+  public final class WindowInsetsConnection_androidKt {
+  }
+
   public final class WindowInsetsKt {
     method public static androidx.compose.foundation.layout.WindowInsets WindowInsets(optional int left, optional int top, optional int right, optional int bottom);
     method public static androidx.compose.foundation.layout.WindowInsets WindowInsets(optional float left, optional float top, optional float right, optional float bottom);
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 8498807..6a468dd 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -41,6 +41,7 @@
         implementation(project(":compose:runtime:runtime"))
         implementation("androidx.compose.ui:ui-util:1.0.0")
         implementation("androidx.core:core:1.7.0")
+        implementation("androidx.compose.animation:animation-core:1.1.1")
         implementation(libs.kotlinStdlibCommon)
 
         testImplementation(libs.testRules)
@@ -90,6 +91,7 @@
             androidMain.dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.core:core:1.7.0")
+                implementation("androidx.compose.animation:animation-core:1.1.1")
             }
 
             desktopMain.dependencies {
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/WindowInsetsConnectionSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/WindowInsetsConnectionSample.kt
new file mode 100644
index 0000000..8bd86e3
--- /dev/null
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/WindowInsetsConnectionSample.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 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.compose.foundation.layout.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.imeNestedScroll
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun windowInsetsNestedScrollDemo() {
+    LazyColumn(
+        modifier = Modifier
+            .fillMaxSize() // fill the window
+            .imePadding() // pad out the bottom for the IME
+            .imeNestedScroll(), // scroll IME at the bottom
+        reverseLayout = true // First item is at the bottom
+    ) {
+        // content
+        items(50) {
+            Text("Hello World")
+        }
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/AndroidManifest.xml b/compose/foundation/foundation-layout/src/androidAndroidTest/AndroidManifest.xml
index 2f531ed..9f5c83b 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/AndroidManifest.xml
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/AndroidManifest.xml
@@ -19,5 +19,9 @@
         <activity
             android:name="androidx.compose.foundation.layout.TestActivity"
             android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen" />
+        <activity
+            android:name="androidx.compose.foundation.layout.WindowInsetsActivity"
+            android:windowSoftInputMode="adjustResize"
+            android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen" />
     </application>
 </manifest>
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsActivity.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsActivity.kt
new file mode 100644
index 0000000..41e064f
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsActivity.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019 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.compose.foundation.layout
+
+import android.os.Build
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.annotation.RequiresApi
+import java.util.concurrent.CountDownLatch
+
+class WindowInsetsActivity : ComponentActivity() {
+    val createdLatch = CountDownLatch(1)
+
+    @RequiresApi(Build.VERSION_CODES.R)
+    override fun onCreate(savedInstanceState: Bundle?) {
+        window.setDecorFitsSystemWindows(false)
+        super.onCreate(savedInstanceState)
+        createdLatch.countDown()
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsControllerTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsControllerTest.kt
new file mode 100644
index 0000000..410e493
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsControllerTest.kt
@@ -0,0 +1,689 @@
+/*
+ * Copyright 2022 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.compose.foundation.layout
+
+import android.graphics.Insets
+import android.os.Build
+import android.os.SystemClock
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MonotonicFrameClock
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Velocity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlin.math.abs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalLayoutApi::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+class WindowInsetsControllerTest {
+    @get:Rule
+    val rule = createAndroidComposeRule<WindowInsetsActivity>()
+
+    val testTag = "TestTag"
+
+    /**
+     * The size of the inset when shown.
+     */
+    private var shownSize = 0
+
+    /**
+     * This is the fling velocity that will move enough so that a spring will show at least
+     * 1 pixel of movement. This should be considered a small fling.
+     */
+    private val FlingToSpring1Pixel = 300f
+
+    // ========================================================
+    // The specific insets are extracted here so that different
+    // insets can be tested locally. IME works for R+, but
+    // status bars only work on S+. The following allows
+    // extracting out the insets particulars so that tests
+    // work with different insets types.
+    // ========================================================
+
+    /**
+     * The android WindowInsets type.
+     */
+    private val insetType = android.view.WindowInsets.Type.statusBars()
+    private val insetSide = WindowInsetsSides.Top
+
+    private val windowInsets: AndroidWindowInsets
+        @Composable
+        get() = WindowInsetsHolder.current().ime
+
+    private val WindowInsets.value: Int
+        get() = getBottom(Density(1f))
+
+    private val Insets.value: Int
+        get() = bottom
+
+    private val reverseLazyColumn = true
+
+    private fun TouchInjectionScope.swipeAwayFromInset() {
+        swipeUp()
+    }
+
+    private fun TouchInjectionScope.swipeTowardInset() {
+        swipeDown()
+    }
+
+    /**
+     * A motion in this direction moves away from the insets
+     */
+    val directionMultiplier: Float = -1f
+
+    private var shownAtStart = false
+
+    @Before
+    fun setup() {
+        rule.activity.createdLatch.await(1, TimeUnit.SECONDS)
+        rule.runOnUiThread {
+            val view = rule.activity.window.decorView
+            shownAtStart = view.rootWindowInsets.isVisible(insetType)
+        }
+    }
+    @After
+    fun teardown() {
+        rule.runOnUiThread {
+            val view = rule.activity.window.decorView
+            if (shownAtStart) {
+                view.windowInsetsController?.show(insetType)
+            } else {
+                view.windowInsetsController?.hide(insetType)
+            }
+        }
+    }
+
+    /**
+     * Scrolling away from the inset with the inset hidden should show it.
+     */
+    @Test
+    fun canScrollToShow() {
+        if (!initializeDeviceWithInsetsHidden()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+        lateinit var coordinates: LayoutCoordinates
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+                .onPlaced { coordinates = it }
+            )
+        }
+
+        val sizeBefore = coordinates.size
+
+        rule.runOnUiThread {
+            // The first scroll triggers the animation controller to be requested
+            val consumed = connection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(3f, directionMultiplier),
+                source = NestedScrollSource.Drag
+            )
+            assertThat(consumed).isEqualTo(Offset(0f, directionMultiplier))
+        }
+        // We don't know when the animation controller request will be fulfilled, so loop
+        // until we're sure
+        val startTime = SystemClock.uptimeMillis()
+        do {
+            assertThat(SystemClock.uptimeMillis()).isLessThan(startTime + 1000)
+            val size = rule.runOnUiThread {
+                connection.onPostScroll(
+                    consumed = Offset.Zero,
+                    available = Offset(3f, directionMultiplier * 5f),
+                    source = NestedScrollSource.Drag
+                )
+                coordinates.size
+            }
+        } while (size == sizeBefore)
+
+        rule.runOnIdle {
+            val sizeAfter = coordinates.size
+            assertThat(sizeBefore.height).isGreaterThan(sizeAfter.height)
+        }
+    }
+
+    /**
+     * Scrolling toward the inset with the inset shown should hide it.
+     */
+    @Test
+    fun canScrollToHide() {
+        if (!initializeDeviceWithInsetsShown()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+        lateinit var coordinates: LayoutCoordinates
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+                .onPlaced { coordinates = it }
+            )
+        }
+
+        val sizeBefore = coordinates.size
+
+        rule.runOnUiThread {
+            // The first scroll triggers the animation controller to be requested
+            val consumed = connection.onPreScroll(
+                available = Offset(3f, -directionMultiplier),
+                source = NestedScrollSource.Drag
+            )
+            assertThat(consumed).isEqualTo(Offset(0f, -directionMultiplier))
+        }
+        // We don't know when the animation controller request will be fulfilled, so loop
+        // until we're sure
+        val startTime = SystemClock.uptimeMillis()
+        do {
+            assertThat(SystemClock.uptimeMillis()).isLessThan(startTime + 1000)
+            val size = rule.runOnUiThread {
+                connection.onPreScroll(
+                    available = Offset(3f, directionMultiplier * -5f),
+                    source = NestedScrollSource.Drag
+                )
+                coordinates.size
+            }
+        } while (size == sizeBefore)
+
+        rule.runOnIdle {
+            val sizeAfter = coordinates.size
+            assertThat(sizeBefore.height).isLessThan(sizeAfter.height)
+        }
+    }
+
+    /**
+     * Flinging away from an inset should show it.
+     */
+    @Test
+    fun canFlingToShow() {
+        if (!initializeDeviceWithInsetsHidden()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+        lateinit var coordinates: LayoutCoordinates
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+                .onPlaced { coordinates = it }
+            )
+        }
+
+        val sizeBefore = coordinates.size
+
+        runBlockingOnUiThread {
+            val consumed = connection.onPostFling(
+                consumed = Velocity.Zero,
+                available = Velocity(3f, directionMultiplier * 5000f)
+            )
+            assertThat(consumed.x).isEqualTo(0f)
+            assertThat(abs(consumed.y)).isLessThan(5000f)
+        }
+
+        rule.runOnIdle {
+            val sizeAfter = coordinates.size
+            assertThat(sizeBefore.height).isGreaterThan(sizeAfter.height)
+        }
+    }
+
+    /**
+     * Flinging toward an inset should hide it.
+     */
+    @Test
+    fun canFlingToHide() {
+        if (!initializeDeviceWithInsetsShown()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+        lateinit var coordinates: LayoutCoordinates
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+                .onPlaced { coordinates = it }
+            )
+        }
+
+        val sizeBefore = coordinates.size
+
+        runBlockingOnUiThread {
+            val consumed = connection.onPreFling(
+                available = Velocity(3f, -directionMultiplier * 5000f)
+            )
+            assertThat(consumed.x).isEqualTo(0f)
+            assertThat(abs(consumed.y)).isLessThan(5000f)
+        }
+
+        rule.runOnIdle {
+            val sizeAfter = coordinates.size
+            assertThat(sizeBefore.height).isLessThan(sizeAfter.height)
+        }
+    }
+
+    /**
+     * A small fling should use an animation to bounce back to hiding the inset
+     */
+    @Test
+    fun smallFlingSpringsBackToHide() {
+        if (!initializeDeviceWithInsetsHidden()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+
+        var maxVisible = 0
+        var isVisible = false
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            maxVisible = maxOf(maxVisible, windowInsets.value)
+            isVisible = windowInsets.isVisible
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+            )
+        }
+
+        runBlockingOnUiThread {
+            connection.onPostFling(
+                consumed = Velocity.Zero,
+                available = Velocity(0f, directionMultiplier * FlingToSpring1Pixel)
+            )
+            assertThat(maxVisible).isGreaterThan(0)
+        }
+
+        rule.runOnIdle {
+            assertThat(isVisible).isFalse()
+        }
+    }
+
+    /**
+     * A small fling should use an animation to bounce back to showing the inset
+     */
+    @Test
+    fun smallFlingSpringsBackToShow() {
+        if (!initializeDeviceWithInsetsShown()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+
+        var minVisible = 0
+        var isVisible = false
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            minVisible = minOf(minVisible, windowInsets.value)
+            isVisible = windowInsets.isVisible
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+            )
+        }
+
+        runBlockingOnUiThread {
+            connection.onPostFling(
+                consumed = Velocity.Zero,
+                available = Velocity(0f, directionMultiplier * FlingToSpring1Pixel)
+            )
+            assertThat(minVisible).isLessThan(shownSize)
+        }
+
+        rule.runOnIdle {
+            assertThat(isVisible).isTrue()
+        }
+    }
+
+    /**
+     * A fling past the middle should animate to fully showing the inset
+     */
+    @Test
+    fun flingPastMiddleSpringsToShow() {
+        if (!initializeDeviceWithInsetsHidden()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+
+        var isVisible = false
+        var insetsSize = 0
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            isVisible = windowInsets.isVisible
+            insetsSize = windowInsets.value
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+            )
+        }
+
+        // We don't know when the animation controller request will be fulfilled, so loop
+        // we scroll
+        val startTime = SystemClock.uptimeMillis()
+        do {
+            assertThat(SystemClock.uptimeMillis()).isLessThan(startTime + 1000)
+            rule.runOnIdle {
+                connection.onPostScroll(
+                    consumed = Offset.Zero,
+                    available = Offset(0f, directionMultiplier),
+                    source = NestedScrollSource.Drag
+                )
+            }
+        } while (!isVisible)
+
+        // now scroll to just short of half way
+        rule.runOnIdle {
+            val sizeDifference = shownSize / 2f - 1f - insetsSize
+            connection.onPostScroll(
+                consumed = Offset.Zero,
+                available = Offset(0f, directionMultiplier * sizeDifference),
+                source = NestedScrollSource.Drag
+            )
+        }
+
+        rule.waitForIdle()
+
+        runBlockingOnUiThread {
+            connection.onPostFling(
+                consumed = Velocity.Zero,
+                available = Velocity(0f, directionMultiplier * FlingToSpring1Pixel)
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(isVisible).isTrue()
+        }
+    }
+
+    /**
+     * A fling that moves more than half way toward hiding should animate to fully hiding the inset
+     */
+    @Test
+    fun flingPastMiddleSpringsToHide() {
+        if (!initializeDeviceWithInsetsShown()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+
+        var isVisible = false
+        var insetsSize = 0
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            isVisible = windowInsets.isVisible
+            insetsSize = windowInsets.value
+            Box(Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+            )
+        }
+
+        // We don't know when the animation controller request will be fulfilled, so loop
+        // we scroll
+        val startTime = SystemClock.uptimeMillis()
+        do {
+            assertThat(SystemClock.uptimeMillis()).isLessThan(startTime + 1000)
+            rule.runOnIdle {
+                connection.onPreScroll(
+                    available = Offset(0f, directionMultiplier * -1f),
+                    source = NestedScrollSource.Drag
+                )
+            }
+        } while (insetsSize != shownSize)
+
+        // now scroll to just short of half way
+        rule.runOnIdle {
+            val sizeDifference = shownSize / 2f + 1f - insetsSize
+            connection.onPreScroll(
+                available = Offset(0f, directionMultiplier * sizeDifference),
+                source = NestedScrollSource.Drag
+            )
+        }
+
+        runBlockingOnUiThread {
+            // should fling at least one pixel past the middle
+            connection.onPreFling(
+                available = Velocity(0f, directionMultiplier * -FlingToSpring1Pixel)
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(isVisible).isFalse()
+        }
+    }
+
+    /**
+     * The insets shouldn't get in the way of normal scrolling on the normal content.
+     */
+    @Test
+    fun allowsContentScroll() {
+        if (!initializeDeviceWithInsetsHidden()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+        lateinit var coordinates: LayoutCoordinates
+        val lazyListState = LazyListState()
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            val boxSize = with(LocalDensity.current) { 100.toDp() }
+            LazyColumn(
+                reverseLayout = reverseLazyColumn,
+                state = lazyListState,
+                modifier = Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+                .testTag(testTag)
+                .onPlaced { coordinates = it }
+            ) {
+                items(1000) {
+                    Box(Modifier.size(boxSize))
+                }
+            }
+        }
+
+        val sizeBefore = coordinates.size
+
+        rule.onNodeWithTag(testTag)
+            .performTouchInput {
+                swipeTowardInset()
+            }
+
+        rule.runOnIdle {
+            assertThat(coordinates.size.height).isEqualTo(sizeBefore.height)
+            // The drag should result in 1 item scrolled, but the fling should give more than 1
+            assertThat(lazyListState.firstVisibleItemIndex).isGreaterThan(2)
+        }
+
+        val firstVisibleIndex = lazyListState.firstVisibleItemIndex
+
+        rule.onNodeWithTag(testTag)
+            .performTouchInput {
+                swipeAwayFromInset()
+            }
+
+        rule.runOnIdle {
+            assertThat(coordinates.size.height).isEqualTo(sizeBefore.height)
+            // The drag should result in 1 item scrolled, but the fling should give more than 1
+            assertThat(lazyListState.firstVisibleItemIndex).isLessThan(firstVisibleIndex - 2)
+        }
+    }
+
+    /**
+     * When flinging more than the inset, it should animate the insets closed and then fling
+     * the content.
+     */
+    @Test
+    fun flingRemainderMovesContent() {
+        if (!initializeDeviceWithInsetsShown()) {
+            return // The insets don't exist on this device
+        }
+        lateinit var connection: NestedScrollConnection
+        lateinit var coordinates: LayoutCoordinates
+        val lazyListState = LazyListState()
+
+        rule.setContent {
+            connection =
+                rememberWindowInsetsConnection(windowInsets, insetSide)
+            val boxSize = with(LocalDensity.current) { 100.toDp() }
+            LazyColumn(
+                reverseLayout = reverseLazyColumn,
+                state = lazyListState,
+                modifier = Modifier
+                .fillMaxSize()
+                .windowInsetsPadding(windowInsets)
+                .nestedScroll(connection)
+                .testTag(testTag)
+                .onPlaced { coordinates = it }
+            ) {
+                items(1000) {
+                    Box(Modifier.size(boxSize))
+                }
+            }
+        }
+
+        val sizeBefore = coordinates.size
+
+        rule.onNodeWithTag(testTag)
+            .performTouchInput {
+                swipeTowardInset()
+            }
+
+        rule.runOnIdle {
+            assertThat(coordinates.size.height).isGreaterThan(sizeBefore.height)
+            // The fling should get at least one item moved
+            assertThat(lazyListState.firstVisibleItemIndex).isGreaterThan(0)
+        }
+    }
+
+    private fun initializeDeviceWithInsetsShown(): Boolean {
+        val view = rule.activity.window.decorView
+
+        rule.runOnUiThread {
+            view.windowInsetsController?.show(insetType)
+        }
+
+        return rule.runOnIdle {
+            val windowInsets = view.rootWindowInsets
+            val insets = windowInsets.getInsets(insetType)
+            shownSize = insets.value
+            windowInsets.isVisible(insetType) && insets.value != 0
+        }
+    }
+
+    private fun initializeDeviceWithInsetsHidden(): Boolean {
+        if (!initializeDeviceWithInsetsShown()) {
+            return false
+        }
+        val view = rule.activity.window.decorView
+        rule.runOnUiThread {
+            view.windowInsetsController?.hide(insetType)
+        }
+        return rule.runOnUiThread {
+            val windowInsets = view.rootWindowInsets
+            !windowInsets.isVisible(insetType)
+        }
+    }
+
+    @OptIn(DelicateCoroutinesApi::class)
+    private fun runBlockingOnUiThread(block: suspend CoroutineScope.() -> Unit) {
+        val latch = CountDownLatch(1)
+        val clock = MyTestFrameClock()
+        GlobalScope.launch(Dispatchers.Main) {
+            val context = coroutineContext + clock
+            withContext(context, block)
+            latch.countDown()
+        }
+        var frameTimeNanos = 0L
+        while (latch.count > 0) {
+            frameTimeNanos += 4_000_000L // 4ms
+            clock.trySendFrame(frameTimeNanos)
+            rule.waitForIdle()
+        }
+    }
+
+    private class MyTestFrameClock : MonotonicFrameClock {
+        private val frameCh = Channel<Long>(1)
+
+        fun trySendFrame(frameTimeNanos: Long) {
+            frameCh.trySend(frameTimeNanos)
+        }
+
+        override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+            return onFrame(frameCh.receive())
+        }
+    }
+}
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsIgnoringVisibilityTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsIgnoringVisibilityTest.kt
index 671d982..7481422 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsIgnoringVisibilityTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/WindowInsetsIgnoringVisibilityTest.kt
@@ -404,4 +404,4 @@
             return insets.toWindowInsets()!!
         }
     }
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt
index 9d59fb7..d0b595673 100644
--- a/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt
+++ b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsets.android.kt
@@ -18,13 +18,14 @@
 
 import androidx.core.graphics.Insets as AndroidXInsets
 import android.os.Build
+import android.os.SystemClock
 import android.view.View
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -37,6 +38,8 @@
 import androidx.core.view.WindowInsetsCompat
 import java.util.WeakHashMap
 import androidx.compose.ui.R
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
 import org.jetbrains.annotations.TestOnly
 
 internal fun AndroidXInsets.toInsetsValues(): InsetsValues =
@@ -46,6 +49,62 @@
     ValueInsets(insets.toInsetsValues(), name)
 
 /**
+ * [WindowInsets] provided by the Android framework. These can be used in
+ * [rememberWindowInsetsConnection] to control the insets.
+ */
+@Stable
+internal class AndroidWindowInsets(
+    internal val type: Int,
+    private val name: String
+) : WindowInsets {
+    internal var insets by mutableStateOf(AndroidXInsets.NONE)
+
+    /**
+     * Returns whether the insets are visible, irrespective of whether or not they
+     * intersect with the Window.
+     */
+    var isVisible by mutableStateOf(true)
+        private set
+
+    override fun getLeft(density: Density, layoutDirection: LayoutDirection): Int {
+        return insets.left
+    }
+
+    override fun getTop(density: Density): Int {
+        return insets.top
+    }
+
+    override fun getRight(density: Density, layoutDirection: LayoutDirection): Int {
+        return insets.right
+    }
+
+    override fun getBottom(density: Density): Int {
+        return insets.bottom
+    }
+
+    @OptIn(ExperimentalLayoutApi::class)
+    internal fun update(windowInsetsCompat: WindowInsetsCompat) {
+        insets = windowInsetsCompat.getInsets(type)
+        isVisible = windowInsetsCompat.isVisible(type)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is AndroidWindowInsets) return false
+
+        return type == other.type
+    }
+
+    override fun hashCode(): Int {
+        return type
+    }
+
+    override fun toString(): String {
+        return "$name(${insets.left}, ${insets.top}, ${insets.right}, ${insets.bottom})"
+    }
+}
+
+/**
  * Indicates whether access to [WindowInsets] within the [content][ComposeView.setContent]
  * should consume the Android  [android.view.WindowInsets]. The default value is `true`, meaning
  * that access to [WindowInsets.Companion] will consume the Android WindowInsets.
@@ -251,7 +310,7 @@
     @ExperimentalLayoutApi
     @Composable
     @NonRestartableComposable
-    get() = WindowInsetsHolder.current().isCaptionBarVisible
+    get() = WindowInsetsHolder.current().captionBar.isVisible
 
 /**
  * `true` when the [soft keyboard][ime] is being displayed, irrespective of
@@ -263,7 +322,7 @@
     @ExperimentalLayoutApi
     @Composable
     @NonRestartableComposable
-    get() = WindowInsetsHolder.current().isImeVisible
+    get() = WindowInsetsHolder.current().ime.isVisible
 
 /**
  * `true` when the [statusBars] are being displayed, irrespective of
@@ -275,7 +334,7 @@
     @ExperimentalLayoutApi
     @Composable
     @NonRestartableComposable
-    get() = WindowInsetsHolder.current().areStatusBarsVisible
+    get() = WindowInsetsHolder.current().statusBars.isVisible
 
 /**
  * `true` when the [navigationBars] are being displayed, irrespective of
@@ -287,7 +346,7 @@
     @ExperimentalLayoutApi
     @Composable
     @NonRestartableComposable
-    get() = WindowInsetsHolder.current().areNavigationBarsVisible
+    get() = WindowInsetsHolder.current().navigationBars.isVisible
 
 /**
  * `true` when the [systemBars] are being displayed, irrespective of
@@ -299,7 +358,7 @@
     @ExperimentalLayoutApi
     @Composable
     @NonRestartableComposable
-    get() = WindowInsetsHolder.current().areSystemBarsVisible
+    get() = WindowInsetsHolder.current().systemBars.isVisible
 /**
  * `true` when the [tappableElement] is being displayed, irrespective of
  * whether they intersects with the Window.
@@ -310,32 +369,33 @@
     @ExperimentalLayoutApi
     @Composable
     @NonRestartableComposable
-    get() = WindowInsetsHolder.current().isTappableElementVisible
+    get() = WindowInsetsHolder.current().tappableElement.isVisible
 
 /**
  * The insets for various values in the current window.
  */
+@OptIn(ExperimentalLayoutApi::class)
 internal class WindowInsetsHolder private constructor(insets: WindowInsetsCompat?) {
     val captionBar =
-        valueInsets(insets, WindowInsetsCompat.Type.captionBar(), "captionBar")
+        systemInsets(insets, WindowInsetsCompat.Type.captionBar(), "captionBar")
     val displayCutout =
-        valueInsets(insets, WindowInsetsCompat.Type.displayCutout(), "displayCutout")
-    val ime = valueInsets(insets, WindowInsetsCompat.Type.ime(), "ime")
-    val mandatorySystemGestures = valueInsets(
+        systemInsets(insets, WindowInsetsCompat.Type.displayCutout(), "displayCutout")
+    val ime = systemInsets(insets, WindowInsetsCompat.Type.ime(), "ime")
+    val mandatorySystemGestures = systemInsets(
         insets,
         WindowInsetsCompat.Type.mandatorySystemGestures(),
         "mandatorySystemGestures"
     )
     val navigationBars =
-        valueInsets(insets, WindowInsetsCompat.Type.navigationBars(), "navigationBars")
+        systemInsets(insets, WindowInsetsCompat.Type.navigationBars(), "navigationBars")
     val statusBars =
-        valueInsets(insets, WindowInsetsCompat.Type.statusBars(), "statusBars")
+        systemInsets(insets, WindowInsetsCompat.Type.statusBars(), "statusBars")
     val systemBars =
-        valueInsets(insets, WindowInsetsCompat.Type.systemBars(), "systemBars")
+        systemInsets(insets, WindowInsetsCompat.Type.systemBars(), "systemBars")
     val systemGestures =
-        valueInsets(insets, WindowInsetsCompat.Type.systemGestures(), "systemGestures")
+        systemInsets(insets, WindowInsetsCompat.Type.systemGestures(), "systemGestures")
     val tappableElement =
-        valueInsets(insets, WindowInsetsCompat.Type.tappableElement(), "tappableElement")
+        systemInsets(insets, WindowInsetsCompat.Type.tappableElement(), "tappableElement")
     val waterfall =
         ValueInsets(insets?.displayCutout?.waterfallInsets ?: AndroidXInsets.NONE, "waterfall")
     val safeDrawing =
@@ -368,19 +428,6 @@
         "tappableElementIgnoringVisibility"
     )
 
-    var isCaptionBarVisible by mutableStateIsVisible(insets, WindowInsetsCompat.Type.captionBar())
-    var isImeVisible by mutableStateIsVisible(insets, WindowInsetsCompat.Type.ime())
-    var areNavigationBarsVisible by mutableStateIsVisible(
-        insets,
-        WindowInsetsCompat.Type.navigationBars()
-    )
-    var areStatusBarsVisible by mutableStateIsVisible(insets, WindowInsetsCompat.Type.statusBars())
-    var areSystemBarsVisible by mutableStateIsVisible(insets, WindowInsetsCompat.Type.systemBars())
-    var isTappableElementVisible by mutableStateIsVisible(
-        insets,
-        WindowInsetsCompat.Type.tappableElement()
-    )
-
     /**
      * `true` unless the `ComposeView` [ComposeView.consumeWindowInsets] is set to `false`.
      */
@@ -431,63 +478,48 @@
      * Updates the WindowInsets values and notifies changes.
      */
     fun update(windowInsets: WindowInsetsCompat) {
-        Snapshot.withMutableSnapshot {
-            val insets = if (testInsets) {
-                // WindowInsetsCompat erases insets that aren't part of the device.
-                // For example, if there is no navigation bar because of hardware keys,
-                // the bottom navigation bar will be removed. By using the constructor
-                // that doesn't accept a View, it doesn't remove the insets that aren't
-                // possible. This is important for testing on arbitrary hardware.
-                WindowInsetsCompat.toWindowInsetsCompat(windowInsets.toWindowInsets()!!)
-            } else {
-                windowInsets
-            }
-            captionBar.value =
-                insets.getInsets(WindowInsetsCompat.Type.captionBar()).toInsetsValues()
-            captionBarIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
-                WindowInsetsCompat.Type.captionBar()
-            ).toInsetsValues()
-            isCaptionBarVisible = insets.isVisible(WindowInsetsCompat.Type.captionBar())
-            ime.value =
-                insets.getInsets(WindowInsetsCompat.Type.ime()).toInsetsValues()
-            isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
-            displayCutout.value =
-                insets.getInsets(WindowInsetsCompat.Type.displayCutout()).toInsetsValues()
-            navigationBars.value =
-                insets.getInsets(WindowInsetsCompat.Type.navigationBars()).toInsetsValues()
-            navigationBarsIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
-                WindowInsetsCompat.Type.navigationBars()
-            ).toInsetsValues()
-            areNavigationBarsVisible = insets.isVisible(WindowInsetsCompat.Type.navigationBars())
-            statusBars.value =
-                insets.getInsets(WindowInsetsCompat.Type.statusBars()).toInsetsValues()
-            statusBarsIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
-                WindowInsetsCompat.Type.statusBars()
-            ).toInsetsValues()
-            areStatusBarsVisible = insets.isVisible(WindowInsetsCompat.Type.statusBars())
-            systemBars.value =
-                insets.getInsets(WindowInsetsCompat.Type.systemBars()).toInsetsValues()
-            systemBarsIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
-                WindowInsetsCompat.Type.systemBars()
-            ).toInsetsValues()
-            areSystemBarsVisible = insets.isVisible(WindowInsetsCompat.Type.systemBars())
-            systemGestures.value =
-                insets.getInsets(WindowInsetsCompat.Type.systemGestures()).toInsetsValues()
-            tappableElement.value =
-                insets.getInsets(WindowInsetsCompat.Type.tappableElement()).toInsetsValues()
-            tappableElementIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
-                WindowInsetsCompat.Type.tappableElement()
-            ).toInsetsValues()
-            isTappableElementVisible = insets.isVisible(WindowInsetsCompat.Type.tappableElement())
-            mandatorySystemGestures.value =
-                insets.getInsets(WindowInsetsCompat.Type.mandatorySystemGestures()).toInsetsValues()
-
-            val cutout = insets.displayCutout
-            if (cutout != null) {
-                val waterfallInsets = cutout.waterfallInsets
-                waterfall.value = waterfallInsets.toInsetsValues()
-            }
+        val insets = if (testInsets) {
+            // WindowInsetsCompat erases insets that aren't part of the device.
+            // For example, if there is no navigation bar because of hardware keys,
+            // the bottom navigation bar will be removed. By using the constructor
+            // that doesn't accept a View, it doesn't remove the insets that aren't
+            // possible. This is important for testing on arbitrary hardware.
+            WindowInsetsCompat.toWindowInsetsCompat(windowInsets.toWindowInsets()!!)
+        } else {
+            windowInsets
         }
+        captionBar.update(insets)
+        ime.update(insets)
+        displayCutout.update(insets)
+        navigationBars.update(insets)
+        statusBars.update(insets)
+        systemBars.update(insets)
+        systemGestures.update(insets)
+        tappableElement.update(insets)
+        mandatorySystemGestures.update(insets)
+
+        captionBarIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
+            WindowInsetsCompat.Type.captionBar()
+        ).toInsetsValues()
+        navigationBarsIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
+            WindowInsetsCompat.Type.navigationBars()
+        ).toInsetsValues()
+        statusBarsIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
+            WindowInsetsCompat.Type.statusBars()
+        ).toInsetsValues()
+        systemBarsIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
+            WindowInsetsCompat.Type.systemBars()
+        ).toInsetsValues()
+        tappableElementIgnoringVisibility.value = insets.getInsetsIgnoringVisibility(
+            WindowInsetsCompat.Type.tappableElement()
+        ).toInsetsValues()
+
+        val cutout = insets.displayCutout
+        if (cutout != null) {
+            val waterfallInsets = cutout.waterfallInsets
+            waterfall.value = waterfallInsets.toInsetsValues()
+        }
+        Snapshot.sendApplyNotifications()
     }
 
     companion object {
@@ -546,14 +578,11 @@
         /**
          * Creates a [ValueInsets] using the value from [windowInsets] if it isn't `null`
          */
-        private fun valueInsets(
+        private fun systemInsets(
             windowInsets: WindowInsetsCompat?,
             type: Int,
             name: String
-        ): ValueInsets {
-            val initial = windowInsets?.getInsets(type) ?: AndroidXInsets.NONE
-            return ValueInsets(initial, name)
-        }
+        ) = AndroidWindowInsets(type, name).apply { windowInsets?.let { update(it) } }
 
         /**
          * Creates a [ValueInsets] using the "ignoring visibility" value from [windowInsets]
@@ -567,18 +596,6 @@
             val initial = windowInsets?.getInsetsIgnoringVisibility(type) ?: AndroidXInsets.NONE
             return ValueInsets(initial, name)
         }
-
-        /**
-         * Creates a [ValueInsets] using the "ignoring visibility" value from [windowInsets]
-         * if it isn't `null`
-         */
-        private fun mutableStateIsVisible(
-            windowInsets: WindowInsetsCompat?,
-            type: Int
-        ): MutableState<Boolean> {
-            val initial = windowInsets?.isVisible(type) ?: true
-            return mutableStateOf(initial)
-        }
     }
 }
 
@@ -598,17 +615,78 @@
 private class InsetsListener(
     val composeInsets: WindowInsetsHolder,
 ) : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP), OnApplyWindowInsetsListener {
+    /**
+     * When [android.view.WindowInsetsController.controlWindowInsetsAnimation] is called,
+     * the [onApplyWindowInsets] is called after [onPrepare] with the target size. We
+     * don't want to report the target size, we want to always report the current size,
+     * so we must ignore those calls. However, the animation may be canceled before it
+     * progresses. On R, it won't make any callbacks, so we have to figure out whether
+     * the [onApplyWindowInsets] is from a canceled animation or if it is from the
+     * controlled animation. We just have to guess that if we don't receive an [onStart]
+     * before a certain time that the animation has been canceled, and to treat the
+     * [onApplyWindowInsets] as a real call. [prepareGiveUpTime] has the time that we
+     * give up waiting for the [onStart] or [onEnd].
+     */
+    var prepareGiveUpTime = 0L
+
+    /**
+     * `true` if the [onStart] has been called, so we know that we're part of an animation
+     * and [onApplyWindowInsets] calls should be ignored.
+     */
+    var started = false
+
+    override fun onPrepare(animation: WindowInsetsAnimationCompat) {
+        prepareGiveUpTime = SystemClock.uptimeMillis() + AnimationCanceledMillis
+        super.onPrepare(animation)
+    }
+
+    override fun onStart(
+        animation: WindowInsetsAnimationCompat,
+        bounds: WindowInsetsAnimationCompat.BoundsCompat
+    ): WindowInsetsAnimationCompat.BoundsCompat {
+        started = true
+        return super.onStart(animation, bounds)
+    }
 
     override fun onProgress(
         insets: WindowInsetsCompat,
         runningAnimations: MutableList<WindowInsetsAnimationCompat>
     ): WindowInsetsCompat {
+        prepareGiveUpTime = 0L
         composeInsets.update(insets)
         return if (composeInsets.consumes) WindowInsetsCompat.CONSUMED else insets
     }
 
+    override fun onEnd(animation: WindowInsetsAnimationCompat) {
+        started = false
+        prepareGiveUpTime = 0L
+        super.onEnd(animation)
+    }
+
     override fun onApplyWindowInsets(view: View, insets: WindowInsetsCompat): WindowInsetsCompat {
+        val prepareGiveUpTime = prepareGiveUpTime
+        this.prepareGiveUpTime = 0L
+
+        // There may be no callback on R if the animation is canceled after onPrepare(),
+        // so we won't know if the onPrepare() was canceled or if the
+        // So we must allow onApplyWindowInsets() to run if it isn't directly after the
+        // onPrepare().
+        val preparing = prepareGiveUpTime != 0L &&
+            (Build.VERSION.SDK_INT > Build.VERSION_CODES.R ||
+                prepareGiveUpTime > SystemClock.uptimeMillis())
+        if (started || preparing) {
+            // Just ignore this one. It came from the onPrepare.
+            return insets
+        }
         composeInsets.update(insets)
         return if (composeInsets.consumes) WindowInsetsCompat.CONSUMED else insets
     }
+
+    companion object {
+        // If an [onApplyWindowInsets] is received this number of milliseconds after
+        // [onPrepare] and the animation hasn't started, then it is assumed that the
+        // animation was canceled before starting. On R and earlier, we don't get any
+        // signal about cancellation.
+        const val AnimationCanceledMillis = 100L
+    }
 }
diff --git a/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsetsConnection.android.kt b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsetsConnection.android.kt
new file mode 100644
index 0000000..e406da8
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/androidMain/kotlin/androidx/compose/foundation/layout/WindowInsetsConnection.android.kt
@@ -0,0 +1,708 @@
+/*
+ * Copyright 2022 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.compose.foundation.layout
+
+import android.graphics.Insets
+import android.os.Build
+import android.os.CancellationSignal
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.WindowInsetsAnimationControlListener
+import android.view.WindowInsetsAnimationController
+import androidx.annotation.RequiresApi
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.FloatDecayAnimationSpec
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.Velocity
+import kotlin.math.roundToInt
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.util.packFloats
+import androidx.compose.ui.util.unpackFloat1
+import androidx.compose.ui.util.unpackFloat2
+import kotlin.math.abs
+import kotlin.math.exp
+import kotlin.math.ln
+import kotlin.math.sign
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Controls the soft keyboard as a nested scrolling on Android [R][Build.VERSION_CODES.R]
+ * and later. This allows the user to drag the soft keyboard up and down.
+ *
+ * After scrolling, the IME will animate either to the fully shown or fully hidden position,
+ * depending on the position and fling.
+ *
+ * @sample androidx.compose.foundation.layout.samples.windowInsetsNestedScrollDemo
+ */
+@ExperimentalLayoutApi
+fun Modifier.imeNestedScroll(): Modifier {
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+        return this
+    }
+    return composed(
+        debugInspectorInfo {
+            name = "imeNestedScroll"
+        }
+    ) {
+        val nestedScrollConnection = rememberWindowInsetsConnection(
+            WindowInsetsHolder.current().ime,
+            WindowInsetsSides.Bottom
+        )
+        nestedScroll(nestedScrollConnection)
+    }
+}
+
+/**
+ * Returns a [NestedScrollConnection] that can be used with [WindowInsets] on Android
+ * [R][Build.VERSION_CODES.R] and later.
+ *
+ * The [NestedScrollConnection] can be used when a developer wants to control a [WindowInsets],
+ * either directly animating it or allowing the user to manually manipulate it. User interactions
+ * will result in the [WindowInsets] animating either hidden or shown, depending on its
+ * current position and the fling velocity received in [NestedScrollConnection.onPreFling] and
+ * [NestedScrollConnection.onPostFling].
+ *
+ * @param windowInsets The insets to be changed by the scroll effect
+ * @param side The side of the [windowInsets] that is to be affected. Can only be one of
+ * [WindowInsetsSides.Left], [WindowInsetsSides.Top], [WindowInsetsSides.Right],
+ * [WindowInsetsSides.Bottom], [WindowInsetsSides.Start], [WindowInsetsSides.End].
+ */
+@ExperimentalLayoutApi
+@Composable
+internal fun rememberWindowInsetsConnection(
+    windowInsets: AndroidWindowInsets,
+    side: WindowInsetsSides
+): NestedScrollConnection {
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+        return DoNothingNestedScrollConnection
+    }
+    val layoutDirection = LocalLayoutDirection.current
+    val sideCalculator = SideCalculator.chooseCalculator(side, layoutDirection)
+    val view = LocalView.current
+    val density = LocalDensity.current
+    val connection = remember(windowInsets, view, sideCalculator, density) {
+        WindowInsetsNestedScrollConnection(windowInsets, view, sideCalculator, density)
+    }
+    DisposableEffect(connection) {
+        onDispose {
+            connection.dispose()
+        }
+    }
+    return connection
+}
+
+/**
+ * A [NestedScrollConnection] that does nothing, for versions before R.
+ */
+private object DoNothingNestedScrollConnection : NestedScrollConnection
+
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalLayoutApi::class)
+@RequiresApi(Build.VERSION_CODES.R)
+private class WindowInsetsNestedScrollConnection(
+    val windowInsets: AndroidWindowInsets,
+    val view: View,
+    val sideCalculator: SideCalculator,
+    val density: Density
+) : NestedScrollConnection,
+    WindowInsetsAnimationControlListener {
+
+    /**
+     * The [WindowInsetsAnimationController] is only available once the insets are starting
+     * to be manipulated. This is used to set the current insets position.
+     */
+    private var animationController: WindowInsetsAnimationController? = null
+
+    /**
+     * `true` when we've requested a [WindowInsetsAnimationController] so that we don't
+     * ask for one when we've already asked for one. This should be `false` until we've
+     * made a request or when we've cleared [animationController] after it is finished.
+     */
+    private var isControllerRequested = false
+
+    /**
+     * We never need to cancel the animation because we always control it directly instead
+     * of using the [WindowInsetsAnimationController] to animate its value.
+     */
+    private val cancellationSignal = CancellationSignal()
+
+    /**
+     * Because touch motion has finer granularity than integers, we capture the fractions of
+     * integers here so that we can keep the finger more in line with the touch. Without this,
+     * we'd accumulate error.
+     */
+    private var partialConsumption = 0f
+
+    /**
+     * The [Job] that is launched to animate the insets during a fling. This can be canceled
+     * when the user touches the screen.
+     */
+    private var animationJob: Job? = null
+
+    /**
+     * Request an animation controller because it is `null`. If one has already been requested,
+     * this method does nothing.
+     */
+    private fun requestAnimationController() {
+        if (!isControllerRequested) {
+            isControllerRequested = true
+            view.windowInsetsController?.controlWindowInsetsAnimation(
+                windowInsets.type, // type
+                -1, // durationMillis
+                null, // interpolator
+                cancellationSignal,
+                this
+            )
+        }
+    }
+
+    private var continuation: CancellableContinuation<WindowInsetsAnimationController?>? = null
+
+    /**
+     * Allows us to suspend, waiting for the animation controller to be returned.
+     */
+    private suspend fun getAnimationController(): WindowInsetsAnimationController? =
+        animationController ?: suspendCancellableCoroutine { continuation ->
+            this.continuation = continuation
+            requestAnimationController()
+        }
+
+    /**
+     * Handle the dragging that hides the WindowInsets.
+     */
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset =
+        scroll(available, sideCalculator.hideMotion(available.x, available.y))
+
+    /**
+     * Handle the dragging that exposes the WindowInsets.
+     */
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset = scroll(available, sideCalculator.showMotion(available.x, available.y))
+
+    /**
+     * Scrolls [scrollAmount] and returns the consumed amount of [available].
+     */
+    private fun scroll(available: Offset, scrollAmount: Float): Offset {
+        animationJob?.let {
+            it.cancel()
+            animationJob = null
+        }
+
+        val animationController = animationController
+
+        if (scrollAmount == 0f ||
+            (windowInsets.isVisible == (scrollAmount > 0f) && animationController == null)
+        ) {
+            // No motion in the right direction or this is already fully shown/hidden.
+            return Offset.Zero
+        }
+
+        if (animationController == null) {
+            partialConsumption = 0f
+            // The animation controller isn't ready yet. Just consume the scroll.
+            requestAnimationController()
+            return sideCalculator.consumedOffsets(available)
+        }
+
+        val hidden = sideCalculator.valueOf(animationController.hiddenStateInsets)
+        val shown = sideCalculator.valueOf(animationController.shownStateInsets)
+        val currentInsets = animationController.currentInsets
+        val current = sideCalculator.valueOf(currentInsets)
+
+        val target = if (scrollAmount > 0f) shown else hidden
+
+        if (current == target) {
+            // This is already correct, so nothing to consume
+            partialConsumption = 0f
+            return Offset.Zero
+        }
+
+        val total = current + scrollAmount + partialConsumption
+        val next = total.roundToInt().coerceIn(hidden, shown)
+        partialConsumption = total - total.roundToInt()
+
+        if (next != current) {
+            animationController.setInsetsAndAlpha(
+                sideCalculator.adjustInsets(currentInsets, next),
+                1f, // alpha
+                0f, // progress
+            )
+        }
+        return sideCalculator.consumedOffsets(available)
+    }
+
+    /**
+     * Handle flinging toward hiding the insets.
+     */
+    override suspend fun onPreFling(available: Velocity): Velocity =
+        fling(available, sideCalculator.hideMotion(available.x, available.y), false)
+
+    /**
+     * Handle flinging toward showing the insets.
+     */
+    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity =
+        fling(available, sideCalculator.showMotion(available.x, available.y), true)
+
+    /**
+     * Handle flinging by [flingAmount] and return the consumed velocity of [available].
+     * [towardShown] should be `true` when the intended motion is to show the insets or `false`
+     * if to hide them. We always handle flinging toward the insets if the [flingAmount] is
+     * `0` so that the insets animate to a fully-shown or fully-hidden state.
+     */
+    private suspend fun fling(
+        available: Velocity,
+        flingAmount: Float,
+        towardShown: Boolean
+    ): Velocity {
+        animationJob?.cancel()
+        animationJob = null
+        partialConsumption = 0f
+
+        if ((flingAmount == 0f && !towardShown) ||
+            (animationController == null && windowInsets.isVisible == towardShown)
+        ) {
+            // Either there's no motion to hide or we're certain that
+            // the inset is already correct.
+            return Velocity.Zero
+        }
+
+        val animationController = getAnimationController() ?: return Velocity.Zero
+
+        val hidden = sideCalculator.valueOf(animationController.hiddenStateInsets)
+        val shown = sideCalculator.valueOf(animationController.shownStateInsets)
+        val currentInsets = animationController.currentInsets
+        val current = sideCalculator.valueOf(currentInsets)
+
+        if ((flingAmount <= 0 && current == hidden) || (flingAmount >= 0 && current == shown)) {
+            // We've already reached the destination
+            animationController.finish(current == shown)
+            this@WindowInsetsNestedScrollConnection.animationController = null
+            return Velocity.Zero
+        }
+
+        // Let's see if the velocity is enough to get open
+        val spec = SplineBasedFloatDecayAnimationSpec(density)
+        val distance = current + spec.flingDistance(flingAmount)
+
+        val endPercent = (distance - hidden) / (shown - hidden)
+        val targetShown = endPercent > 0.5f
+        val target = if (targetShown) shown else hidden
+
+        if (distance > shown || distance < hidden) {
+            var endVelocity = 0f
+            // This is enough to reach hidden or shown state, so we can use the Android
+            // spline animation.
+            coroutineScope {
+                animationJob = launch {
+                    animateDecay(
+                        initialValue = current.toFloat(),
+                        initialVelocity = flingAmount,
+                        animationSpec = spec
+                    ) { value, velocity ->
+                        if (value in hidden.toFloat()..shown.toFloat()) {
+                            adjustInsets(value)
+                        } else {
+                            // We've reached the end
+                            endVelocity = velocity
+                            animationController.finish(targetShown)
+                            this@WindowInsetsNestedScrollConnection.animationController = null
+                            animationJob?.cancel()
+                        }
+                    }
+                }
+                animationJob?.join()
+                animationJob = null
+            }
+            return sideCalculator.consumedVelocity(available, endVelocity)
+        } else {
+            // This fling won't make it to the end, so animate to shown or hidden state using
+            // a spring animation
+            coroutineScope {
+                animationJob = launch {
+                    val animatedValue = Animatable(current.toFloat())
+                    animatedValue.animateTo(target.toFloat(), initialVelocity = flingAmount) {
+                        adjustInsets(value)
+                    }
+                    animationController.finish(targetShown)
+                    this@WindowInsetsNestedScrollConnection.animationController = null
+                }
+            }
+            return sideCalculator.consumedVelocity(available, 0f)
+        }
+    }
+
+    /**
+     * Change the inset's side to [inset].
+     */
+    private fun adjustInsets(inset: Float) {
+        animationController?.let {
+            val currentInsets = it.currentInsets
+            val nextInsets = sideCalculator.adjustInsets(currentInsets, inset.roundToInt())
+            it.setInsetsAndAlpha(
+                nextInsets,
+                1f, // alpha
+                0f, // progress
+            )
+        }
+    }
+
+    /**
+     * Called after [requestAnimationController] and the [animationController] is ready.
+     */
+    override fun onReady(controller: WindowInsetsAnimationController, types: Int) {
+        animationController = controller
+        isControllerRequested = false
+        continuation?.resume(controller) { }
+        continuation = null
+    }
+
+    fun dispose() {
+        continuation?.resume(null) { }
+        animationJob?.cancel()
+        val animationController = animationController
+        if (animationController != null) {
+            // We don't want to leave the insets in a partially open or closed state, so finish
+            // the animation
+            val visible = animationController.currentInsets != animationController.hiddenStateInsets
+            animationController.finish(visible)
+        }
+    }
+
+    override fun onFinished(controller: WindowInsetsAnimationController) {
+        animationEnded()
+    }
+
+    override fun onCancelled(controller: WindowInsetsAnimationController?) {
+        animationEnded()
+    }
+
+    /**
+     * The controlled animation has been terminated.
+     */
+    private fun animationEnded() {
+        if (animationController?.isReady == true) {
+            animationController?.finish(windowInsets.isVisible)
+        }
+        animationController = null
+
+        // The animation controller may not have been given to us, so we have to cancel animations
+        // waiting for it.
+        continuation?.resume(null) { }
+        continuation = null
+
+        // Cancel any animation that's running.
+        animationJob?.cancel()
+        animationJob = null
+
+        partialConsumption = 0f
+        isControllerRequested = false
+    }
+}
+
+/**
+ * This interface allows logic for the specific side (left, top, right, bottom) to be
+ * extracted from the logic controlling showing and hiding insets. For example, an inset
+ * at the top will show when dragging down, while an inset at the bottom will hide
+ * when dragging down.
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+private interface SideCalculator {
+    /**
+     * Returns the insets value for the side that this [SideCalculator] is associated with.
+     */
+    fun valueOf(insets: Insets): Int
+
+    /**
+     * Returns the motion, adjusted for side direction, that the [x], and [y] grant. A positive
+     * result indicates that it is in the direction of opening the insets on that side and
+     * a negative result indicates a closing of the insets on that side.
+     */
+    fun motionOf(x: Float, y: Float): Float
+
+    /**
+     * The motion of [x], [y] that indicates showing more of the insets on the side or `0` if
+     * no motion is given to showing more insets.
+     */
+    fun showMotion(x: Float, y: Float): Float = motionOf(x, y).coerceAtLeast(0f)
+
+    /**
+     * The motion of [x], [y] that indicates showing less of the insets on the side or `0` if
+     * no motion is given to showing less insets.
+     */
+    fun hideMotion(x: Float, y: Float): Float = motionOf(x, y).coerceAtMost(0f)
+
+    /**
+     * Takes all values of [oldInsets], except for this side and replaces this side with [newValue].
+     */
+    fun adjustInsets(oldInsets: Insets, newValue: Int): Insets
+
+    /**
+     * Returns the [Offset] that consumes [available] in the direction of this side.
+     */
+    fun consumedOffsets(available: Offset): Offset
+
+    /**
+     * Returns the [Velocity] that consumes [available] in the direction of this side.
+     */
+    fun consumedVelocity(available: Velocity, remaining: Float): Velocity
+
+    companion object {
+        /**
+         * Returns a [SideCalculator] for [side] and the given [layoutDirection]. This only
+         * works for one side and no combination of sides.
+         */
+        fun chooseCalculator(side: WindowInsetsSides, layoutDirection: LayoutDirection) =
+            when (side) {
+                WindowInsetsSides.Left -> LeftSideCalculator
+                WindowInsetsSides.Top -> TopSideCalculator
+                WindowInsetsSides.Right -> RightSideCalculator
+                WindowInsetsSides.Bottom -> BottomSideCalculator
+                WindowInsetsSides.Start -> if (layoutDirection == LayoutDirection.Ltr) {
+                    LeftSideCalculator
+                } else {
+                    RightSideCalculator
+                }
+                WindowInsetsSides.End -> if (layoutDirection == LayoutDirection.Ltr) {
+                    RightSideCalculator
+                } else {
+                    LeftSideCalculator
+                }
+                else -> error("Only Left, Top, Right, Bottom, Start and End are allowed")
+            }
+
+        private val LeftSideCalculator = object : SideCalculator {
+            override fun valueOf(insets: Insets): Int = insets.left
+            override fun motionOf(x: Float, y: Float): Float = x
+            override fun adjustInsets(oldInsets: Insets, newValue: Int): Insets =
+                Insets.of(newValue, oldInsets.top, oldInsets.right, oldInsets.bottom)
+            override fun consumedOffsets(available: Offset): Offset = Offset(available.x, 0f)
+            override fun consumedVelocity(available: Velocity, remaining: Float): Velocity =
+                Velocity(available.x - remaining, 0f)
+        }
+
+        private val TopSideCalculator = object : SideCalculator {
+            override fun valueOf(insets: Insets): Int = insets.top
+            override fun motionOf(x: Float, y: Float): Float = y
+            override fun adjustInsets(oldInsets: Insets, newValue: Int): Insets =
+                Insets.of(oldInsets.left, newValue, oldInsets.right, oldInsets.bottom)
+            override fun consumedOffsets(available: Offset): Offset = Offset(0f, available.y)
+            override fun consumedVelocity(available: Velocity, remaining: Float): Velocity =
+                Velocity(0f, available.y - remaining)
+        }
+
+        private val RightSideCalculator = object : SideCalculator {
+            override fun valueOf(insets: Insets): Int = insets.right
+            override fun motionOf(x: Float, y: Float): Float = -x
+            override fun adjustInsets(oldInsets: Insets, newValue: Int): Insets =
+                Insets.of(oldInsets.left, oldInsets.top, newValue, oldInsets.bottom)
+            override fun consumedOffsets(available: Offset): Offset = Offset(available.x, 0f)
+            override fun consumedVelocity(available: Velocity, remaining: Float): Velocity =
+                Velocity(available.x + remaining, 0f)
+        }
+
+        private val BottomSideCalculator = object : SideCalculator {
+            override fun valueOf(insets: Insets): Int = insets.bottom
+            override fun motionOf(x: Float, y: Float): Float = -y
+            override fun adjustInsets(oldInsets: Insets, newValue: Int): Insets =
+                Insets.of(oldInsets.left, oldInsets.top, oldInsets.right, newValue)
+            override fun consumedOffsets(available: Offset): Offset = Offset(0f, available.y)
+            override fun consumedVelocity(available: Velocity, remaining: Float): Velocity =
+                Velocity(0f, available.y + remaining)
+        }
+    }
+}
+
+// SplineBasedFloatDecayAnimationSpec is in animation:animation library, which depends on
+// foundation-layout, so I've copied it below, but a bit trimmed to only have what is needed.
+
+// These constants are copied from the Android spline decay rate
+private const val Inflection = 0.35f // Tension lines cross at (Inflection, 1)
+private val PlatformFlingScrollFriction = ViewConfiguration.getScrollFriction()
+private const val GravityEarth = 9.80665f
+private const val InchesPerMeter = 39.37f
+private val DecelerationRate = ln(0.78) / ln(0.9)
+private val DecelMinusOne = DecelerationRate - 1.0
+private const val StartTension = 0.5f
+private const val EndTension = 1.0f
+private const val P1 = StartTension * Inflection
+private const val P2 = 1.0f - EndTension * (1.0f - Inflection)
+
+private class SplineBasedFloatDecayAnimationSpec(density: Density) :
+    FloatDecayAnimationSpec {
+
+    override val absVelocityThreshold: Float get() = 0f
+
+    /**
+     * A density-specific coefficient adjusted to physical values.
+     */
+    private val magicPhysicalCoefficient: Float =
+        GravityEarth * InchesPerMeter * density.density * 160f * 0.84f
+
+    private fun getSplineDeceleration(velocity: Float): Double =
+        AndroidFlingSpline.deceleration(
+            velocity,
+            PlatformFlingScrollFriction * magicPhysicalCoefficient
+        )
+
+    /**
+     * Compute the distance of a fling in units given an initial [velocity] of units/second
+     */
+    fun flingDistance(velocity: Float): Float {
+        val l = getSplineDeceleration(velocity)
+        return (
+            PlatformFlingScrollFriction * magicPhysicalCoefficient
+                * exp(DecelerationRate / DecelMinusOne * l)
+            ).toFloat() * sign(velocity)
+    }
+
+    override fun getTargetValue(initialValue: Float, initialVelocity: Float): Float =
+        initialValue + flingDistance(initialVelocity)
+
+    @Suppress("MethodNameUnits")
+    override fun getValueFromNanos(
+        playTimeNanos: Long,
+        initialValue: Float,
+        initialVelocity: Float
+    ): Float {
+        val duration = getDurationNanos(0f, initialVelocity)
+        val splinePos = if (duration > 0) playTimeNanos / duration.toFloat() else 1f
+        val distance = flingDistance(initialVelocity)
+        return initialValue + distance *
+            AndroidFlingSpline.flingPosition(splinePos).distanceCoefficient
+    }
+
+    @Suppress("MethodNameUnits")
+    override fun getDurationNanos(initialValue: Float, initialVelocity: Float): Long {
+        val l = getSplineDeceleration(initialVelocity)
+        return (1_000_000_000.0 * exp(l / DecelMinusOne)).toLong()
+    }
+
+    @Suppress("MethodNameUnits")
+    override fun getVelocityFromNanos(
+        playTimeNanos: Long,
+        initialValue: Float,
+        initialVelocity: Float
+    ): Float {
+        val duration = getDurationNanos(0f, initialVelocity)
+        val splinePos = if (duration > 0L) playTimeNanos / duration.toFloat() else 1f
+        val distance = flingDistance(initialVelocity)
+        return AndroidFlingSpline.flingPosition(splinePos).velocityCoefficient *
+            distance / duration * 1_000_000_000.0f
+    }
+}
+
+private object AndroidFlingSpline {
+    private const val NbSamples = 100
+    private val SplinePositions = FloatArray(NbSamples + 1)
+    private val SplineTimes = FloatArray(NbSamples + 1)
+
+    init {
+        var xMin = 0.0f
+        var yMin = 0.0f
+        for (i in 0 until NbSamples) {
+            val alpha = i.toFloat() / NbSamples
+            var xMax = 1.0f
+            var x: Float
+            var tx: Float
+            var coef: Float
+            while (true) {
+                x = xMin + (xMax - xMin) / 2.0f
+                coef = 3.0f * x * (1.0f - x)
+                tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x
+                if (abs(tx - alpha) < 1E-5) break
+                if (tx > alpha) xMax = x else xMin = x
+            }
+            SplinePositions[i] = coef * ((1.0f - x) * StartTension + x) + x * x * x
+            var yMax = 1.0f
+            var y: Float
+            var dy: Float
+            while (true) {
+                y = yMin + (yMax - yMin) / 2.0f
+                coef = 3.0f * y * (1.0f - y)
+                dy = coef * ((1.0f - y) * StartTension + y) + y * y * y
+                if (abs(dy - alpha) < 1E-5) break
+                if (dy > alpha) yMax = y else yMin = y
+            }
+            SplineTimes[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y
+        }
+        SplineTimes[NbSamples] = 1.0f
+        SplinePositions[NbSamples] = SplineTimes[NbSamples]
+    }
+
+    /**
+     * Compute an instantaneous fling position along the scroller spline.
+     *
+     * @param time progress through the fling animation from 0-1
+     */
+    fun flingPosition(time: Float): FlingResult {
+        val index = (NbSamples * time).toInt()
+        var distanceCoef = 1f
+        var velocityCoef = 0f
+        if (index < NbSamples) {
+            val tInf = index.toFloat() / NbSamples
+            val tSup = (index + 1).toFloat() / NbSamples
+            val dInf = SplinePositions[index]
+            val dSup = SplinePositions[index + 1]
+            velocityCoef = (dSup - dInf) / (tSup - tInf)
+            distanceCoef = dInf + (time - tInf) * velocityCoef
+        }
+        return FlingResult(packFloats(distanceCoef, velocityCoef))
+    }
+
+    /**
+     * The rate of deceleration along the spline motion given [velocity] and [friction].
+     */
+    fun deceleration(velocity: Float, friction: Float): Double =
+        ln(Inflection * abs(velocity) / friction.toDouble())
+
+    /**
+     * Result coefficients of a scroll computation
+     */
+    @JvmInline
+    value class FlingResult(private val packedValue: Long) {
+        /**
+         * Linear distance traveled from 0-1, from source (0) to destination (1)
+         */
+        val distanceCoefficient: Float get() = unpackFloat1(packedValue)
+        /**
+         * Instantaneous velocity coefficient at this point in the fling expressed in
+         * total distance per unit time
+         */
+        val velocityCoefficient: Float get() = unpackFloat2(packedValue)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt
index 5fbb82fc..ffc7e85 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/WindowInsetsPadding.kt
@@ -129,7 +129,8 @@
         val horizontal = left + right
         val vertical = top + bottom
 
-        val placeable = measurable.measure(constraints.offset(-horizontal, -vertical))
+        val childConstraints = constraints.offset(-horizontal, -vertical)
+        val placeable = measurable.measure(childConstraints)
 
         val width = constraints.constrainWidth(placeable.width + horizontal)
         val height = constraints.constrainHeight(placeable.height + vertical)
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index 43e0f74..4b0918f 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -154,13 +154,13 @@
   }
 
   public final class ChipKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void AssistChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ElevatedAssistChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ElevatedFilterChip(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? selectedIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.SelectableChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SelectableChipBorder? border, optional androidx.compose.material3.SelectableChipColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ElevatedSuggestionChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void FilterChip(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? selectedIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.SelectableChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SelectableChipBorder? border, optional androidx.compose.material3.SelectableChipColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void InputChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? avatar, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SuggestionChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void AssistChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ElevatedAssistChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ElevatedFilterChip(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? selectedIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.SelectableChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SelectableChipBorder? border, optional androidx.compose.material3.SelectableChipColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ElevatedSuggestionChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void FilterChip(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? selectedIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.SelectableChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.SelectableChipBorder? border, optional androidx.compose.material3.SelectableChipColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void InputChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? avatar, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void SuggestionChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.material3.ChipColors colors);
   }
 
   @androidx.compose.runtime.Stable public final class ColorScheme {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
index 2bbf677..d7784eb 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Chip.kt
@@ -30,7 +30,6 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.padding
@@ -50,11 +49,10 @@
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.clip
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
@@ -109,7 +107,7 @@
 @Composable
 fun AssistChip(
     onClick: () -> Unit,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     leadingIcon: @Composable (() -> Unit)? = null,
@@ -152,7 +150,7 @@
  * calendar event from the home screen. Assist chips function as though the user asked an assistant
  * to complete the action. They should appear dynamically and contextually in a UI.
  *
- * ![Assist chip image](https://developer.android.com/images/reference/androidx/compose/material3/assist-chip.png)
+ * ![Assist chip image](https://developer.android.com/images/reference/androidx/compose/material3/elevated-assist-chip.png)
  *
  * This assist chip is applied with an elevated style. If you want a flat style, use the
  * [AssistChip].
@@ -188,7 +186,7 @@
 @Composable
 fun ElevatedAssistChip(
     onClick: () -> Unit,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     leadingIcon: @Composable (() -> Unit)? = null,
@@ -276,7 +274,7 @@
 fun FilterChip(
     selected: Boolean,
     onClick: () -> Unit,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     leadingIcon: @Composable (() -> Unit)? = null,
@@ -315,7 +313,7 @@
  * Filter chips use tags or descriptive words to filter content. They can be a good alternative to
  * toggle buttons or checkboxes.
  *
- * ![Filter chip image](https://developer.android.com/images/reference/androidx/compose/material3/filter-chip.png)
+ * ![Filter chip image](https://developer.android.com/images/reference/androidx/compose/material3/elevated-filter-chip.png)
  *
  * This filter chip is applied with an elevated style. If you want a flat style, use the
  * [FilterChip].
@@ -358,7 +356,7 @@
 fun ElevatedFilterChip(
     selected: Boolean,
     onClick: () -> Unit,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     leadingIcon: @Composable (() -> Unit)? = null,
@@ -442,7 +440,7 @@
 @Composable
 fun InputChip(
     onClick: () -> Unit,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     leadingIcon: @Composable (() -> Unit)? = null,
@@ -459,11 +457,14 @@
     var shapedAvatar: @Composable (() -> Unit)? = null
     if (avatar != null) {
         val avatarOpacity = if (enabled) 1f else InputChipTokens.DisabledAvatarOpacity
+        val avatarShape = InputChipTokens.AvatarShape.toShape()
         shapedAvatar = @Composable {
             Box(
-                modifier = Modifier
-                    .alpha(avatarOpacity)
-                    .clip(InputChipTokens.AvatarShape.toShape()),
+                modifier = Modifier.graphicsLayer {
+                    this.alpha = avatarOpacity
+                    this.shape = avatarShape
+                    this.clip = true
+                },
                 contentAlignment = Alignment.Center
             ) {
                 avatar()
@@ -542,7 +543,7 @@
 @Composable
 fun SuggestionChip(
     onClick: () -> Unit,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     icon: @Composable (() -> Unit)? = null,
@@ -583,7 +584,7 @@
  * Suggestion chips help narrow a user's intent by presenting dynamically generated suggestions,
  * such as possible responses or search filters.
  *
- * ![Suggestion chip image](https://developer.android.com/images/reference/androidx/compose/material3/suggestion-chip.png)
+ * ![Suggestion chip image](https://developer.android.com/images/reference/androidx/compose/material3/elevated-suggestion-chip.png)
  *
  * This suggestion chip is applied with an elevated style. If you want a flat style, use the
  * [SuggestionChip].
@@ -620,7 +621,7 @@
 @Composable
 fun ElevatedSuggestionChip(
     onClick: () -> Unit,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     icon: @Composable (() -> Unit)? = null,
@@ -662,8 +663,8 @@
      * [interactionSource]. This should typically be the same value as the [shadowElevation].
      *
      * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
-     * When surface's color is [ColorScheme.surface], a higher the elevation will result
-     * in a darker color in light theme and lighter color in dark theme.
+     * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
+     * color in light theme and lighter color in dark theme.
      *
      * See [shadowElevation] which controls the elevation of the shadow drawn around the Chip.
      *
@@ -699,8 +700,8 @@
      * [interactionSource]. This should typically be the same value as the [shadowElevation].
      *
      * Tonal elevation is used to apply a color shift to the surface to give the it higher emphasis.
-     * When surface's color is [ColorScheme.surface], a higher the elevation will result
-     * in a darker color in light theme and lighter color in dark theme.
+     * When surface's color is [ColorScheme.surface], a higher elevation will result in a darker
+     * color in light theme and lighter color in dark theme.
      *
      * See [shadowElevation] which controls the elevation of the shadow drawn around the Chip.
      *
@@ -854,7 +855,7 @@
      * @param selected whether the chip is selected
      */
     @Composable
-    fun borderStroke(enabled: Boolean, selected: Boolean): State<BorderStroke>
+    fun borderStroke(enabled: Boolean, selected: Boolean): State<BorderStroke?>
 }
 
 /**
@@ -1615,7 +1616,7 @@
     modifier: Modifier,
     onClick: () -> Unit,
     enabled: Boolean,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     labelTextStyle: TextStyle,
     labelColor: Color,
     leadingIcon: @Composable (() -> Unit)?,
@@ -1665,7 +1666,7 @@
     modifier: Modifier,
     onClick: () -> Unit,
     enabled: Boolean,
-    label: @Composable RowScope.() -> Unit,
+    label: @Composable () -> Unit,
     labelTextStyle: TextStyle,
     leadingIcon: @Composable (() -> Unit)?,
     trailingIcon: @Composable (() -> Unit)?,
@@ -1709,7 +1710,7 @@
 
 @Composable
 private fun ChipContent(
-    label: @Composable (RowScope.() -> Unit),
+    label: @Composable () -> Unit,
     labelTextStyle: TextStyle,
     labelColor: Color,
     leadingIcon: @Composable (() -> Unit)?,
@@ -2158,7 +2159,7 @@
     private val selectedBorderWidth: Dp
 ) : SelectableChipBorder {
     @Composable
-    override fun borderStroke(enabled: Boolean, selected: Boolean): State<BorderStroke> {
+    override fun borderStroke(enabled: Boolean, selected: Boolean): State<BorderStroke?> {
         val color = if (enabled) {
             if (selected) selectedBorderColor else borderColor
         } else {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a7061ca..53c4c95 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -39,6 +39,7 @@
 leakcanary = "2.7"
 metalava = "1.0.0-alpha06"
 mockito = "2.25.0"
+protobuf = "3.19.4"
 skiko = "0.7.7"
 sqldelight = "1.3.0"
 wire = "3.6.0"
@@ -148,10 +149,10 @@
 playServicesBase = { module = "com.google.android.gms:play-services-base", version = "17.0.0" }
 playServicesBasement = { module = "com.google.android.gms:play-services-basement", version = "17.0.0" }
 playServicesWearable = { module = "com.google.android.gms:play-services-wearable", version = "17.1.0" }
-protobuf = { module = "com.google.protobuf:protobuf-java", version = "3.4.0" }
-protobufCompiler = { module = "com.google.protobuf:protoc", version = "3.19.4" }
+protobuf = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
+protobufCompiler = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
 protobufGradlePluginz = { module = "com.google.protobuf:protobuf-gradle-plugin", version = "0.8.18" }
-protobufLite = { module = "com.google.protobuf:protobuf-javalite", version = "3.19.4" }
+protobufLite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
 reactiveStreams = { module = "org.reactivestreams:reactive-streams", version = "1.0.0" }
 retrofit = { module = "com.squareup.retrofit2:retrofit", version = "2.7.2" }
 robolectric = { module = "org.robolectric:robolectric", version = "4.7.3" }
diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys
index b069851b..3100613 100644
--- a/gradle/verification-keyring.keys
+++ b/gradle/verification-keyring.keys
@@ -2478,6 +2478,65 @@
 -----END PGP PUBLIC KEY BLOCK-----
 
 
+pub    3AD93C3C677A106E
+uid    Carl Mastrangelo <carl@carlmastrangelo.com>
+
+sub    9B2A1B698A113AAD
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+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
+U/UZPX6m9g==
+=XXe6
+-----END PGP PUBLIC KEY BLOCK-----
+
+
 pub    3C0A8F4744F37328
 sub    D17266C6E05F9993
 -----BEGIN PGP PUBLIC KEY BLOCK-----
@@ -5769,6 +5828,53 @@
 -----END PGP PUBLIC KEY BLOCK-----
 
 
+pub    8D7F1BEC1E2ECAE7
+uid    Tatu Saloranta (cowtowncoder) <tatu.saloranta@iki.fi>
+uid    Tatu Saloranta <tatu.saloranta@iki.fi>
+
+sub    E98008460EB9BB34
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+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
+Jqg9YirnHcrlYF0r
+=gukc
+-----END PGP PUBLIC KEY BLOCK-----
+
+
 pub    8E3F0DE7AE354651
 sub    D3047B0BA4452AE1
 -----BEGIN PGP PUBLIC KEY BLOCK-----
@@ -7782,6 +7888,65 @@
 -----END PGP PUBLIC KEY BLOCK-----
 
 
+pub    AC7A514BC9F9BB70
+uid    Punyashloka Biswal <punya@google.com>
+
+sub    7B92B768F9D37337
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+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
+sg==
+=w5lW
+-----END PGP PUBLIC KEY BLOCK-----
+
+
 pub    B0F3710FA64900E7
 uid    ?amonn McManus <eamonn@mcmanus.net>
 
@@ -8791,6 +8956,54 @@
 -----END PGP PUBLIC KEY BLOCK-----
 
 
+pub    D041CAD2E452550F
+uid    Deanna <deannagarcia@google.com>
+
+sub    5199F3DAE89C332D
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+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
+f8npB1l1fT5cozzQJkPVYzhBWH1KXP5X
+=vnEw
+-----END PGP PUBLIC KEY BLOCK-----
+
+
 pub    D4DA5EAB3CD7E958
 uid    Jiaxiang Chen <jiaxiang@google.com>
 
@@ -9557,6 +9770,70 @@
 -----END PGP PUBLIC KEY BLOCK-----
 
 
+pub    E16AB52D79FD224F
+sub    5A34A5E06B936F93
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQENBFF/4bYBCADTeOLZiVGNbjlPrwG7UcMl+yXmEqpf9dB1A9cuicH3PWXj0WOb
+LSzHjzoRvRekEqSUmgoveey1lPuA2qjOUkXY6Kiyx+oLiG0/ObJHUQW2O+tjSQ0R
+ZXKd4ftaw65SLbwYO2JHzj5fLC9j2mZQiRjGs1bWM58c/dOKp1XaOc1/ffcl3L3q
+Up64jWH9r3yhPemh5SHo47UxNvItdaJJYnt20azpZj9oq1ebUuQFMaQDc/RTALhf
+Xb4BWO+z2PCmChz60i/Ko2ZKPJV2TqPqWO+aklgxTTwZZ0IvgFm/5n3Dtn5p5iGf
+qwKkHPJIDWc8cWYtxC608LFdqiAlYmp/oPi5ABEBAAG5AQ0EUX/htgEIALToF36j
+45OitNd4k17BSZJKnuS3uIL3tTw0fRqLv0/3EBaj4zD5Qc5YTKFgM66Bb5ybI63c
+wYhfSBHP2ZRS7oNdDbPd/30jDKNvmcDjIhGLT7bZJwC9SJVifHuvtzr6wBR8xoIt
+yYva5D3ax8ZvnzqIbMPeHou+0ZnRYSPjy2c2TxAJTjDOG461h9mVXDdK74wL8kQs
+IxqqYRIeEdmrXMrd/B8IPwuIv8w7LwzadNgRnXaJ5Q5bnMvvhVLnWKRt5aiQVBxc
+67FTujjqFF4Y/1UJb311K+1LSqNrTT7As8nhf2Gu/Gb47kw1bb7wBdKv2Swx5mYq
+iW5+ARQU7jCiUVkAEQEAAYkBHwQYAQIACQUCUX/htgIbDAAKCRDharUtef0iT2Sy
+CADAznSkG/8EdIU5UQhp/lY9h3WLzYI7aARw0IA6O4ijGLwcytO7TaWjEzUCMZdw
+01vAjVH1xNn9QvTgQV+2GyqyBNsjmgGt5/tK/+JtMgXUwr8+KsBf3908rOqAAZ3Y
+GyM9N8sRsyfPB/PHfv289sL2IKPxiFTGI0NGS3qOAKQ5TZvV7OPsP5+yHfeJG/Xh
+CW8p+nkMGpH4rE8Z6NKgLe/WC6J36aQ4kBfYneueH90Dc400rfGyL+0Gn1Rzuj2K
+FuUFK6q/GBlFaNo0azCqtdpcO6C3GpJYtISxpQ1Rp9kSEzSCL3tOli8Xs6gsruc+
+vCSIy8lzRw19ZO9G7qhjcHLc
+=vO0K
+-----END PGP PUBLIC KEY BLOCK-----
+
+
+pub    E16AB52D79FD224F
+uid    Google Api Client Libraries (Releases) <google-api-client-libraries@google.com>
+
+sub    5A34A5E06B936F93
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: BCPG v1.68
+
+mQENBFF/4bYBCADTeOLZiVGNbjlPrwG7UcMl+yXmEqpf9dB1A9cuicH3PWXj0WOb
+LSzHjzoRvRekEqSUmgoveey1lPuA2qjOUkXY6Kiyx+oLiG0/ObJHUQW2O+tjSQ0R
+ZXKd4ftaw65SLbwYO2JHzj5fLC9j2mZQiRjGs1bWM58c/dOKp1XaOc1/ffcl3L3q
+Up64jWH9r3yhPemh5SHo47UxNvItdaJJYnt20azpZj9oq1ebUuQFMaQDc/RTALhf
+Xb4BWO+z2PCmChz60i/Ko2ZKPJV2TqPqWO+aklgxTTwZZ0IvgFm/5n3Dtn5p5iGf
+qwKkHPJIDWc8cWYtxC608LFdqiAlYmp/oPi5ABEBAAG0T0dvb2dsZSBBcGkgQ2xp
+ZW50IExpYnJhcmllcyAoUmVsZWFzZXMpIDxnb29nbGUtYXBpLWNsaWVudC1saWJy
+YXJpZXNAZ29vZ2xlLmNvbT6JATgEEwECACIFAlF/4bYCGwMGCwkIBwMCBhUIAgkK
+CwQWAgMBAh4BAheAAAoJEOFqtS15/SJP6NYIAMbwdE5S9M/5tIhLPg2arbvnxfh2
+i1e2aKE6PcRlBGeNq8YzWQStIewRhoDbY4MY3dG0nX9wgXU6XFJNxjyjE5Gqpyrc
+xEhs6r+HfxlGwB/OCaDMGR89lcWn1xF7uju3Qsdkb6o2JuCSAqOh4M1zIqADSMMq
+OjuSJ13GddcUFQ/MEvouE7Leesls9AM724BNZfnyJjIfJqfk38rXCxKpMbxCJ2v2
+louDMu0LClA1efdvDBPOrc8+A74dsVh7cQqMoe5lCqrcoC5apMqlJ71UV7SIBNBr
+7+AD6p0ZdGY8C/pTCl+NGe/Skjdqk7uEbmyePpm4BaJTmbwIgiZdGxthUgO5AQ0E
+UX/htgEIALToF36j45OitNd4k17BSZJKnuS3uIL3tTw0fRqLv0/3EBaj4zD5Qc5Y
+TKFgM66Bb5ybI63cwYhfSBHP2ZRS7oNdDbPd/30jDKNvmcDjIhGLT7bZJwC9SJVi
+fHuvtzr6wBR8xoItyYva5D3ax8ZvnzqIbMPeHou+0ZnRYSPjy2c2TxAJTjDOG461
+h9mVXDdK74wL8kQsIxqqYRIeEdmrXMrd/B8IPwuIv8w7LwzadNgRnXaJ5Q5bnMvv
+hVLnWKRt5aiQVBxc67FTujjqFF4Y/1UJb311K+1LSqNrTT7As8nhf2Gu/Gb47kw1
+bb7wBdKv2Swx5mYqiW5+ARQU7jCiUVkAEQEAAYkBHwQYAQIACQUCUX/htgIbDAAK
+CRDharUtef0iT2SyCADAznSkG/8EdIU5UQhp/lY9h3WLzYI7aARw0IA6O4ijGLwc
+ytO7TaWjEzUCMZdw01vAjVH1xNn9QvTgQV+2GyqyBNsjmgGt5/tK/+JtMgXUwr8+
+KsBf3908rOqAAZ3YGyM9N8sRsyfPB/PHfv289sL2IKPxiFTGI0NGS3qOAKQ5TZvV
+7OPsP5+yHfeJG/XhCW8p+nkMGpH4rE8Z6NKgLe/WC6J36aQ4kBfYneueH90Dc400
+rfGyL+0Gn1Rzuj2KFuUFK6q/GBlFaNo0azCqtdpcO6C3GpJYtISxpQ1Rp9kSEzSC
+L3tOli8Xs6gsruc+vCSIy8lzRw19ZO9G7qhjcHLc
+=JOMJ
+-----END PGP PUBLIC KEY BLOCK-----
+
+
 pub    E3822B59020A349D
 sub    9351716690874F25
 sub    60EB70DDAAC2EC21
@@ -10146,132 +10423,3 @@
 Pt2uco8an9pO9/oqU6vlZUr38w==
 =alQS
 -----END PGP PUBLIC KEY BLOCK-----
-
-pub    D041CAD2E452550F
------BEGIN PGP PUBLIC KEY BLOCK-----
-Comment: Hostname:
-Version: Hockeypuck ~unreleased
-
-xsDNBGCtdhoBDADdopjDt4eUNEqLJSw1ZICSR0oq09SOVtJSaSYdF8UiXjBfL1Ds
-fhTDqSv5pT2a2gLj0OU3tFhWHvINLaKKCjQnHVcFXi2LTxt+XBOjRYkFjHVisbaZ
-PZ6HnTMStPrvs+hQ168vU3VfYOsOLN22j53I/Ba+FA7E0G0bqkratuT5L7BTR1mC
-fqDaeisWSCllfe6EEysaFF+/1RcRy+Yt+8ZWV0FZEF7UwQvqKHcYmlkqPIn3v/8y
-J/yvmzIEtCQ1F+bvJbzaROmeJf254G2Uh7IfMYEm9WlqnGwNdbIhil7bdxq8Y/0H
-XbQPaESxkki7yL5JTfH/+UzdklMe+Dga273L/cgzfjV3zJJ9vR94W5ABAbGYh4ZW
-aKvNnT1m4vTbEMfo4r3NF2zc+K9Ly/JNaHqkR5M4SVElvN2lsC5KNUiRvExhg+h0
-mKyx61mu3gUIrC1UOmqhtx7RzQQf7ESMdzmNHY0P93lR0Ic10fyli0wfl7A6q7+q
-zV2a1V2k9Yg6B9sAEQEAAc0gRGVhbm5hIDxkZWFubmFnYXJjaWFAZ29vZ2xlLmNv
-bT7CwRQEEwEKAD4WIQRpa2GZoqnYwpznjMDQQcrS5FJVDwUCYK12GgIbAwUJA8Jn
-AAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRDQQcrS5FJVD51GC/4oTue5F8jx
-oeMyDzoZdeiEnEFvrTU80/9y1CrlSIX0HdD5m9nENQOfDjSv9RrHC16WfJyQtKYs
-ADujaZGf2Da4zl2XZ29zeowQK7nzlh2y2bWz7eb9i+nzRApvM07AvSe1ILgFXy1S
-MEMZTnE7ceEosF1hbP/yRDT83ylhEA6ncFGR42WwHHLZMBjBdehl+LxBIkKJyHKb
-M/T2dBGEOBEjSxqDD9jCaRG4n9yq1TWY3iQD2EuxpfnEYeGLZ68E3ipctlCv/nLd
-e3V1se0bvRdx/FlpF7GTZlBcCpmOxTAUon8O8rpgktdoEkz4hknl2cVod4Preax8
-b3jMyf9GFMdl+b0xU3ho3IyfiMMtez02lNTjDJA43R5hLGLgZPV6crHeukbMmQcm
-l4ckUkABFBMuwxn+TT5CEaDG4UoNmOS3n8vDM31G+FcJYJHeNAYNXj5ZW4T38rX3
-47Hz+R9ZAiHrypAPZXGnAizzEQ+bdAqge3Ye6KA/krv2A9rP272UiBTOwM0EYK12
-GgEMAMgP3//QeBsTS3IrfSp3m44el96X6BWona2yo4DvVyuwqfULZE+Nhj7I+kEZ
-LrA29AOySOD/6quJ4MIJZfq/Do920Di8/10WQ00OdCM1wH7bMz2UvcSqsr0iOgQt
-ycuUf7JOHSTME9vqk+C3Lhn0r59AVaRdXEe6zBgNZyzZJeCr5F8wRhglPlwvhOGs
-2aLEqlCxFnY4pLayQFoQyw1lDjHIXHg5JtfOHvqiNXVDcGpyKLG8SzImp62iL4sf
-uA0weVIQeS9kZiQabSYKvSf3TvNXYTgmFz/vjPbYhv9LTkBroTlVg3l+UmAxLrHV
-uXMx0zX3jfNNHAqUjVhPYZhnifMkmGJgLeMIVqr5Q/tx8pzyYiiOcqQ1zDg8ubJD
-GRue1JjlUGdw19OvhFDs+lydukt8Mmhb0gPkBLi2syZHgYHtEooXPLwEsJ+SynZC
-FhZiWj8BsWNFJpaDd8ynNeWhMAcwi3B5ZeQiZaAlV0sItxsrzvbu4ZYZtkjAkQds
-aaTWSwARAQABwsD8BBgBCgAmFiEEaWthmaKp2MKc54zA0EHK0uRSVQ8FAmCtdhoC
-GwwFCQPCZwAACgkQ0EHK0uRSVQ+G7wwAvaVPDgnM+i2pGQPwq6MkSzhKEG4H1pvB
-WyYR8H9D3p/dE33IjVu3EEy1h37Nzdyp46KtASGNe3KBodSsh6gvPlV5pNGxMNbX
-6fo8ZGtS83C+6uTF1cYmuO1nmi8P4+7qtcNZg4xv/ujAZIC20kemYKDth3FvPxEX
-soxY+Ns7sxgd3SqoyLhjcyoczI8uyhim5nfvvbnEd6WrdiBPBtb/F1h/nfqdFj2T
-cZkAlnzGnlVlgU8J60u6zE+9VvBm0lJR73Ar55mQEwarGFPL1a3/A7ZEeNa0Dc3O
-a5sKMYtxMlGKZ0WGUoGcDWiaDEsv5YyRnaSOaXKM1NkJCR013QArRcHrRBPo+0/R
-IZVE+b8oEcmGzdL8HNwnm7e06ruZryF9LQA5YBmCKE0urigmgEvCzZsj/fMJ+OIZ
-cAhE7UVae48GpW2kLATxmK01oSzvizIlmN3rVz2EnjOun2iuuEpF/lmDbjK5n1r3
-f8npB1l1fT5cozzQJkPVYzhBWH1KXP5X
-=qJgQ
------END PGP PUBLIC KEY BLOCK-----
-
-pub    E16AB52D79FD224F
-sub    5A34A5E06B936F93
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: BCPG v1.68
-
-mQENBFF/4bYBCADTeOLZiVGNbjlPrwG7UcMl+yXmEqpf9dB1A9cuicH3PWXj0WOb
-LSzHjzoRvRekEqSUmgoveey1lPuA2qjOUkXY6Kiyx+oLiG0/ObJHUQW2O+tjSQ0R
-ZXKd4ftaw65SLbwYO2JHzj5fLC9j2mZQiRjGs1bWM58c/dOKp1XaOc1/ffcl3L3q
-Up64jWH9r3yhPemh5SHo47UxNvItdaJJYnt20azpZj9oq1ebUuQFMaQDc/RTALhf
-Xb4BWO+z2PCmChz60i/Ko2ZKPJV2TqPqWO+aklgxTTwZZ0IvgFm/5n3Dtn5p5iGf
-qwKkHPJIDWc8cWYtxC608LFdqiAlYmp/oPi5ABEBAAG5AQ0EUX/htgEIALToF36j
-45OitNd4k17BSZJKnuS3uIL3tTw0fRqLv0/3EBaj4zD5Qc5YTKFgM66Bb5ybI63c
-wYhfSBHP2ZRS7oNdDbPd/30jDKNvmcDjIhGLT7bZJwC9SJVifHuvtzr6wBR8xoIt
-yYva5D3ax8ZvnzqIbMPeHou+0ZnRYSPjy2c2TxAJTjDOG461h9mVXDdK74wL8kQs
-IxqqYRIeEdmrXMrd/B8IPwuIv8w7LwzadNgRnXaJ5Q5bnMvvhVLnWKRt5aiQVBxc
-67FTujjqFF4Y/1UJb311K+1LSqNrTT7As8nhf2Gu/Gb47kw1bb7wBdKv2Swx5mYq
-iW5+ARQU7jCiUVkAEQEAAYkBHwQYAQIACQUCUX/htgIbDAAKCRDharUtef0iT2Sy
-CADAznSkG/8EdIU5UQhp/lY9h3WLzYI7aARw0IA6O4ijGLwcytO7TaWjEzUCMZdw
-01vAjVH1xNn9QvTgQV+2GyqyBNsjmgGt5/tK/+JtMgXUwr8+KsBf3908rOqAAZ3Y
-GyM9N8sRsyfPB/PHfv289sL2IKPxiFTGI0NGS3qOAKQ5TZvV7OPsP5+yHfeJG/Xh
-CW8p+nkMGpH4rE8Z6NKgLe/WC6J36aQ4kBfYneueH90Dc400rfGyL+0Gn1Rzuj2K
-FuUFK6q/GBlFaNo0azCqtdpcO6C3GpJYtISxpQ1Rp9kSEzSCL3tOli8Xs6gsruc+
-vCSIy8lzRw19ZO9G7qhjcHLc
-=vO0K
------END PGP PUBLIC KEY BLOCK-----
-
-pub    3AD93C3C677A106E
-uid    Carl Mastrangelo <carl@carlmastrangelo.com>
-
-sub    9B2A1B698A113AAD
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: BCPG v1.68
-
-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
-U/UZPX6m9g==
-=XXe6
------END PGP PUBLIC KEY BLOCK-----
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 869f77d..be71621 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -57,9 +57,7 @@
          <trusted-key id="160a7a9cf46221a56b06ad64461a804f2609fd89" group="com.github.shyiko.klob" name="klob"/>
          <trusted-key id="1861c322c56014b2" group="commons-lang"/>
          <trusted-key id="190d5a957ff22273e601f7a7c92c5fec70161c62" group="org.codehaus.mojo"/>
-         <trusted-key id="19beab2d799c020f17c69126b16698a4adf4d638">
-            <trusting group="org.checkerframework"/>
-         </trusted-key>
+         <trusted-key id="19beab2d799c020f17c69126b16698a4adf4d638" group="org.checkerframework"/>
          <trusted-key id="1b2718089ce964b8" group="com.thoughtworks.qdox"/>
          <trusted-key id="1bc86444bbd2a24c3a40904a438e9634a2319637" group="co.nstant.in" name="cbor"/>
          <trusted-key id="1d0a8b5e77c678a7c724445abf984b4145ea13f7" group="com.squareup"/>
@@ -135,7 +133,16 @@
          <trusted-key id="438e9634a2319637" group="co.nstant.in"/>
          <trusted-key id="44ce7bf2825ea2cd" group="com.ibm.icu"/>
          <trusted-key id="461a804f2609fd89" group="com.github.shyiko.klob"/>
-         <trusted-key id="47504b76cf89c15c0512d9afe16ab52d79fd224f" group="com.google.api.grpc"/>
+         <trusted-key id="47504b76cf89c15c0512d9afe16ab52d79fd224f">
+            <trusting group="com.google.api"/>
+            <trusting group="com.google.api-client"/>
+            <trusting group="com.google.api.grpc"/>
+            <trusting group="com.google.apis"/>
+            <trusting group="com.google.auth"/>
+            <trusting group="com.google.cloud"/>
+            <trusting group="com.google.http-client"/>
+            <trusting group="com.google.oauth-client"/>
+         </trusted-key>
          <trusted-key id="47586a1b75ef0de5" group="com.squareup.wire"/>
          <trusted-key id="475f3b8e59e6e63aa78067482c7b12f2a511e325" group="org.slf4j"/>
          <trusted-key id="476634a4694e716a" group="com.googlecode.java-diff-utils"/>
@@ -176,6 +183,7 @@
          <trusted-key id="5ed22f661bbf0acc" group="com.almworks.sqlite4java"/>
          <trusted-key id="5f7786df73e61f56" group="com.google.devtools.ksp"/>
          <trusted-key id="5fa41c402006eac55d72aafd99ce9d9f22dc5c99" group="org.json"/>
+         <trusted-key id="600ea202b1ec682f4a788e5aac7a514bc9f9bb70" group="io.opencensus"/>
          <trusted-key id="6214760097dc5cfad0175ac2c9fbaa83a8753994">
             <trusting group="com.fasterxml"/>
             <trusting group="com.fasterxml.jackson"/>
@@ -198,6 +206,7 @@
             <trusting group="com.google.jimfs"/>
             <trusting group="^com[.]google($|([.].*))" regex="true"/>
          </trusted-key>
+         <trusted-key id="696b6199a2a9d8c29ce78cc0d041cad2e452550f" group="com.google.protobuf"/>
          <trusted-key id="6a65176a0fb1cd0b" group="org.codehaus.groovy"/>
          <trusted-key id="6ccc36cc6c69fc17" group="com.google"/>
          <trusted-key id="6dd3b8c64ef75253beb2c53ad908a43fb7ec07ac">
@@ -252,6 +261,11 @@
          </trusted-key>
          <trusted-key id="8858d45be9b276802318155b96fb9db219f3338d" group="kr.motd.maven"/>
          <trusted-key id="88bb19a33a18445f" group="net.ltgt.gradle.incap"/>
+         <trusted-key id="8a10792983023d5d14c93b488d7f1bec1e2ecae7">
+            <trusting group="com.fasterxml"/>
+            <trusting group="com.fasterxml.jackson"/>
+            <trusting group="com.fasterxml.jackson.core"/>
+         </trusted-key>
          <trusted-key id="8f9a3c6d105b9f57844a721d79e193516be7998f" group="org.dom4j" name="dom4j"/>
          <trusted-key id="908366594e746bf3c449f5622be5d98f751f4136" group="org.pcollections" name="pcollections"/>
          <trusted-key id="90ee19787a7bcf6fd37a1e9180c08b1c29100955">
@@ -310,7 +324,10 @@
          <trusted-key id="afcc4c7594d09e2182c60e0f7a01b0f236e5430f" group="com.google.code.gson"/>
          <trusted-key id="b02335aa54ccf21e52bbf9abd9c565aa72ba2fdd" group="io.grpc"/>
          <trusted-key id="b252e5789636134a311e4463971b04f56669b805" group="com.google.jsilver" name="jsilver"/>
-         <trusted-key id="b41089a2da79b0fa5810252872385ff0af338d52" group="org.threeten" name="threeten-extra"/>
+         <trusted-key id="b41089a2da79b0fa5810252872385ff0af338d52">
+            <trusting group="org.threeten" name="threeten-extra"/>
+            <trusting group="org.threeten" name="threetenbp"/>
+         </trusted-key>
          <trusted-key id="b47034c19c9b1f3dc3702f8d476634a4694e716a" group="com.googlecode.java-diff-utils" name="diffutils"/>
          <trusted-key id="b4c70893b62babe8" group="org.apache.logging.log4j"/>
          <trusted-key id="b57bd58ef6d0a713" group="com.google.protobuf"/>
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
index 0882ee3..789386b 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
@@ -16,6 +16,7 @@
 package androidx.health.connect.client.impl.converters.datatype
 
 import androidx.health.connect.client.records.ActiveCaloriesBurned
+import androidx.health.connect.client.records.ActiveEnergyBurned
 import androidx.health.connect.client.records.ActivityEvent
 import androidx.health.connect.client.records.ActivityLap
 import androidx.health.connect.client.records.ActivitySession
@@ -74,6 +75,7 @@
 private val ALL_RECORDS_TYPES =
     setOf(
         ActiveCaloriesBurned::class,
+        ActiveEnergyBurned::class,
         ActivityEvent::class,
         ActivityLap::class,
         ActivitySession::class,
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
index e1af71d..4eb318e 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -16,6 +16,7 @@
 package androidx.health.connect.client.impl.converters.records
 
 import androidx.health.connect.client.records.ActiveCaloriesBurned
+import androidx.health.connect.client.records.ActiveEnergyBurned
 import androidx.health.connect.client.records.ActivityEvent
 import androidx.health.connect.client.records.ActivityLap
 import androidx.health.connect.client.records.ActivitySession
@@ -358,6 +359,15 @@
                     endZoneOffset = endZoneOffset,
                     metadata = metadata
                 )
+            "ActiveEnergyBurned" ->
+                ActiveEnergyBurned(
+                    energyKcal = getDouble("energy"),
+                    startTime = startTime,
+                    startZoneOffset = startZoneOffset,
+                    endTime = endTime,
+                    endZoneOffset = endZoneOffset,
+                    metadata = metadata
+                )
             "ActivityEvent" ->
                 ActivityEvent(
                     eventType = getEnum("eventType") ?: "",
@@ -478,6 +488,7 @@
             "Repetitions" ->
                 Repetitions(
                     count = getLong("count"),
+                    type = getEnum("type") ?: "",
                     startTime = startTime,
                     startZoneOffset = startZoneOffset,
                     endTime = endTime,
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
index eb883bb..02b3c01 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
@@ -16,6 +16,7 @@
 package androidx.health.connect.client.impl.converters.records
 
 import androidx.health.connect.client.records.ActiveCaloriesBurned
+import androidx.health.connect.client.records.ActiveEnergyBurned
 import androidx.health.connect.client.records.ActivityEvent
 import androidx.health.connect.client.records.ActivityLap
 import androidx.health.connect.client.records.ActivitySession
@@ -288,6 +289,11 @@
                 .setDataType(protoDataType("ActiveCaloriesBurned"))
                 .apply { putValues("energy", doubleVal(energyKcal)) }
                 .build()
+        is ActiveEnergyBurned ->
+            intervalProto()
+                .setDataType(protoDataType("ActiveEnergyBurned"))
+                .apply { putValues("energy", doubleVal(energyKcal)) }
+                .build()
         is ActivityEvent ->
             intervalProto()
                 .setDataType(protoDataType("ActivityEvent"))
@@ -468,7 +474,10 @@
         is Repetitions ->
             intervalProto()
                 .setDataType(protoDataType("Repetitions"))
-                .apply { putValues("count", longVal(count)) }
+                .apply {
+                    putValues("count", longVal(count))
+                    putValues("type", enumVal(type))
+                }
                 .build()
         is SleepSession ->
             intervalProto()
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/ActiveEnergyBurned.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/ActiveEnergyBurned.kt
new file mode 100644
index 0000000..20bd09a
--- /dev/null
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/ActiveEnergyBurned.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.records
+
+import androidx.annotation.RestrictTo
+import androidx.health.connect.client.metadata.Metadata
+import java.time.Instant
+import java.time.ZoneOffset
+
+/**
+ * Captures the estimated active energy burned by the user (in kilocalories), excluding basal
+ * metabolic rate (BMR). Each record represents the total kilocalories burned over a time interval,
+ * so both the start and end times should be set.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class ActiveEnergyBurned(
+    /** Energy in kilocalories. Required field. Valid range: 0-1000000. */
+    public val energyKcal: Double,
+    override val startTime: Instant,
+    override val startZoneOffset: ZoneOffset?,
+    override val endTime: Instant,
+    override val endZoneOffset: ZoneOffset?,
+    override val metadata: Metadata = Metadata.EMPTY,
+) : IntervalRecord {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ActiveEnergyBurned) return false
+
+        if (energyKcal != other.energyKcal) return false
+        if (startTime != other.startTime) return false
+        if (startZoneOffset != other.startZoneOffset) return false
+        if (endTime != other.endTime) return false
+        if (endZoneOffset != other.endZoneOffset) return false
+        if (metadata != other.metadata) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = 0
+        result = 31 * result + energyKcal.hashCode()
+        result = 31 * result + (startZoneOffset?.hashCode() ?: 0)
+        result = 31 * result + endTime.hashCode()
+        result = 31 * result + (endZoneOffset?.hashCode() ?: 0)
+        result = 31 * result + metadata.hashCode()
+        return result
+    }
+}
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/RepetitionActivityTypes.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/RepetitionActivityTypes.kt
new file mode 100644
index 0000000..0e75226
--- /dev/null
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/RepetitionActivityTypes.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 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.records
+
+import androidx.annotation.RestrictTo
+import androidx.annotation.StringDef
+
+/** Activity types supported by repetitions. */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public object RepetitionActivityTypes {
+    const val ARM_CURL = "arm_curl"
+    const val BACK_EXTENSION = "back_extension"
+    const val BALL_SLAM = "ball_slam"
+    const val BENCH_PRESS = "bench_press"
+    const val BURPEE = "burpee"
+    const val CRUNCH = "crunch"
+    const val DEADLIFT = "deadlift"
+    const val DOUBLE_ARM_TRICEPS_EXTENSION = "double_arm_triceps_extension"
+    const val DUMBBELL_ROW = "dumbbell_row"
+    const val FRONT_RAISE = "front_raise"
+    const val HIP_THRUST = "hip_thrust"
+    const val HULA_HOOP = "hula_hoop"
+    const val JUMPING_JACK = "jumping_jack"
+    const val JUMP_ROPE = "jump_rope"
+    const val KETTLEBELL_SWING = "kettlebell_swing"
+    const val LATERAL_RAISE = "lateral_raise"
+    const val LAT_PULL_DOWN = "lat_pull_down"
+    const val LEG_CURL = "leg_curl"
+    const val LEG_EXTENSION = "leg_extension"
+    const val LEG_PRESS = "leg_press"
+    const val LEG_RAISE = "leg_raise"
+    const val LUNGE = "lunge"
+    const val MOUNTAIN_CLIMBER = "mountain_climber"
+    const val PLANK = "plank"
+    const val PULL_UP = "pull_up"
+    const val PUNCH = "punch"
+    const val SHOULDER_PRESS = "shoulder_press"
+    const val SINGLE_ARM_TRICEPS_EXTENSION = "single_arm_triceps_extension"
+    const val SIT_UP = "sit_up"
+    const val SQUAT = "squat"
+}
+
+/**
+ * Activity types supported by repetitions.
+ * @suppress
+ */
+@Retention(AnnotationRetention.SOURCE)
+@StringDef(
+    value =
+        [
+            RepetitionActivityTypes.ARM_CURL,
+            RepetitionActivityTypes.BACK_EXTENSION,
+            RepetitionActivityTypes.BALL_SLAM,
+            RepetitionActivityTypes.BENCH_PRESS,
+            RepetitionActivityTypes.BURPEE,
+            RepetitionActivityTypes.CRUNCH,
+            RepetitionActivityTypes.DEADLIFT,
+            RepetitionActivityTypes.DOUBLE_ARM_TRICEPS_EXTENSION,
+            RepetitionActivityTypes.DUMBBELL_ROW,
+            RepetitionActivityTypes.FRONT_RAISE,
+            RepetitionActivityTypes.HIP_THRUST,
+            RepetitionActivityTypes.HULA_HOOP,
+            RepetitionActivityTypes.JUMPING_JACK,
+            RepetitionActivityTypes.JUMP_ROPE,
+            RepetitionActivityTypes.KETTLEBELL_SWING,
+            RepetitionActivityTypes.LATERAL_RAISE,
+            RepetitionActivityTypes.LAT_PULL_DOWN,
+            RepetitionActivityTypes.LEG_CURL,
+            RepetitionActivityTypes.LEG_EXTENSION,
+            RepetitionActivityTypes.LEG_PRESS,
+            RepetitionActivityTypes.LEG_RAISE,
+            RepetitionActivityTypes.LUNGE,
+            RepetitionActivityTypes.MOUNTAIN_CLIMBER,
+            RepetitionActivityTypes.PLANK,
+            RepetitionActivityTypes.PULL_UP,
+            RepetitionActivityTypes.PUNCH,
+            RepetitionActivityTypes.SHOULDER_PRESS,
+            RepetitionActivityTypes.SINGLE_ARM_TRICEPS_EXTENSION,
+            RepetitionActivityTypes.SIT_UP,
+            RepetitionActivityTypes.SQUAT]
+)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+annotation class RepetitionActivityType
diff --git a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/Repetitions.kt b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/Repetitions.kt
index da712fd..c7309f5 100644
--- a/health/health-connect-client/src/main/java/androidx/health/connect/client/records/Repetitions.kt
+++ b/health/health-connect-client/src/main/java/androidx/health/connect/client/records/Repetitions.kt
@@ -25,6 +25,10 @@
 public class Repetitions(
     /** Count. Required field. Valid range: 1-1000000. */
     public val count: Long,
+    /**
+     * Type of activity being repeated. Optional field. Allowed values: [RepetitionActivityType].
+     */
+    @property:RepetitionActivityType public val type: String,
     override val startTime: Instant,
     override val startZoneOffset: ZoneOffset?,
     override val endTime: Instant,
@@ -36,6 +40,7 @@
         if (other !is Repetitions) return false
 
         if (count != other.count) return false
+        if (type != other.type) return false
         if (startTime != other.startTime) return false
         if (startZoneOffset != other.startZoneOffset) return false
         if (endTime != other.endTime) return false
@@ -48,6 +53,7 @@
     override fun hashCode(): Int {
         var result = 0
         result = 31 * result + count.hashCode()
+        result = 31 * result + type.hashCode()
         result = 31 * result + (startZoneOffset?.hashCode() ?: 0)
         result = 31 * result + endTime.hashCode()
         result = 31 * result + (endZoneOffset?.hashCode() ?: 0)
diff --git a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
index c9adb81..fdd5da4 100644
--- a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
@@ -31,6 +31,7 @@
 import androidx.health.connect.client.metadata.Device
 import androidx.health.connect.client.metadata.Metadata
 import androidx.health.connect.client.permission.Permission
+import androidx.health.connect.client.permission.Permission.Companion.createReadPermission
 import androidx.health.connect.client.records.ActiveCaloriesBurned
 import androidx.health.connect.client.records.Nutrition
 import androidx.health.connect.client.records.Steps
@@ -182,9 +183,7 @@
     @Test
     fun getGrantedPermissions_none() = runTest {
         val deferred = async {
-            healthConnectClient.getGrantedPermissions(
-                setOf(Permission.createReadPermission(Steps::class))
-            )
+            healthConnectClient.getGrantedPermissions(setOf(createReadPermission(Steps::class)))
         }
 
         advanceUntilIdle()
@@ -205,16 +204,14 @@
             )
         )
         val deferred = async {
-            healthConnectClient.getGrantedPermissions(
-                setOf(Permission.createReadPermission(Steps::class))
-            )
+            healthConnectClient.getGrantedPermissions(setOf(createReadPermission(Steps::class)))
         }
 
         advanceUntilIdle()
         waitForMainLooperIdle()
 
         val response = deferred.await()
-        assertThat(response).containsExactly(Permission.createReadPermission(Steps::class))
+        assertThat(response).containsExactly(createReadPermission(Steps::class))
     }
 
     @Test
diff --git a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index 24f4b37..a487acf 100644
--- a/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/health-connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -19,6 +19,7 @@
 import androidx.health.connect.client.metadata.Device
 import androidx.health.connect.client.metadata.Metadata
 import androidx.health.connect.client.records.ActiveCaloriesBurned
+import androidx.health.connect.client.records.ActiveEnergyBurned
 import androidx.health.connect.client.records.ActivityEvent
 import androidx.health.connect.client.records.ActivityEventTypes
 import androidx.health.connect.client.records.ActivityLap
@@ -63,6 +64,7 @@
 import androidx.health.connect.client.records.OvulationTestResults
 import androidx.health.connect.client.records.OxygenSaturation
 import androidx.health.connect.client.records.Power
+import androidx.health.connect.client.records.RepetitionActivityTypes
 import androidx.health.connect.client.records.Repetitions
 import androidx.health.connect.client.records.RespiratoryRate
 import androidx.health.connect.client.records.RestingHeartRate
@@ -631,6 +633,21 @@
     }
 
     @Test
+    fun testActiveEnergyBurned() {
+        val data =
+            ActiveEnergyBurned(
+                energyKcal = 1.0,
+                startTime = START_TIME,
+                startZoneOffset = START_ZONE_OFFSET,
+                endTime = END_TIME,
+                endZoneOffset = END_ZONE_OFFSET,
+                metadata = TEST_METADATA
+            )
+
+        assertThat(toRecord(data.toProto())).isEqualTo(data)
+    }
+
+    @Test
     fun testActivityEvent() {
         val data =
             ActivityEvent(
@@ -800,6 +817,7 @@
         val data =
             Repetitions(
                 count = 1,
+                type = RepetitionActivityTypes.JUMPING_JACK,
                 startTime = START_TIME,
                 startZoneOffset = START_ZONE_OFFSET,
                 endTime = END_TIME,
diff --git a/health/health-services-client/build.gradle b/health/health-services-client/build.gradle
index cb61e51..467e4aa 100644
--- a/health/health-services-client/build.gradle
+++ b/health/health-services-client/build.gradle
@@ -30,7 +30,7 @@
     implementation(libs.guavaListenableFuture)
     implementation(libs.guavaAndroid)
     implementation("androidx.core:core-ktx:1.5.0-alpha04")
-    implementation "com.google.protobuf:protobuf-javalite:3.10.0"
+    implementation(libs.protobufLite)
 }
 
 android {
@@ -45,7 +45,7 @@
 
 protobuf {
     protoc {
-        artifact = "com.google.protobuf:protoc:3.10.0"
+        artifact = libs.protobufCompiler.get()
     }
 
     // Generates the java proto-lite code for the protos in this project. See
diff --git a/libraryversions.toml b/libraryversions.toml
index cfe4eac..bb41f7d 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,5 +1,5 @@
 [versions]
-ACTIVITY = "1.5.0-beta01"
+ACTIVITY = "1.5.0-rc01"
 ADS_IDENTIFIER = "1.0.0-alpha05"
 ANNOTATION = "1.4.0-alpha03"
 ANNOTATION_EXPERIMENTAL = "1.3.0-alpha01"
@@ -22,7 +22,7 @@
 COMPOSE_MATERIAL3 = "1.0.0-alpha10"
 CONTENTPAGER = "1.1.0-alpha01"
 COORDINATORLAYOUT = "1.3.0-alpha01"
-CORE = "1.8.0-beta01"
+CORE = "1.8.0-rc01"
 CORE_ANIMATION = "1.0.0-beta01"
 CORE_ANIMATION_TESTING = "1.0.0-beta01"
 CORE_APPDIGEST = "1.0.0-alpha01"
@@ -67,7 +67,7 @@
 LEANBACK_TAB = "1.1.0-beta01"
 LEGACY = "1.1.0-alpha01"
 LIBYUV = "0.1.0-dev01"
-LIFECYCLE = "2.5.0-beta01"
+LIFECYCLE = "2.5.0-rc01"
 LIFECYCLE_EXTENSIONS = "2.2.0"
 LOADER = "1.2.0-alpha01"
 MEDIA = "1.7.0-alpha01"
@@ -88,7 +88,7 @@
 REMOTECALLBACK = "1.0.0-alpha02"
 RESOURCEINSPECTION = "1.1.0-alpha01"
 ROOM = "2.5.0-alpha02"
-SAVEDSTATE = "1.2.0-beta01"
+SAVEDSTATE = "1.2.0-rc01"
 SECURITY = "1.1.0-alpha04"
 SECURITY_APP_AUTHENTICATOR = "1.0.0-alpha03"
 SECURITY_APP_AUTHENTICATOR_TESTING = "1.0.0-alpha02"
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index 2024abe..075cb85 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -47,6 +47,7 @@
 tasks.withType(KotlinCompile).configureEach {
     kotlinOptions {
         freeCompilerArgs += [
+                "-Xjvm-default=all",
                 "-opt-in=kotlin.RequiresOptIn",
                 "-opt-in=kotlin.contracts.ExperimentalContracts",
                 "-opt-in=androidx.room.compiler.processing.ExperimentalProcessingApi"
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/JavaImplProcessingStep.java b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/JavaImplProcessingStep.java
new file mode 100644
index 0000000..f613a8f
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/JavaImplProcessingStep.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 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.room.compiler.processing;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Not a JUnit test but a test nonetheless, it verifies that a Java source implementation of
+ * XProcessingStep does not need to override interface methods with a body since Kotlin should
+ * generate a default method, i.e. it verifies Kotlin's jvm-default is turned ON.
+ */
+@SuppressWarnings("deprecation") // On purpose overriding deprecated method.
+public class JavaImplProcessingStep implements XProcessingStep {
+    @NonNull
+    @Override
+    public Set<XElement> process(@NonNull XProcessingEnv env,
+            @NonNull Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) {
+        return XProcessingStep.super.process(env, elementsByAnnotation);
+    }
+
+    @Override
+    public void processOver(@NonNull XProcessingEnv env,
+            @NonNull Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) {
+        XProcessingStep.super.processOver(env, elementsByAnnotation);
+    }
+
+    @NonNull
+    @Override
+    public Set<String> annotations() {
+        return Collections.emptySet();
+    }
+}
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index e80685d..9a4310c 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -286,6 +286,7 @@
 tasks.withType(KotlinCompile).configureEach {
     kotlinOptions {
         freeCompilerArgs += [
+                "-Xjvm-default=all",
                 "-opt-in=kotlin.RequiresOptIn",
                 "-opt-in=kotlin.contracts.ExperimentalContracts",
                 "-opt-in=androidx.room.compiler.processing.ExperimentalProcessingApi"
diff --git a/settings.gradle b/settings.gradle
index f582cf6..8bd3b85 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -23,6 +23,7 @@
     dependencies {
         classpath("com.gradle:gradle-enterprise-gradle-plugin:3.9")
         classpath("com.gradle:common-custom-user-data-gradle-plugin:1.6.5")
+        classpath("androidx.build.gradle.gcpbuildcache:gcpbuildcache:1.0.0-alpha02")
     }
 }
 
@@ -59,6 +60,7 @@
 
 apply(plugin: "com.gradle.enterprise")
 apply(plugin: "com.gradle.common-custom-user-data-gradle-plugin")
+apply(plugin: "androidx.build.gradle.gcpbuildcache")
 
 gradleEnterprise {
     server = "https://ge.androidx.dev"
diff --git a/sqlite/integration-tests/inspection-room-testapp/build.gradle b/sqlite/integration-tests/inspection-room-testapp/build.gradle
index af17c84..a4048b05 100644
--- a/sqlite/integration-tests/inspection-room-testapp/build.gradle
+++ b/sqlite/integration-tests/inspection-room-testapp/build.gradle
@@ -31,7 +31,7 @@
     androidTestImplementation(project(":room:room-runtime"))
     androidTestImplementation(project(":sqlite:sqlite-inspection"))
     androidTestImplementation(project(":inspection:inspection-testing"))
-    androidTestImplementation("com.google.protobuf:protobuf-javalite:3.10.0")
+    androidTestImplementation(libs.protobufLite)
     kaptAndroidTest(project(":room:room-compiler"))
 
 }
diff --git a/sqlite/integration-tests/inspection-sqldelight-testapp/build.gradle b/sqlite/integration-tests/inspection-sqldelight-testapp/build.gradle
index 1245b22..02a31e9 100644
--- a/sqlite/integration-tests/inspection-sqldelight-testapp/build.gradle
+++ b/sqlite/integration-tests/inspection-sqldelight-testapp/build.gradle
@@ -31,7 +31,7 @@
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(project(":sqlite:sqlite-inspection"))
     androidTestImplementation(project(":inspection:inspection-testing"))
-    androidTestImplementation("com.google.protobuf:protobuf-javalite:3.10.0")
+    androidTestImplementation(libs.protobufLite)
 }
 
 android {
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Button.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Button.kt
index 241330a..573900c 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Button.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Button.kt
@@ -81,7 +81,7 @@
  * appearance / behavior of this Button in different [Interaction]s.
  */
 @Composable
-fun Button(
+public fun Button(
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
@@ -156,7 +156,7 @@
  * appearance / behavior of this Button in different [Interaction]s.
  */
 @Composable
-fun CompactButton(
+public fun CompactButton(
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
@@ -202,14 +202,14 @@
  * in a secondary styled [Button].
  */
 @Stable
-interface ButtonColors {
+public interface ButtonColors {
     /**
      * Represents the background color for this button, depending on [enabled].
      *
      * @param enabled whether the button is enabled
      */
     @Composable
-    fun backgroundColor(enabled: Boolean): State<Color>
+    public fun backgroundColor(enabled: Boolean): State<Color>
 
     /**
      * Represents the content color for this button, depending on [enabled].
@@ -217,13 +217,22 @@
      * @param enabled whether the button is enabled
      */
     @Composable
-    fun contentColor(enabled: Boolean): State<Color>
+    public fun contentColor(enabled: Boolean): State<Color>
 }
 
 /**
  * Contains the default values used by [Button].
  */
 public object ButtonDefaults {
+    /**
+     * Creates a [ButtonColors] that represents the default background and content colors for a
+     * primary [Button]. Primary buttons have a colored background with a contrasting content color.
+     * If a button is disabled then the color will have an alpha ([ContentAlpha.disabled]) value
+     * applied.
+     *
+     * @param backgroundColor The background color of this [Button] when enabled
+     * @param contentColor The content color of this [Button] when enabled
+     */
     @Composable
     public fun primaryButtonColors(
         backgroundColor: Color = MaterialTheme.colors.primary,
@@ -235,6 +244,15 @@
         )
     }
 
+    /**
+     * Creates a [ButtonColors] that represents the default background and content colors for a
+     * secondary [Button]. Secondary buttons have a muted background with a contrasting content
+     * color. If a button is disabled then the color will have an alpha ([ContentAlpha.disabled])
+     * value applied.
+     *
+     * @param backgroundColor The background color of this [Button] when enabled
+     * @param contentColor The content color of this [Button] when enabled
+     */
     @Composable
     public fun secondaryButtonColors(
         backgroundColor: Color = MaterialTheme.colors.surface,
@@ -246,6 +264,13 @@
         )
     }
 
+    /**
+     * Creates a [ButtonColors] that represents the content colors for
+     * an icon-only [Button]. If a button is disabled then the color will have an alpha
+     * ([ContentAlpha.disabled]) value applied.
+     *
+     * @param contentColor The content color of this [Button] when enabled
+     */
     @Composable
     public fun iconButtonColors(
         contentColor: Color = MaterialTheme.colors.onSurface,
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleButton.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleButton.kt
index c57c6d7..26279b2 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleButton.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ToggleButton.kt
@@ -77,7 +77,7 @@
  * @param content The icon, image or text to be drawn inside the toggle button.
  */
 @Composable
-fun ToggleButton(
+public fun ToggleButton(
     checked: Boolean,
     onCheckedChange: (Boolean) -> Unit,
     modifier: Modifier = Modifier,
@@ -124,7 +124,7 @@
  * primary-styled for a checked toggle button and surface-styled for unchecked.
  */
 @Stable
-interface ToggleButtonColors {
+public interface ToggleButtonColors {
     /**
      * Represents the background color for this toggle button, depending on [enabled] and [checked].
      *
@@ -132,7 +132,7 @@
      * @param checked whether the toggle button is checked
      */
     @Composable
-    fun backgroundColor(enabled: Boolean, checked: Boolean): State<Color>
+    public fun backgroundColor(enabled: Boolean, checked: Boolean): State<Color>
 
     /**
      * Represents the content color for this toggle button, depending on [enabled] and [checked].
@@ -141,7 +141,7 @@
      * @param checked whether the toggle button is checked
      */
     @Composable
-    fun contentColor(enabled: Boolean, checked: Boolean): State<Color>
+    public fun contentColor(enabled: Boolean, checked: Boolean): State<Color>
 }
 
 /**
diff --git a/wear/tiles/tiles-renderer/build.gradle b/wear/tiles/tiles-renderer/build.gradle
index c0d8dab..f7c230b 100644
--- a/wear/tiles/tiles-renderer/build.gradle
+++ b/wear/tiles/tiles-renderer/build.gradle
@@ -44,7 +44,7 @@
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
-    androidTestImplementation("com.google.protobuf:protobuf-java:3.10.0")
+    androidTestImplementation(libs.protobuf)
 
     // I'm not 100% sure why, but androidTestImplementation doesn't appear to use the standard
     // results of a project build. This leads to it not using the shadow configuration from
@@ -93,7 +93,7 @@
 
 protobuf {
     protoc {
-        artifact = "com.google.protobuf:protoc:3.10.0"
+        artifact = libs.protobufCompiler.get()
     }
 
     // Generates the java proto-lite code for the protos in this project. See