Merge "Update detectDragGestures to be re-used in Draggables." into androidx-main
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
index c82ead6..d184d2f 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
@@ -20,7 +20,7 @@
 
 // Minimum AGP version required
 internal val MIN_AGP_VERSION_REQUIRED_INCLUSIVE = AndroidPluginVersion(8, 0, 0)
-internal val MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE = AndroidPluginVersion(8, 5, 0).alpha(1)
+internal val MAX_AGP_VERSION_RECOMMENDED_EXCLUSIVE = AndroidPluginVersion(8, 6, 0).alpha(1)
 
 // Prefix for the build type baseline profile
 internal const val BUILD_TYPE_BASELINE_PROFILE_PREFIX = "nonMinified"
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
index 34493db..fa4a65e 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
@@ -17,10 +17,11 @@
 package androidx.compose.foundation.lazy.staggeredgrid
 
 import androidx.compose.foundation.AutoTestFrameClock
-import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -34,11 +35,11 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
-@OptIn(ExperimentalFoundationApi::class)
 @MediumTest
 @RunWith(Parameterized::class)
 class LazyStaggeredGridScrollTest(
@@ -59,20 +60,23 @@
     private var itemSizeDp = Dp.Unspecified
     private val itemCount = 100
 
+    @Before
+    fun initSizes() {
+        itemSizeDp = with(rule.density) {
+            itemSizePx.toDp()
+        }
+    }
+
     fun setContent(
         containerSizePx: Int = itemSizePx * 5,
         afterContentPaddingPx: Int = 0
     ) {
-        itemSizeDp = with(rule.density) {
-            itemSizePx.toDp()
-        }
         rule.setContent {
             state = rememberLazyStaggeredGridState()
             with(rule.density) {
                 TestContent(containerSizePx.toDp(), afterContentPaddingPx.toDp())
             }
         }
-        rule.waitForIdle()
     }
 
     @Test
@@ -353,6 +357,41 @@
         }
     }
 
+    @Test
+    fun scrollBy_emptyItemWithSpacing() {
+        val state = LazyStaggeredGridState()
+        val spacingDp = with(rule.density) { 10.toDp() }
+        rule.setContent {
+            LazyStaggeredGrid(
+                lanes = 1,
+                state = state,
+                mainAxisSpacing = spacingDp,
+                modifier = Modifier.size(itemSizeDp),
+            ) {
+                item {
+                    Box(Modifier.size(itemSizeDp).testTag("0").debugBorder())
+                }
+                item { } // empty item surrounded by spacings
+                item {
+                    Box(Modifier.size(itemSizeDp).testTag("2").debugBorder())
+                }
+            }
+        }
+        rule.runOnIdle {
+            runBlocking(AutoTestFrameClock()) {
+                // empty item introduces two spacings 10 pixels each
+                // so after this operation item 2 is right at the edge of the viewport
+                state.scrollBy(20f)
+                // then we do some extra scrolling to make item 2 visible
+                state.scrollBy(20f)
+            }
+        }
+        rule.onNodeWithTag("2")
+            .assertMainAxisStartPositionInRootIsEqualTo(
+                itemSizeDp - with(rule.density) { 20.toDp() }
+            )
+    }
+
     @Composable
     private fun TestContent(containerSizeDp: Dp, afterContentPaddingDp: Dp) {
         // |-|-|
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt
index d1097a6..4d0efc2 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/AndroidExternalSurfaceTest.kt
@@ -273,6 +273,13 @@
     }
 
     @Test
+    @Ignore(
+        """Despite best efforts in screenshotToImage(), this test is too flaky currently.
+            |Since this test only tests that the `zOrder` parameter is properly passed
+            |to the underlying SurfaceView, we don't lose much by disabling it.
+            |This test should be more robust on API level 34 using the window
+            |screenshot API."""
+    )
     fun testZOrderDefault() {
         val latch = CountDownLatch(FrameCount)
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt
index 79686c2..3de2da2 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/HandwritingDetectorTest.kt
@@ -16,8 +16,12 @@
 
 package androidx.compose.foundation.text.input
 
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.safeContentPadding
+import androidx.compose.foundation.text.handwriting.HandwritingBoundsVerticalOffset
 import androidx.compose.foundation.text.handwriting.handwritingDetector
 import androidx.compose.foundation.text.handwriting.isStylusHandwritingSupported
 import androidx.compose.foundation.text.performStylusClick
@@ -48,7 +52,9 @@
 
     private val imm = FakeInputMethodManager()
 
-    private val tag = "detector"
+    private val detectorTag = "detector"
+    private val insideSpacerTag = "inside"
+    private val outsideSpacerTag = "outside"
 
     private var callbackCount = 0
 
@@ -62,39 +68,72 @@
         callbackCount = 0
 
         rule.setContent {
-            Spacer(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .handwritingDetector { callbackCount++ }
-                    .testTag(tag)
-            )
+            Column(Modifier.safeContentPadding()) {
+                Spacer(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .height(HandwritingBoundsVerticalOffset)
+                        .handwritingDetector { callbackCount++ }
+                        .testTag(detectorTag)
+                )
+                // This spacer is within the extended handwriting bounds of the detector
+                Spacer(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .height(HandwritingBoundsVerticalOffset)
+                        .testTag(insideSpacerTag)
+                )
+                // This spacer is outside the extended handwriting bounds of the detector
+                Spacer(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .height(HandwritingBoundsVerticalOffset)
+                        .testTag(outsideSpacerTag)
+                )
+            }
         }
     }
 
     @Test
     fun detector_handwriting_preparesDelegation() {
-        rule.onNodeWithTag(tag).performStylusHandwriting()
+        rule.onNodeWithTag(detectorTag).performStylusHandwriting()
 
         assertHandwritingDelegationPrepared()
     }
 
     @Test
+    fun detector_handwritingInExtendedBounds_preparesDelegation() {
+        // This spacer is within the extended handwriting bounds of the detector
+        rule.onNodeWithTag(insideSpacerTag).performStylusHandwriting()
+
+        assertHandwritingDelegationPrepared()
+    }
+
+    @Test
+    fun detector_handwritingOutsideExtendedBounds_notPreparesDelegation() {
+        // This spacer is outside the extended handwriting bounds of the detector
+        rule.onNodeWithTag(outsideSpacerTag).performStylusHandwriting()
+
+        assertHandwritingDelegationNotPrepared()
+    }
+
+    @Test
     fun detector_click_notPreparesDelegation() {
-        rule.onNodeWithTag(tag).performStylusClick()
+        rule.onNodeWithTag(detectorTag).performStylusClick()
 
         assertHandwritingDelegationNotPrepared()
     }
 
     @Test
     fun detector_longClick_notPreparesDelegation() {
-        rule.onNodeWithTag(tag).performStylusLongClick()
+        rule.onNodeWithTag(detectorTag).performStylusLongClick()
 
         assertHandwritingDelegationNotPrepared()
     }
 
     @Test
     fun detector_longPressAndDrag_notPreparesDelegation() {
-        rule.onNodeWithTag(tag).performStylusLongPressAndDrag()
+        rule.onNodeWithTag(detectorTag).performStylusLongPressAndDrag()
 
         assertHandwritingDelegationNotPrepared()
     }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt
index a6ac8fc..ed44b5e 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/handwriting/HandwritingDetector.android.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.text.handwriting
 
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.text.input.internal.ComposeInputMethodManager
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.PointerEvent
@@ -52,7 +53,15 @@
  * @sample androidx.compose.foundation.samples.HandwritingDetectorSample
  */
 fun Modifier.handwritingDetector(callback: () -> Unit) =
-    if (isStylusHandwritingSupported) then(HandwritingDetectorElement(callback)) else this
+    if (isStylusHandwritingSupported) {
+        then(HandwritingDetectorElement(callback))
+            .padding(
+                horizontal = HandwritingBoundsHorizontalOffset,
+                vertical = HandwritingBoundsVerticalOffset
+            )
+    } else {
+        this
+    }
 
 private class HandwritingDetectorElement(
     private val callback: () -> Unit
@@ -92,9 +101,9 @@
         pointerInputNode.onCancelPointerInput()
     }
 
-    val pointerInputNode = delegate(StylusHandwritingNode {
+    val pointerInputNode = delegate(StylusHandwritingNodeWithNegativePadding {
         callback()
         composeImm.prepareStylusHandwritingDelegation()
-        return@StylusHandwritingNode true
+        return@StylusHandwritingNodeWithNegativePadding true
     })
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index 5928a6e..886f376 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -960,10 +960,6 @@
         val mainAxisOffset = itemScrollOffsets.maxInRange(spanRange)
         val crossAxisOffset = resolvedSlots.positions[laneIndex]
 
-        if (item.placeablesCount == 0) {
-            // nothing to place, ignore spacings
-            continue
-        }
         item.position(
             mainAxis = mainAxisOffset,
             crossAxis = crossAxisOffset,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
index 381dbbf..4909b8f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/handwriting/StylusHandwriting.kt
@@ -89,7 +89,7 @@
  * same.
  * Note: this node is a temporary solution, ideally we don't need it.
  */
-private class StylusHandwritingNodeWithNegativePadding(
+internal class StylusHandwritingNodeWithNegativePadding(
     onHandwritingSlopExceeded: () -> Boolean
 ) : StylusHandwritingNode(onHandwritingSlopExceeded), LayoutModifierNode {
     override fun MeasureScope.measure(
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
index cccbd34..0eadc27 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ b/compose/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -266,6 +266,7 @@
             </intent-filter>
             <intent-filter>
                 <action android:name="androidx.compose.integration.macrobenchmark.target.CROSSFADE_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
 	</activity>
 
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index 324023b..01a08c0 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -136,3 +136,15 @@
 android {
     namespace "androidx.compose.material.icons.extended"
 }
+
+afterEvaluate {
+    // Workaround for b/337776938
+    if (!project.hasProperty("android.injected.invoked.from.ide")) {
+        tasks.named("lintAnalyzeDebugAndroidTest").configure {
+            it.dependsOn("generateTestFilesDebugAndroidTest")
+        }
+        tasks.named("generateDebugAndroidTestLintModel").configure {
+            it.dependsOn("generateTestFilesDebugAndroidTest")
+        }
+    }
+}
diff --git a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt
index 0f50354..0df30f3 100644
--- a/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt
+++ b/compose/material/material/src/androidInstrumentedTest/kotlin/androidx/compose/material/SnackbarTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.shape.CutCornerShape
 import androidx.compose.runtime.CompositionLocalProvider
@@ -95,6 +96,29 @@
     }
 
     @Test
+    fun snackbar_emptyContent() {
+        rule.setMaterialContent {
+            Snackbar {
+                // empty content should not crash
+            }
+        }
+    }
+
+    @Test
+    fun snackbar_noTextInContent() {
+        val snackbarHeight = 48.dp
+        val contentSize = 20.dp
+        rule.setMaterialContent {
+            Snackbar {
+                // non-text content should not crash
+                Box(Modifier.testTag("content").size(contentSize))
+            }
+        }
+        rule.onNodeWithTag("content")
+            .assertTopPositionInRootIsEqualTo((snackbarHeight - contentSize) / 2)
+    }
+
+    @Test
     fun snackbar_shortTextOnly_defaultSizes() {
         val snackbar = rule.setMaterialContentForSizeAssertions(
             parentMaxWidth = 300.dp
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
index 29673c2..2e490ec 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Snackbar.kt
@@ -32,10 +32,12 @@
 import androidx.compose.ui.layout.FirstBaseline
 import androidx.compose.ui.layout.LastBaseline
 import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastForEach
 import kotlin.math.max
 
 /**
@@ -245,25 +247,42 @@
             content()
         }
     }) { measurables, constraints ->
-        require(measurables.size == 1) {
-            "text for Snackbar expected to have exactly only one child"
+        val textPlaceables = ArrayList<Placeable>(measurables.size)
+        var firstBaseline = AlignmentLine.Unspecified
+        var lastBaseline = AlignmentLine.Unspecified
+        var height = 0
+
+        measurables.fastForEach {
+            val placeable = it.measure(constraints)
+            textPlaceables.add(placeable)
+            if (placeable[FirstBaseline] != AlignmentLine.Unspecified &&
+                (firstBaseline == AlignmentLine.Unspecified ||
+                    placeable[FirstBaseline] < firstBaseline)) {
+                firstBaseline = placeable[FirstBaseline]
+            }
+            if (placeable[LastBaseline] != AlignmentLine.Unspecified &&
+                (lastBaseline == AlignmentLine.Unspecified ||
+                    placeable[LastBaseline] > lastBaseline)) {
+                lastBaseline = placeable[LastBaseline]
+            }
+            height = max(height, placeable.height)
         }
-        val textPlaceable = measurables.first().measure(constraints)
-        val firstBaseline = textPlaceable[FirstBaseline]
-        val lastBaseline = textPlaceable[LastBaseline]
-        require(firstBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
-        require(lastBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
+
+        val hasText = firstBaseline != AlignmentLine.Unspecified &&
+            lastBaseline != AlignmentLine.Unspecified
 
         val minHeight =
-            if (firstBaseline == lastBaseline) {
+            if (firstBaseline == lastBaseline || !hasText) {
                 SnackbarMinHeightOneLine
             } else {
                 SnackbarMinHeightTwoLines
             }
-        val containerHeight = max(minHeight.roundToPx(), textPlaceable.height)
+        val containerHeight = max(minHeight.roundToPx(), height)
         layout(constraints.maxWidth, containerHeight) {
-            val textPlaceY = (containerHeight - textPlaceable.height) / 2
-            textPlaceable.placeRelative(0, textPlaceY)
+            textPlaceables.fastForEach {
+                val textPlaceY = (containerHeight - it.height) / 2
+                it.placeRelative(0, textPlaceY)
+            }
         }
     }
 }
@@ -316,10 +335,10 @@
         )
 
         val firstTextBaseline = textPlaceable[FirstBaseline]
-        require(firstTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
         val lastTextBaseline = textPlaceable[LastBaseline]
-        require(lastTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
-        val isOneLine = firstTextBaseline == lastTextBaseline
+        val hasText = firstTextBaseline != AlignmentLine.Unspecified &&
+            lastTextBaseline != AlignmentLine.Unspecified
+        val isOneLine = firstTextBaseline == lastTextBaseline || !hasText
         val buttonPlaceX = constraints.maxWidth - buttonPlaceable.width
 
         val textPlaceY: Int
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
index 9aa622d..571508f 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
@@ -29,6 +29,10 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionOnScreen
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -47,6 +51,7 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -188,50 +193,68 @@
 
     @Test
     fun dismissibleNavigationDrawer_testOffset_customWidthLarger_whenOpen() {
-        val customWidth = NavigationDrawerWidth + 5.dp
+        val customWidth = NavigationDrawerWidth + 20.dp
+        val density = Density(0.5f)
+        lateinit var coords: LayoutCoordinates
         rule.setMaterialContent(lightColorScheme()) {
-            val drawerState = rememberDrawerState(DrawerValue.Open)
-            DismissibleNavigationDrawer(
-                drawerState = drawerState,
-                drawerContent = {
-                    DismissibleDrawerSheet(Modifier.width(customWidth)) {
-                        Box(
-                            Modifier
-                                .fillMaxSize()
-                                .testTag("content")
-                        )
-                    }
-                },
-                content = {}
-            )
+            // Reduce density to ensure wide drawer fits on screen
+            CompositionLocalProvider(LocalDensity provides density) {
+                val drawerState = rememberDrawerState(DrawerValue.Open)
+                DismissibleNavigationDrawer(
+                    drawerState = drawerState,
+                    drawerContent = {
+                        DismissibleDrawerSheet(Modifier.width(customWidth)) {
+                            Box(
+                                Modifier
+                                    .fillMaxSize()
+                                    .onGloballyPositioned {
+                                        coords = it
+                                    }
+                            )
+                        }
+                    },
+                    content = {}
+                )
+            }
         }
 
-        rule.onNodeWithTag("content")
-            .assertLeftPositionInRootIsEqualTo(0.dp)
+        rule.runOnIdle {
+            assertThat(coords.positionOnScreen().x).isEqualTo(0f)
+        }
     }
 
     @Test
     fun dismissibleNavigationDrawer_testOffset_customWidthLarger_whenClosed() {
-        val customWidth = NavigationDrawerWidth + 5.dp
+        val customWidth = NavigationDrawerWidth + 20.dp
+        val density = Density(0.5f)
+        lateinit var coords: LayoutCoordinates
         rule.setMaterialContent(lightColorScheme()) {
-            val drawerState = rememberDrawerState(DrawerValue.Closed)
-            DismissibleNavigationDrawer(
-                drawerState = drawerState,
-                drawerContent = {
-                    DismissibleDrawerSheet(Modifier.width(customWidth)) {
-                        Box(
-                            Modifier
-                                .fillMaxSize()
-                                .testTag("content")
-                        )
-                    }
-                },
-                content = {}
-            )
+            // Reduce density to ensure wide drawer fits on screen
+            CompositionLocalProvider(LocalDensity provides density) {
+                val drawerState = rememberDrawerState(DrawerValue.Closed)
+                DismissibleNavigationDrawer(
+                    drawerState = drawerState,
+                    drawerContent = {
+                        DismissibleDrawerSheet(Modifier.width(customWidth)) {
+                            Box(
+                                Modifier
+                                    .fillMaxSize()
+                                    .onGloballyPositioned {
+                                        coords = it
+                                    }
+                            )
+                        }
+                    },
+                    content = {}
+                )
+            }
         }
 
-        rule.onNodeWithTag("content")
-            .assertLeftPositionInRootIsEqualTo(-customWidth)
+        rule.runOnIdle {
+            with(density) {
+                assertThat(coords.positionOnScreen().x).isWithin(1f).of(-customWidth.toPx())
+            }
+        }
     }
 
     @Test
@@ -416,12 +439,16 @@
         // When the drawer state is set to Opened
         drawerState.snapTo(DrawerValue.Open)
         // Then the drawer should be opened
-        assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+        }
 
         // When the drawer state is set to Closed
         drawerState.snapTo(DrawerValue.Closed)
         // Then the drawer should be closed
-        assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+        }
     }
 
     @Test
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
index 587382f..97f3407 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
@@ -29,6 +29,10 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionOnScreen
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -49,6 +53,7 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -167,50 +172,68 @@
 
     @Test
     fun navigationDrawer_testOffset_customWidthLarger_whenOpen() {
-        val customWidth = NavigationDrawerWidth + 5.dp
+        val customWidth = NavigationDrawerWidth + 20.dp
+        val density = Density(0.5f)
+        lateinit var coords: LayoutCoordinates
         rule.setMaterialContent(lightColorScheme()) {
-            val drawerState = rememberDrawerState(DrawerValue.Open)
-            ModalNavigationDrawer(
-                drawerState = drawerState,
-                drawerContent = {
-                    ModalDrawerSheet(Modifier.width(customWidth)) {
-                        Box(
-                            Modifier
-                                .fillMaxSize()
-                                .testTag("content")
-                        )
-                    }
-                },
-                content = {}
-            )
+            // Reduce density to ensure wide drawer fits on screen
+            CompositionLocalProvider(LocalDensity provides density) {
+                val drawerState = rememberDrawerState(DrawerValue.Open)
+                ModalNavigationDrawer(
+                    drawerState = drawerState,
+                    drawerContent = {
+                        ModalDrawerSheet(Modifier.width(customWidth)) {
+                            Box(
+                                Modifier
+                                    .fillMaxSize()
+                                    .onGloballyPositioned {
+                                        coords = it
+                                    }
+                            )
+                        }
+                    },
+                    content = {}
+                )
+            }
         }
 
-        rule.onNodeWithTag("content")
-            .assertLeftPositionInRootIsEqualTo(0.dp)
+        rule.runOnIdle {
+            assertThat(coords.positionOnScreen().x).isEqualTo(0f)
+        }
     }
 
     @Test
     fun navigationDrawer_testOffset_customWidthLarger_whenClosed() {
-        val customWidth = NavigationDrawerWidth + 5.dp
+        val customWidth = NavigationDrawerWidth + 20.dp
+        val density = Density(0.5f)
+        lateinit var coords: LayoutCoordinates
         rule.setMaterialContent(lightColorScheme()) {
-            val drawerState = rememberDrawerState(DrawerValue.Closed)
-            ModalNavigationDrawer(
-                drawerState = drawerState,
-                drawerContent = {
-                    ModalDrawerSheet(Modifier.width(customWidth)) {
-                        Box(
-                            Modifier
-                                .fillMaxSize()
-                                .testTag("content")
-                        )
-                    }
-                },
-                content = {}
-            )
+            // Reduce density to ensure wide drawer fits on screen
+            CompositionLocalProvider(LocalDensity provides density) {
+                val drawerState = rememberDrawerState(DrawerValue.Closed)
+                ModalNavigationDrawer(
+                    drawerState = drawerState,
+                    drawerContent = {
+                        ModalDrawerSheet(Modifier.width(customWidth)) {
+                            Box(
+                                Modifier
+                                    .fillMaxSize()
+                                    .onGloballyPositioned {
+                                        coords = it
+                                    }
+                            )
+                        }
+                    },
+                    content = {}
+                )
+            }
         }
 
-        rule.onNodeWithTag("content")
-            .assertLeftPositionInRootIsEqualTo(-customWidth)
+        rule.runOnIdle {
+            with(density) {
+                assertThat(coords.positionOnScreen().x).isWithin(1f).of(-customWidth.toPx())
+            }
+        }
     }
 
     @Test
@@ -419,12 +442,16 @@
         // When the drawer state is set to Opened
         drawerState.snapTo(DrawerValue.Open)
         // Then the drawer should be opened
-        assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Open)
+        }
 
         // When the drawer state is set to Closed
         drawerState.snapTo(DrawerValue.Closed)
         // Then the drawer should be closed
-        assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(DrawerValue.Closed)
+        }
     }
 
     @Test
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt
index 50e761f..38861c1 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/SnackbarTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.material3
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.internal.Strings
 import androidx.compose.material3.internal.getString
@@ -88,6 +89,29 @@
     }
 
     @Test
+    fun snackbar_emptyContent() {
+        rule.setMaterialContent(lightColorScheme()) {
+            Snackbar {
+                // empty content should not crash
+            }
+        }
+    }
+
+    @Test
+    fun snackbar_noTextInContent() {
+        val snackbarHeight = 48.dp
+        val contentSize = 20.dp
+        rule.setMaterialContent(lightColorScheme()) {
+            Snackbar {
+                // non-text content should not crash
+                Box(Modifier.testTag("content").size(contentSize))
+            }
+        }
+        rule.onNodeWithTag("content")
+            .assertTopPositionInRootIsEqualTo((snackbarHeight - contentSize) / 2)
+    }
+
+    @Test
     fun snackbar_withDismiss_semantics() {
         var clicked = false
         val snackbarVisuals =
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
index 644850d5..ed987ae 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
@@ -119,29 +119,21 @@
         val actionTextStyle = SnackbarTokens.ActionLabelTextFont.value
         CompositionLocalProvider(LocalTextStyle provides textStyle) {
             when {
-                action == null -> OneRowSnackbar(
+                actionOnNewLine && action != null -> NewLineButtonSnackbar(
                     text = content,
-                    action = null,
+                    action = action,
                     dismissAction = dismissAction,
-                    actionTextStyle,
-                    actionContentColor,
-                    dismissActionContentColor
-                )
-                actionOnNewLine -> NewLineButtonSnackbar(
-                    content,
-                    action,
-                    dismissAction,
-                    actionTextStyle,
-                    actionContentColor,
-                    dismissActionContentColor
+                    actionTextStyle = actionTextStyle,
+                    actionContentColor = actionContentColor,
+                    dismissActionContentColor = dismissActionContentColor,
                 )
                 else -> OneRowSnackbar(
                     text = content,
                     action = action,
                     dismissAction = dismissAction,
-                    actionTextStyle,
-                    actionContentColor,
-                    dismissActionContentColor
+                    actionTextStyle = actionTextStyle,
+                    actionTextColor = actionContentColor,
+                    dismissActionColor = dismissActionContentColor,
                 )
             }
         }
@@ -353,10 +345,10 @@
         )
 
         val firstTextBaseline = textPlaceable[FirstBaseline]
-        require(firstTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
         val lastTextBaseline = textPlaceable[LastBaseline]
-        require(lastTextBaseline != AlignmentLine.Unspecified) { "No baselines for text" }
-        val isOneLine = firstTextBaseline == lastTextBaseline
+        val hasText = firstTextBaseline != AlignmentLine.Unspecified &&
+            lastTextBaseline != AlignmentLine.Unspecified
+        val isOneLine = firstTextBaseline == lastTextBaseline || !hasText
         val dismissButtonPlaceX = containerWidth - dismissButtonWidth
         val actionButtonPlaceX = dismissButtonPlaceX - actionButtonWidth
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
index df5f37a..95ea8e8 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
@@ -82,14 +82,13 @@
     override suspend fun <R> withFrameNanos(
         onFrame: (Long) -> R
     ): R = suspendCancellableCoroutine { co ->
-        lateinit var awaiter: FrameAwaiter<R>
+        val awaiter = FrameAwaiter(onFrame, co)
         val hasNewAwaiters = synchronized(lock) {
             val cause = failureCause
             if (cause != null) {
                 co.resumeWithException(cause)
                 return@suspendCancellableCoroutine
             }
-            awaiter = FrameAwaiter(onFrame, co)
             val hadAwaiters = awaiters.isNotEmpty()
             awaiters.add(awaiter)
             if (!hadAwaiters) hasAwaitersUnlocked.set(1)
diff --git a/compose/ui/ui-text/lint-baseline.xml b/compose/ui/ui-text/lint-baseline.xml
index e34eca2..d8802c0 100644
--- a/compose/ui/ui-text/lint-baseline.xml
+++ b/compose/ui/ui-text/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-beta01)" variant="all" version="8.3.0-beta01">
+<issues format="6" by="lint 8.5.0-alpha03" type="baseline" client="gradle" dependencies="false" name="AGP (8.5.0-alpha03)" variant="all" version="8.5.0-alpha03">
 
     <issue
         id="NewApi"
@@ -12,7 +12,7 @@
 
     <issue
         id="NewApi"
-        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
         errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
         errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -30,7 +30,7 @@
 
     <issue
         id="NewApi"
-        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
         errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
         errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -48,7 +48,7 @@
 
     <issue
         id="NewApi"
-        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
         errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
         errorLine2="                   ~~~~~~~~~~~~~~~~~~~">
         <location
@@ -75,7 +75,7 @@
 
     <issue
         id="NewApi"
-        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        message="Implicit cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
         errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.DstOver)"
         errorLine2="                   ~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt
index 8af3326..3e20eda 100644
--- a/compose/ui/ui-unit/api/current.txt
+++ b/compose/ui/ui-unit/api/current.txt
@@ -132,8 +132,8 @@
     method public long copy(optional float x, optional float y);
     method public float getX();
     method public float getY();
-    method @androidx.compose.runtime.Stable public inline operator long minus(long other);
-    method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+    method @androidx.compose.runtime.Stable public operator long minus(long other);
+    method @androidx.compose.runtime.Stable public operator long plus(long other);
     property @androidx.compose.runtime.Stable public final float x;
     property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
@@ -176,8 +176,8 @@
     method @androidx.compose.runtime.Stable public operator long div(int other);
     method public float getHeight();
     method public float getWidth();
-    method @androidx.compose.runtime.Stable public inline operator long minus(long other);
-    method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+    method @androidx.compose.runtime.Stable public operator long minus(long other);
+    method @androidx.compose.runtime.Stable public operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(float other);
     method @androidx.compose.runtime.Stable public operator long times(int other);
     property @androidx.compose.runtime.Stable public final float height;
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt
index d3e260a..7d41176 100644
--- a/compose/ui/ui-unit/api/restricted_current.txt
+++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -132,8 +132,8 @@
     method public long copy(optional float x, optional float y);
     method public float getX();
     method public float getY();
-    method @androidx.compose.runtime.Stable public inline operator long minus(long other);
-    method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+    method @androidx.compose.runtime.Stable public operator long minus(long other);
+    method @androidx.compose.runtime.Stable public operator long plus(long other);
     property @androidx.compose.runtime.Stable public final float x;
     property @androidx.compose.runtime.Stable public final float y;
     field public static final androidx.compose.ui.unit.DpOffset.Companion Companion;
@@ -176,8 +176,8 @@
     method @androidx.compose.runtime.Stable public operator long div(int other);
     method public float getHeight();
     method public float getWidth();
-    method @androidx.compose.runtime.Stable public inline operator long minus(long other);
-    method @androidx.compose.runtime.Stable public inline operator long plus(long other);
+    method @androidx.compose.runtime.Stable public operator long minus(long other);
+    method @androidx.compose.runtime.Stable public operator long plus(long other);
     method @androidx.compose.runtime.Stable public operator long times(float other);
     method @androidx.compose.runtime.Stable public operator long times(int other);
     property @androidx.compose.runtime.Stable public final float height;
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
index 5c5102f..e2d9797 100644
--- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
+++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/Dp.kt
@@ -269,21 +269,29 @@
      * Returns a copy of this [DpOffset] instance optionally overriding the
      * x or y parameter
      */
-    fun copy(x: Dp = this.x, y: Dp = this.y): DpOffset = DpOffset(x, y)
+    fun copy(x: Dp = this.x, y: Dp = this.y): DpOffset = DpOffset(packFloats(x.value, y.value))
 
     /**
      * Subtract a [DpOffset] from another one.
      */
     @Stable
-    inline operator fun minus(other: DpOffset) =
-        DpOffset(x - other.x, y - other.y)
+    operator fun minus(other: DpOffset) = DpOffset(
+        packFloats(
+            (x - other.x).value,
+            (y - other.y).value
+        )
+    )
 
     /**
      * Add a [DpOffset] to another one.
      */
     @Stable
-    inline operator fun plus(other: DpOffset) =
-        DpOffset(x + other.x, y + other.y)
+    operator fun plus(other: DpOffset) = DpOffset(
+        packFloats(
+            (x + other.x).value,
+            (y + other.y).value
+        )
+    )
 
     @Stable
     override fun toString(): String =
@@ -297,14 +305,14 @@
         /**
          * A [DpOffset] with 0 DP [x] and 0 DP [y] values.
          */
-        val Zero = DpOffset(0.dp, 0.dp)
+        val Zero = DpOffset(0x0L)
 
         /**
          * Represents an offset whose [x] and [y] are unspecified. This is usually a replacement for
          * `null` when a primitive value is desired.
          * Access to [x] or [y] on an unspecified offset is not allowed.
          */
-        val Unspecified = DpOffset(Dp.Unspecified, Dp.Unspecified)
+        val Unspecified = DpOffset(0x7fc00000_7fc00000L)
     }
 }
 
@@ -342,7 +350,12 @@
  */
 @Stable
 fun lerp(start: DpOffset, stop: DpOffset, fraction: Float): DpOffset =
-    DpOffset(lerp(start.x, stop.x, fraction), lerp(start.y, stop.y, fraction))
+    DpOffset(
+        packFloats(
+            lerp(start.x.value, stop.x.value, fraction),
+            lerp(start.y.value, stop.y.value, fraction)
+        )
+    )
 
 /**
  * Constructs a [DpSize] from [width] and [height] [Dp] values.
@@ -372,21 +385,33 @@
      * Returns a copy of this [DpSize] instance optionally overriding the
      * width or height parameter
      */
-    fun copy(width: Dp = this.width, height: Dp = this.height): DpSize = DpSize(width, height)
+    fun copy(width: Dp = this.width, height: Dp = this.height): DpSize = DpSize(
+        packFloats(width.value, height.value)
+    )
 
     /**
      * Subtract a [DpSize] from another one.
      */
     @Stable
-    inline operator fun minus(other: DpSize) =
-        DpSize(width - other.width, height - other.height)
+    operator fun minus(other: DpSize) =
+        DpSize(
+            packFloats(
+                (width - other.width).value,
+                (height - other.height).value
+            )
+        )
 
     /**
      * Add a [DpSize] to another one.
      */
     @Stable
-    inline operator fun plus(other: DpSize) =
-        DpSize(width + other.width, height + other.height)
+    operator fun plus(other: DpSize) =
+        DpSize(
+            packFloats(
+                (width + other.width).value,
+                (height + other.height).value
+            )
+        )
 
     @Stable
     inline operator fun component1(): Dp = width
@@ -395,16 +420,36 @@
     inline operator fun component2(): Dp = height
 
     @Stable
-    operator fun times(other: Int): DpSize = DpSize(width * other, height * other)
+    operator fun times(other: Int): DpSize = DpSize(
+        packFloats(
+            (width * other).value,
+            (height * other).value
+        )
+    )
 
     @Stable
-    operator fun times(other: Float): DpSize = DpSize(width * other, height * other)
+    operator fun times(other: Float): DpSize = DpSize(
+        packFloats(
+            (width * other).value,
+            (height * other).value
+        )
+    )
 
     @Stable
-    operator fun div(other: Int): DpSize = DpSize(width / other, height / other)
+    operator fun div(other: Int): DpSize = DpSize(
+        packFloats(
+            (width / other).value,
+            (height / other).value
+        )
+    )
 
     @Stable
-    operator fun div(other: Float): DpSize = DpSize(width / other, height / other)
+    operator fun div(other: Float): DpSize = DpSize(
+        packFloats(
+            (width / other).value,
+            (height / other).value
+        )
+    )
 
     @Stable
     override fun toString(): String =
@@ -418,14 +463,14 @@
         /**
          * A [DpSize] with 0 DP [width] and 0 DP [height] values.
          */
-        val Zero = DpSize(0.dp, 0.dp)
+        val Zero = DpSize(0x0L)
 
         /**
          * A size whose [width] and [height] are unspecified. This is usually a replacement for
          * `null` when a primitive value is desired.
          * Access to [width] or [height] on an unspecified size is not allowed.
          */
-        val Unspecified = DpSize(Dp.Unspecified, Dp.Unspecified)
+        val Unspecified = DpSize(0x7fc00000_7fc00000L)
     }
 }
 
@@ -456,7 +501,12 @@
  */
 @Stable
 val DpSize.center: DpOffset
-    get() = DpOffset(width / 2f, height / 2f)
+    get() = DpOffset(
+        packFloats(
+            (width / 2f).value,
+            (height / 2f).value
+        )
+    )
 
 @Stable
 inline operator fun Int.times(size: DpSize) = size * this
@@ -476,7 +526,12 @@
  */
 @Stable
 fun lerp(start: DpSize, stop: DpSize, fraction: Float): DpSize =
-    DpSize(lerp(start.width, stop.width, fraction), lerp(start.height, stop.height, fraction))
+    DpSize(
+        packFloats(
+            lerp(start.width, stop.width, fraction).value,
+            lerp(start.height, stop.height, fraction).value
+        )
+    )
 
 /**
  * A four dimensional bounds using [Dp] for units
diff --git a/compose/ui/ui/src/androidMain/res/values-hi/strings.xml b/compose/ui/ui/src/androidMain/res/values-hi/strings.xml
index be2db07..103ecfa 100644
--- a/compose/ui/ui/src/androidMain/res/values-hi/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-hi/strings.xml
@@ -31,8 +31,7 @@
     <string name="close_drawer" msgid="406453423630273620">"नेविगेशन मेन्यू बंद करें"</string>
     <string name="close_sheet" msgid="7573152094250666567">"शीट बंद करें"</string>
     <string name="default_error_message" msgid="8038256446254964252">"अमान्य इनपुट"</string>
-    <!-- no translation found for state_empty (4139871816613051306) -->
-    <skip />
+    <string name="state_empty" msgid="4139871816613051306">"कोई भी तार नहीं लगा है"</string>
     <string name="default_popup_window_title" msgid="6312721426453364202">"पॉप-अप विंडो"</string>
     <string name="range_start" msgid="7097486360902471446">"रेंज की शुरुआत"</string>
     <string name="range_end" msgid="5941395253238309765">"रेंज की सीमा"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-km/strings.xml b/compose/ui/ui/src/androidMain/res/values-km/strings.xml
index 207a067..e10e501 100644
--- a/compose/ui/ui/src/androidMain/res/values-km/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-km/strings.xml
@@ -31,8 +31,7 @@
     <string name="close_drawer" msgid="406453423630273620">"បិទម៉ឺនុយរុករក"</string>
     <string name="close_sheet" msgid="7573152094250666567">"បិទសន្លឹក"</string>
     <string name="default_error_message" msgid="8038256446254964252">"ការបញ្ចូល​មិនត្រឹមត្រូវ"</string>
-    <!-- no translation found for state_empty (4139871816613051306) -->
-    <skip />
+    <string name="state_empty" msgid="4139871816613051306">"ទទេ"</string>
     <string name="default_popup_window_title" msgid="6312721426453364202">"វិនដូ​លោតឡើង"</string>
     <string name="range_start" msgid="7097486360902471446">"ចំណុចចាប់ផ្ដើម"</string>
     <string name="range_end" msgid="5941395253238309765">"ចំណុចបញ្ចប់"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-lo/strings.xml b/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
index 95bccae..c201f11 100644
--- a/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-lo/strings.xml
@@ -31,8 +31,7 @@
     <string name="close_drawer" msgid="406453423630273620">"ປິດ​ເມ​ນູການ​ນຳ​ທາງ"</string>
     <string name="close_sheet" msgid="7573152094250666567">"ປິດຊີດ"</string>
     <string name="default_error_message" msgid="8038256446254964252">"ຂໍ້ມູນທີ່ປ້ອນເຂົ້າບໍ່ຖືກຕ້ອງ"</string>
-    <!-- no translation found for state_empty (4139871816613051306) -->
-    <skip />
+    <string name="state_empty" msgid="4139871816613051306">"ຫວ່າງເປົ່າ"</string>
     <string name="default_popup_window_title" msgid="6312721426453364202">"ໜ້າຈໍປັອບອັບ"</string>
     <string name="range_start" msgid="7097486360902471446">"ເລີ່ມຕົ້ນໄລຍະ"</string>
     <string name="range_end" msgid="5941395253238309765">"ສິ້ນສຸດໄລຍະ"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-my/strings.xml b/compose/ui/ui/src/androidMain/res/values-my/strings.xml
index 074defe..a44f8ed 100644
--- a/compose/ui/ui/src/androidMain/res/values-my/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-my/strings.xml
@@ -31,8 +31,7 @@
     <string name="close_drawer" msgid="406453423630273620">"လမ်းညွှန် မီနူး ပိတ်ရန်"</string>
     <string name="close_sheet" msgid="7573152094250666567">"စာမျက်နှာ ပိတ်ရန်"</string>
     <string name="default_error_message" msgid="8038256446254964252">"ထည့်သွင်းမှု မမှန်ကန်ပါ"</string>
-    <!-- no translation found for state_empty (4139871816613051306) -->
-    <skip />
+    <string name="state_empty" msgid="4139871816613051306">"မရှိပါ"</string>
     <string name="default_popup_window_title" msgid="6312721426453364202">"ပေါ့ပ်အပ် ဝင်းဒိုး"</string>
     <string name="range_start" msgid="7097486360902471446">"အပိုင်းအခြား အစ"</string>
     <string name="range_end" msgid="5941395253238309765">"အပိုင်းအခြား အဆုံး"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-ne/strings.xml b/compose/ui/ui/src/androidMain/res/values-ne/strings.xml
index 7078d5c..f8a9002 100644
--- a/compose/ui/ui/src/androidMain/res/values-ne/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-ne/strings.xml
@@ -31,8 +31,7 @@
     <string name="close_drawer" msgid="406453423630273620">"नेभिगेसन मेनु बन्द गर्नुहोस्"</string>
     <string name="close_sheet" msgid="7573152094250666567">"पाना बन्द गर्नुहोस्"</string>
     <string name="default_error_message" msgid="8038256446254964252">"अवैद्य इन्पुट"</string>
-    <!-- no translation found for state_empty (4139871816613051306) -->
-    <skip />
+    <string name="state_empty" msgid="4139871816613051306">"खाली"</string>
     <string name="default_popup_window_title" msgid="6312721426453364202">"पपअप विन्डो"</string>
     <string name="range_start" msgid="7097486360902471446">"दायराको सुरुवात बिन्दु"</string>
     <string name="range_end" msgid="5941395253238309765">"दायराको अन्तिम बिन्दु"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml b/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml
index e9061ec..be24761 100644
--- a/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-pt-rPT/strings.xml
@@ -31,8 +31,7 @@
     <string name="close_drawer" msgid="406453423630273620">"Fechar menu de navegação"</string>
     <string name="close_sheet" msgid="7573152094250666567">"Fechar folha"</string>
     <string name="default_error_message" msgid="8038256446254964252">"Entrada inválida"</string>
-    <!-- no translation found for state_empty (4139871816613051306) -->
-    <skip />
+    <string name="state_empty" msgid="4139871816613051306">"Vazio"</string>
     <string name="default_popup_window_title" msgid="6312721426453364202">"Janela pop-up"</string>
     <string name="range_start" msgid="7097486360902471446">"Início do intervalo"</string>
     <string name="range_end" msgid="5941395253238309765">"Fim do intervalo"</string>
diff --git a/compose/ui/ui/src/androidMain/res/values-sw/strings.xml b/compose/ui/ui/src/androidMain/res/values-sw/strings.xml
index a213605..98421b2 100644
--- a/compose/ui/ui/src/androidMain/res/values-sw/strings.xml
+++ b/compose/ui/ui/src/androidMain/res/values-sw/strings.xml
@@ -31,8 +31,7 @@
     <string name="close_drawer" msgid="406453423630273620">"Funga menyu ya kusogeza"</string>
     <string name="close_sheet" msgid="7573152094250666567">"Funga laha"</string>
     <string name="default_error_message" msgid="8038256446254964252">"Ulichoweka si sahihi"</string>
-    <!-- no translation found for state_empty (4139871816613051306) -->
-    <skip />
+    <string name="state_empty" msgid="4139871816613051306">"Tupu"</string>
     <string name="default_popup_window_title" msgid="6312721426453364202">"Dirisha Ibukizi"</string>
     <string name="range_start" msgid="7097486360902471446">"Mwanzo wa masafa"</string>
     <string name="range_end" msgid="5941395253238309765">"Mwisho wa masafa"</string>
diff --git a/development/update_studio.sh b/development/update_studio.sh
index ef74a284..324322f 100755
--- a/development/update_studio.sh
+++ b/development/update_studio.sh
@@ -7,8 +7,8 @@
 
 # Versions that the user should update when running this script
 echo Getting Studio version and link
-AGP_VERSION=${1:-8.4.0-alpha12}
-STUDIO_VERSION_STRING=${2:-"Android Studio Jellyfish | 2023.3.1 Canary 12"}
+AGP_VERSION=${1:-8.5.0-alpha06}
+STUDIO_VERSION_STRING=${2:-"Android Studio Koala | 2024.1.1 Canary 6"}
 
 # Get studio version number from version name
 STUDIO_IFRAME_LINK=`curl "https://developer.android.com/studio/archive.html" | grep "<iframe " | sed "s/.* src=\"\([^\"]*\)\".*/\1/g"`
diff --git a/gradle.properties b/gradle.properties
index 7a42aa7..68fc0ca 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,6 +21,7 @@
 # fullsdk-linux/**/package.xml -> b/291331139
 org.gradle.configuration-cache.inputs.unsafe.ignore.file-system-checks=**/prebuilts/fullsdk-linux;**/prebuilts/fullsdk-linux/platforms/android-*/package.xml
 
+android.javaCompile.suppressSourceTargetDeprecationWarning=true
 android.lint.baselineOmitLineNumbers=true
 android.lint.printStackTrace=true
 android.builder.sdkDownload=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9c01869..da3f63c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,13 +2,13 @@
 # -----------------------------------------------------------------------------
 # All of the following should be updated in sync.
 # -----------------------------------------------------------------------------
-androidGradlePlugin = "8.4.0-alpha12"
+androidGradlePlugin = "8.5.0-alpha06"
 # NOTE: When updating the lint version we also need to update the `api` version
 # supported by `IssueRegistry`'s.' For e.g. r.android.com/1331903
-androidLint = "31.4.0-alpha12"
+androidLint = "31.5.0-alpha06"
 # Once you have a chosen version of AGP to upgrade to, go to
 # https://developer.android.com/studio/archive and find the matching version of Studio.
-androidStudio = "2023.3.1.12"
+androidStudio = "2024.1.1.4"
 # -----------------------------------------------------------------------------
 
 androidGradlePluginMin = "7.0.4"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e5c45a0..1d107cd 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-8.7-bin.zip
-distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
+distributionUrl=../../../../tools/external/gradle/gradle-8.8-rc-1-bin.zip
+distributionSha256Sum=a2e1cfee7ffdeee86015b85b2dd2a435032c40eedc01d8172285556c7d8fea13
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index e13d44f..dce5e38 100755
--- a/gradlew
+++ b/gradlew
@@ -16,7 +16,6 @@
     OUT_DIR="$(cd $OUT_DIR && pwd -P)"
     export GRADLE_USER_HOME="$OUT_DIR/.gradle"
     export TMPDIR="$OUT_DIR/tmp"
-    mkdir -p "$TMPDIR"
 else
     CHECKOUT_ROOT="$(cd $SCRIPT_PATH/../.. && pwd -P)"
     export OUT_DIR="$CHECKOUT_ROOT/out"
@@ -230,9 +229,6 @@
 if [ "$GRADLE_USER_HOME" != "" ]; then
     HOME_SYSTEM_PROPERTY_ARGUMENT="-Duser.home=$GRADLE_USER_HOME"
 fi
-if [ "$TMPDIR" != "" ]; then
-  TMPDIR_ARG="-Djava.io.tmpdir=$TMPDIR"
-fi
 
 if [[ " ${@} " =~ " --clean " ]]; then
   cleanCaches=true
@@ -407,6 +403,11 @@
 }
 
 function runGradle() {
+  if [ "$TMPDIR" != "" ]; then
+    mkdir -p "$TMPDIR"
+    TMPDIR_ARG="-Djava.io.tmpdir=$TMPDIR"
+  fi
+
   processOutput=false
   if [[ " ${@} " =~ " -Pandroidx.validateNoUnrecognizedMessages " ]]; then
     processOutput=true
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt
index 8bcf5f0..a42b7d8 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaverTest.android.kt
@@ -359,6 +359,76 @@
         assertThat(getOptionalCount!!()).isEqualTo(1)
         assertThat(savedStateHandle?.keys()).isEqualTo(setOf("count"))
     }
+
+    @OptIn(SavedStateHandleSaveableApi::class)
+    @Test
+    fun noConflictKeys_delegate_simpleRestore() {
+        activityTestRuleScenario.scenario.onActivity { activity ->
+            activity.setContent {
+                val viewModel = viewModel<SavingTestViewModel>(activity)
+                val firstClass = FirstClass(viewModel.savedStateHandle)
+                val secondClass = SecondClass(viewModel.savedStateHandle)
+                assertThat(firstClass.savedProperty).isEqualTo("One")
+                assertThat(secondClass.savedProperty).isEqualTo("Two")
+            }
+        }
+
+        activityTestRuleScenario.scenario.recreate()
+
+        activityTestRuleScenario.scenario.onActivity { activity ->
+            activity.setContent {
+                val viewModel = viewModel<SavingTestViewModel>(activity)
+                val firstClass = FirstClass(viewModel.savedStateHandle)
+                val secondClass = SecondClass(viewModel.savedStateHandle)
+                assertThat(firstClass.savedProperty).isEqualTo("One")
+                assertThat(secondClass.savedProperty).isEqualTo("Two")
+            }
+        }
+    }
+
+    @OptIn(SavedStateHandleSaveableApi::class)
+    @Test
+    fun conflictKeys_local_delegate_simpleRestore() {
+
+        fun firstFunction(handle: SavedStateHandle): String {
+            val localProperty by handle.saveable { mutableStateOf("One") }
+            return localProperty
+        }
+
+        fun secondFunction(handle: SavedStateHandle): String {
+            val localProperty by handle.saveable { mutableStateOf("Two") }
+            return localProperty
+        }
+
+        activityTestRuleScenario.scenario.onActivity { activity ->
+            activity.setContent {
+                val savedStateHandle = viewModel<SavingTestViewModel>(activity).savedStateHandle
+                firstFunction(savedStateHandle)
+                secondFunction(savedStateHandle)
+            }
+        }
+
+        activityTestRuleScenario.scenario.recreate()
+
+        activityTestRuleScenario.scenario.onActivity { activity ->
+            activity.setContent {
+                val savedStateHandle = viewModel<SavingTestViewModel>(activity).savedStateHandle
+                // TODO(b/331695354): Fix local property saveable delegate key conflict
+                assertThat(firstFunction(savedStateHandle)).isEqualTo("Two")
+                assertThat(secondFunction(savedStateHandle)).isEqualTo("Two")
+            }
+        }
+    }
 }
 
 class SavingTestViewModel(val savedStateHandle: SavedStateHandle) : ViewModel()
+
+class FirstClass(savedStateHandle: SavedStateHandle) {
+    @OptIn(SavedStateHandleSaveableApi::class)
+    val savedProperty by savedStateHandle.saveable { mutableStateOf("One") }
+}
+
+class SecondClass(savedStateHandle: SavedStateHandle) {
+    @OptIn(SavedStateHandleSaveableApi::class)
+    val savedProperty by savedStateHandle.saveable { mutableStateOf("Two") }
+}
diff --git a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
index ada4c35..019167e 100644
--- a/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
+++ b/lifecycle/lifecycle-viewmodel-compose/src/androidMain/kotlin/androidx/lifecycle/viewmodel/compose/SavedStateHandleSaver.android.kt
@@ -117,9 +117,10 @@
     saver: Saver<T, out Any> = autoSaver(),
     init: () -> T,
 ): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
-    PropertyDelegateProvider { _, property ->
+    PropertyDelegateProvider { thisRef, property ->
+        val classNamePrefix = if (thisRef != null) thisRef::class.qualifiedName + "." else ""
         val value = saveable(
-            key = property.name,
+            key = classNamePrefix + property.name,
             saver = saver,
             init = init
         )
@@ -155,9 +156,10 @@
     stateSaver: Saver<T, out Any> = autoSaver(),
     init: () -> M,
 ): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> =
-    PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> { _, property ->
+    PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> { thisRef, property ->
+        val classNamePrefix = if (thisRef != null) thisRef::class.qualifiedName + "." else ""
         val mutableState = saveable(
-            key = property.name,
+            key = classNamePrefix + property.name,
             stateSaver = stateSaver,
             init = init
         )
diff --git a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
index 6057cc1..9a36c5f 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
@@ -487,7 +487,7 @@
 
             // Builtin R8 desugaring, such as rewriting compare calls (see b/36390874)
             if (owner.startsWith("java.") &&
-                DesugaredMethodLookup.isDesugared(owner, name, desc, context.sourceSetType)) {
+                DesugaredMethodLookup.isDesugaredMethod(owner, name, desc, context.sourceSetType)) {
                 return
             }
 
@@ -573,7 +573,7 @@
             api: Int
         ): LintFix? {
             val callPsi = call.sourcePsi ?: return null
-            if (isKotlin(callPsi)) {
+            if (isKotlin(callPsi.language)) {
                 // We only support Java right now.
                 return null
             }
diff --git a/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt b/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt
index a9f7fbd..a773acc 100644
--- a/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/MissingJvmDefaultWithCompatibilityDetector.kt
@@ -59,7 +59,7 @@
                 return
             }
 
-            if (!isKotlin(node)) return
+            if (!isKotlin(node.language)) return
             if (!node.isInterface) return
             if (node.annotatedWithAnyOf(
                     // If the interface is not stable, it doesn't need the annotation
diff --git a/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt b/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
index 6ed71ee..bf0227e 100644
--- a/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/NullabilityAnnotationsDetector.kt
@@ -40,7 +40,8 @@
 
     private inner class AnnotationChecker(val context: JavaContext) : UElementHandler() {
         override fun visitAnnotation(node: UAnnotation) {
-            if (isJava(node.sourcePsi)) {
+            val element = node.sourcePsi
+            if (element != null && isJava(element.language)) {
                 checkForAnnotation(node, "NotNull", "NonNull")
                 checkForAnnotation(node, "Nullable", "Nullable")
             }
diff --git a/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt b/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt
index 36cbf9cd..9eaf45c 100644
--- a/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/RestrictToDetector.kt
@@ -86,7 +86,9 @@
             // here, but that points to impl classes in its hierarchy which leads to
             // class loading trouble.
             val sourcePsi = element.sourcePsi
-            if (isKotlin(sourcePsi) && sourcePsi?.parent?.toString() == "CONSTRUCTOR_CALLEE") {
+            if (sourcePsi != null &&
+                isKotlin(sourcePsi.language) &&
+                sourcePsi.parent?.toString() == "CONSTRUCTOR_CALLEE") {
                 return
             }
         }
diff --git a/mediarouter/mediarouter/src/main/res/values-iw/strings.xml b/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
index 03371fa..7bafb1a 100644
--- a/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-iw/strings.xml
@@ -18,11 +18,11 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mr_system_route_name" msgid="7449553026175453403">"מערכת"</string>
     <string name="mr_user_route_category_name" msgid="4088331695424166162">"מכשירים"</string>
-    <string name="mr_button_content_description" msgid="2939063992730535343">"‏העברה (cast)"</string>
-    <string name="mr_cast_button_disconnected" msgid="8071109333469380363">"‏העברה (cast). אין חיבור"</string>
-    <string name="mr_cast_button_connecting" msgid="6629927151350192407">"‏העברה (cast). מתבצעת התחברות"</string>
-    <string name="mr_cast_button_connected" msgid="6073720094880410356">"‏העברה (cast). מחובר"</string>
-    <string name="mr_chooser_title" msgid="1419936397646839840">"העברה אל"</string>
+    <string name="mr_button_content_description" msgid="2939063992730535343">"‏הפעלת Cast"</string>
+    <string name="mr_cast_button_disconnected" msgid="8071109333469380363">"‏הפעלת Cast. אין מכשיר מחובר"</string>
+    <string name="mr_cast_button_connecting" msgid="6629927151350192407">"‏הפעלת Cast. מתחברים למכשיר"</string>
+    <string name="mr_cast_button_connected" msgid="6073720094880410356">"‏הפעלת Cast. יש מכשיר מחובר"</string>
+    <string name="mr_chooser_title" msgid="1419936397646839840">"‏הפעלת Cast אל"</string>
     <string name="mr_chooser_searching" msgid="6114250663023140921">"מתבצע חיפוש מכשירים"</string>
     <string name="mr_chooser_looking_for_devices" msgid="4257319068277776035">"מתבצע חיפוש מכשירים…"</string>
     <string name="mr_controller_disconnect" msgid="7812275474138309497">"ניתוק"</string>
@@ -50,5 +50,5 @@
     <string name="mr_chooser_wifi_warning_description_car" msgid="2998902945608081567">"‏מוודאים שהמכשיר השני והרכב מחוברים לאותה רשת Wi-Fi"</string>
     <string name="mr_chooser_wifi_warning_description_unknown" msgid="3459891599800041449">"‏מוודאים ששני המכשירים מחוברים לאותה רשת Wi-Fi"</string>
     <string name="mr_chooser_wifi_learn_more" msgid="3799500840179081429"><a href="https://support.google.com/chromecast/?p=trouble-finding-devices">"מידע נוסף"</a></string>
-    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"‏איך להעביר (cast)"</string>
+    <string name="ic_media_route_learn_more_accessibility" msgid="9119039724000326934">"‏איך להפעיל Cast"</string>
 </resources>
diff --git a/playground-common/gradle/wrapper/gradle-wrapper.properties b/playground-common/gradle/wrapper/gradle-wrapper.properties
index 61f9702..fc4b959 100644
--- a/playground-common/gradle/wrapper/gradle-wrapper.properties
+++ b/playground-common/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
-distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-rc-1-bin.zip
+distributionSha256Sum=a2e1cfee7ffdeee86015b85b2dd2a435032c40eedc01d8172285556c7d8fea13
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt b/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
index 2e7601c..4d9ce75 100644
--- a/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
+++ b/stableaidl/stableaidl-gradle-plugin/src/test/java/com/android/build/gradle/internal/fixtures/FakeGradleProperty.kt
@@ -111,6 +111,10 @@
         throw NotImplementedError()
     }
 
+    override fun replace(transformation: Transformer<out Provider<out T>?, in Provider<T>>) {
+        throw NotImplementedError()
+    }
+
     @Deprecated("Deprecated in Java")
     override fun forUseAtConfigurationTime(): Provider<T> {
         throw NotImplementedError()
diff --git a/testutils/testutils-datastore/build.gradle b/testutils/testutils-datastore/build.gradle
index 2721d70..a2057f8 100644
--- a/testutils/testutils-datastore/build.gradle
+++ b/testutils/testutils-datastore/build.gradle
@@ -23,14 +23,12 @@
  */
 import androidx.build.LibraryType
 
-import static org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.*
-
 plugins {
     id("AndroidXPlugin")
 }
 
 androidXMultiplatform {
-    jvm {}
+    jvm()
     mac()
     linux()
     ios()
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt
index 825a3ee..3ab85ce 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedColumn.kt
@@ -44,6 +44,7 @@
  * than the curved column, either at the [CurvedAlignment.Angular.Start] of the layout,
  * at the [CurvedAlignment.Angular.End], or [CurvedAlignment.Angular.Center].
  * If unspecified or null, they can choose for themselves.
+ * @param contentBuilder Scope used to provide the content for this column.
  */
 public fun CurvedScope.curvedColumn(
     modifier: CurvedModifier = CurvedModifier,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt
index f4fa3ca..4f5daea 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/CurvedRow.kt
@@ -42,6 +42,7 @@
  * and if those needs to be reversed in a Rtl layout.
  * If not specified, it will be inherited from the enclosing [curvedRow] or [CurvedLayout]
  * See [CurvedDirection.Angular].
+ * @param contentBuilder Scope used to provide the content for this row.
  */
 public fun CurvedScope.curvedRow(
     modifier: CurvedModifier = CurvedModifier,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
index e344c9f..2087c5c3 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeableV2.kt
@@ -677,17 +677,19 @@
 @RestrictTo(LIBRARY_GROUP)
 public object SwipeableV2Defaults {
     /**
-     * The default animation used by [SwipeableV2State].
+     * The default animation that will be used to animate to a new state.
      */
     val AnimationSpec = SpringSpec<Float>()
 
     /**
-     * The default velocity threshold (1.8 dp per millisecond) used by [rememberSwipeableV2State].
+     * The default velocity threshold (in dp per second) that the end velocity has to
+     * exceed in order to animate to the next state.
      */
     val VelocityThreshold: Dp = 125.dp
 
     /**
-     * The default positional threshold (56 dp) used by [rememberSwipeableV2State]
+     * The default positional threshold used when calculating the target state while a swipe is in
+     * progress and when settling after the swipe ends.
      */
     val PositionalThreshold: Density.(totalDistance: Float) -> Float =
         fixedPositionalThreshold(56.dp)
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
index 1fdbc11..114fd30 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
@@ -310,10 +310,9 @@
     // Canvas internally uses Spacer.drawBehind.
     // Using Spacer.drawWithCache to optimize the stroke allocations.
     Spacer(
+        // NB We must set the semantic role to Role.RadioButton in the parent Button,
+        // not here in the selection control - see b/330869742
         modifier = modifier
-            .semantics {
-                this.role = Role.RadioButton
-            }
             .maybeSelectable(
                 onClick, enabled, selected, interactionSource, ripple, width, height
             )
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
index 792bd00..3b0332a 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
@@ -211,6 +211,9 @@
                         indication = ripple,
                         interactionSource = interactionSource
                     )
+                    // For a toggleable button, the role could be Checkbox or Switch,
+                    // so we cannot set the semantics here. Instead,
+                    // we set them in the toggle control
                 } else {
                     Modifier.selectable(
                         enabled = enabled,
@@ -218,7 +221,12 @@
                         onClick = { onCheckedChange(true) },
                         indication = ripple,
                         interactionSource = interactionSource
-                    )
+                    ).semantics {
+                        // For a selectable button, the role is always RadioButton.
+                        // See also b/330869742 for issue with setting the RadioButton role
+                        // within the selection control.
+                        role = Role.RadioButton
+                    }
                 }
             )
             .padding(contentPadding),
@@ -375,6 +383,12 @@
                     indication = ripple,
                     interactionSource = checkedInteractionSource
                 )
+                .semantics {
+                    // For a selectable button, the role is always RadioButton.
+                    // See also b/330869742 for issue with setting the RadioButton role
+                    // within the selection control.
+                    role = Role.RadioButton
+                }
             }
 
         Box(
diff --git a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt
index 2fde27b..c3d34d8 100644
--- a/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt
+++ b/wear/compose/compose-material/src/androidTest/kotlin/androidx/wear/compose/material/SelectableChipTest.kt
@@ -136,6 +136,28 @@
     }
 
     @Test
+    fun selectable_chip_has_role_radiobutton() {
+        rule.setContentWithTheme {
+            SelectableChip(
+                selected = true,
+                onClick = {},
+                enabled = false,
+                label = { Text("Label") },
+                selectionControl = { TestImage() },
+                modifier = Modifier.testTag(TEST_TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(
+                SemanticsMatcher.expectValue(
+                    SemanticsProperties.Role,
+                    Role.RadioButton
+                )
+            )
+    }
+
+    @Test
     fun split_chip_has_clickaction_when_disabled() {
         rule.setContentWithTheme {
             SplitSelectableChip(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
index fe84508..c9c736c 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
@@ -90,6 +90,7 @@
  * still happen internally.
  * @param role The type of user interface element. Accessibility services might use this
  * to describe the element or do customizations
+ * @param content Slot for composable body content displayed on the Card
  */
 @Composable
 public fun Card(
@@ -179,6 +180,7 @@
  * set.
  * @param timeColor The default color to use for time() slot unless explicitly set.
  * @param titleColor The default color to use for title() slot unless explicitly set.
+ * @param content Slot for composable body content displayed on the Card
  */
 @Composable
 public fun AppCard(
@@ -289,6 +291,7 @@
  * @param contentColor The default color to use for content() slot unless explicitly set.
  * @param titleColor The default color to use for title() slot unless explicitly set.
  * @param timeColor The default color to use for time() slot unless explicitly set.
+ * @param content Slot for composable body content displayed on the Card
  */
 @Composable
 public fun TitleCard(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
index d80d069..5fed098 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
@@ -172,6 +172,7 @@
  * still happen internally.
  * @param role The type of user interface element. Accessibility services might use this
  * to describe the element or do customizations
+ * @param content Slot for composable body content displayed on the Chip
  */
 @Composable
 public fun Chip(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt
index a7c7a0c..0566bf5 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ListHeader.kt
@@ -43,6 +43,7 @@
  * @param modifier The modifier for the list header
  * @param backgroundColor The background color to apply - typically Color.Transparent
  * @param contentColor The color to apply to content
+ * @param content Slot for displayed header text
  */
 @Composable
 public fun ListHeader(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
index ed866b9..8f7bdbf 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
@@ -53,6 +53,7 @@
  * @param colors A complete definition of the Wear Material Color theme for this hierarchy
  * @param typography A set of text styles to be used as this hierarchy's typography system
  * @param shapes A set of shapes to be used by the components in this hierarchy
+ * @param content Slot for composable content displayed with this theme
  */
 @Composable
 public fun MaterialTheme(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt
index e9a468b..fb7965a 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Scaffold.kt
@@ -51,6 +51,7 @@
  * page indicator is a pager with horizontally swipeable pages.
  * @param timeText time and potential application status message to display at the top middle of the
  * screen. Expected to be a TimeText component.
+ * @param content Slot for composable screen content
  */
 @Composable
 public fun Scaffold(
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
index f5c3ef0..3997c14 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/RadioButtonTest.kt
@@ -82,6 +82,23 @@
     }
 
     @Test
+    fun radio_button_has_role_radiobutton() {
+        rule.setContentWithTheme {
+            RadioButtonWithDefaults(
+                modifier = Modifier.testTag(TEST_TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .assert(
+                SemanticsMatcher.expectValue(
+                    SemanticsProperties.Role,
+                    Role.RadioButton
+                )
+            )
+    }
+
+    @Test
     fun radio_button_samples_build() {
         rule.setContentWithTheme {
             RadioButton()
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
index 600e737..a8ac0da 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/SelectionControlsTest.kt
@@ -22,10 +22,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.SemanticsProperties
-import androidx.compose.ui.test.SemanticsMatcher
-import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.captureToImage
@@ -67,25 +63,6 @@
     }
 
     @Test
-    fun radio_control_has_role_radiobutton() {
-        rule.setContentWithTheme {
-            with(SelectionControlScope(isEnabled = true, isSelected = true)) {
-                Radio(
-                    modifier = Modifier.testTag(TEST_TAG)
-                )
-            }
-        }
-
-        rule.onNodeWithTag(TEST_TAG)
-            .assert(
-                SemanticsMatcher.expectValue(
-                    SemanticsProperties.Role,
-                    Role.RadioButton
-                )
-            )
-    }
-
-    @Test
     fun radio_control_is_correctly_enabled() {
         rule.setContentWithTheme {
             with(SelectionControlScope(isEnabled = true, isSelected = true)) {
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
index 2dc5958..3755e89 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
@@ -106,6 +106,7 @@
  * emitting [Interaction]s for this button. You can use this to change the button's appearance
  * or preview the button in different states. Note that if `null` is provided, interactions will
  * still happen internally.
+ * @param content Slot for composable body content displayed on the Button
  */
 @Composable
 fun Button(
@@ -176,6 +177,7 @@
  * emitting [Interaction]s for this button. You can use this to change the button's appearance
  * or preview the button in different states. Note that if `null` is provided, interactions will
  * still happen internally.
+ * @param content Slot for composable body content displayed on the Button
  */
 @Composable
 fun FilledTonalButton(
@@ -245,6 +247,7 @@
  * emitting [Interaction]s for this button. You can use this to change the button's appearance
  * or preview the button in different states. Note that if `null` is provided, interactions will
  * still happen internally.
+ * @param content Slot for composable body content displayed on the OutlinedButton
  */
 @Composable
 fun OutlinedButton(
@@ -314,6 +317,7 @@
  * emitting [Interaction]s for this button. You can use this to change the button's appearance
  * or preview the button in different states. Note that if `null` is provided, interactions will
  * still happen internally.
+ * @param content Slot for composable body content displayed on the ChildButton
  */
 @Composable
 fun ChildButton(
@@ -1058,6 +1062,8 @@
     /**
      * Creates a [BorderStroke], such as for an [OutlinedButton]
      *
+     * @param enabled Controls the color of the border based on the enabled/disabled state of the
+     * button
      * @param borderColor The color to use for the border for this outline when enabled
      * @param disabledBorderColor The color to use for the border for this outline when
      * disabled
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
index 5ed024f..9ef0bde 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
@@ -50,6 +50,7 @@
  * @param colorScheme A complete definition of the Wear Material Color theme for this hierarchy
  * @param typography A set of text styles to be used as this hierarchy's typography system
  * @param shapes A set of shapes to be used by the components in this hierarchy
+ * @param content Slot for composable content displayed with this theme
  */
 @Composable
 fun MaterialTheme(
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
index 70dbab71..b4a6f53 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/RadioButton.kt
@@ -144,7 +144,13 @@
                 indication = rippleOrFallbackImplementation(),
                 interactionSource = interactionSource
             )
-            .padding(contentPadding),
+            .padding(contentPadding)
+            .semantics {
+                // For a selectable button, the role is always RadioButton.
+                // See also b/330869742 for issue with setting the RadioButton role
+                // within the selection control.
+                role = Role.RadioButton
+            },
         verticalAlignment = Alignment.CenterVertically
     ) {
         if (icon != null) {
@@ -329,7 +335,13 @@
                 .width(SPLIT_WIDTH)
                 .wrapContentHeight(align = Alignment.CenterVertically)
                 .wrapContentWidth(align = Alignment.End)
-                .then(endPadding),
+                .then(endPadding)
+                .semantics {
+                    // For a selectable button, the role is always RadioButton.
+                    // See also b/330869742 for issue with setting the RadioButton role
+                    // within the selection control.
+                    role = Role.RadioButton
+                },
         ) {
             val scope = remember(enabled, selected) { SelectionControlScope(enabled, selected) }
             selectionControl(scope)
diff --git a/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt b/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt
index 7981cf2..5b295fc 100644
--- a/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt
+++ b/wear/compose/compose-ui-tooling/src/main/java/androidx/wear/compose/ui/tooling/preview/WearPreviewFontScales.kt
@@ -34,7 +34,7 @@
  * note, the above list is not exhaustive. It previews the composables on a small round Wear device.
  *
  * @sample androidx.wear.compose.material.samples.TitleCardWithImagePreview
- * @see [Preview.fontScale]
+ * @see Preview.fontScale
  */
 @Preview(
     device = WearDevices.SMALL_ROUND,
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 28d0392..d68e962 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -25,8 +25,8 @@
     defaultConfig {
         applicationId "androidx.wear.compose.integration.demos"
         minSdk 25
-        versionCode 23
-        versionName "1.23"
+        versionCode 24
+        versionName "1.24"
     }
 
     buildTypes {
diff --git a/wear/protolayout/protolayout-expression/api/current.txt b/wear/protolayout/protolayout-expression/api/current.txt
index bd9565b..756cbe0 100644
--- a/wear/protolayout/protolayout-expression/api/current.txt
+++ b/wear/protolayout/protolayout-expression/api/current.txt
@@ -14,7 +14,7 @@
   }
 
   public static final class AnimationParameterBuilders.AnimationParameters.Builder {
-    ctor public AnimationParameterBuilders.AnimationParameters.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationParameters.Builder();
     method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDelayMillis(@IntRange(from=0) long);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDurationMillis(@IntRange(from=0) long);
@@ -27,10 +27,10 @@
   }
 
   public static final class AnimationParameterBuilders.AnimationSpec.Builder {
-    ctor public AnimationParameterBuilders.AnimationSpec.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationSpec.Builder();
     method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setAnimationParameters(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
-    method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static interface AnimationParameterBuilders.Easing {
@@ -53,7 +53,7 @@
   }
 
   public static final class AnimationParameterBuilders.Repeatable.Builder {
-    ctor public AnimationParameterBuilders.Repeatable.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.Repeatable.Builder();
     method public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setForwardRepeatOverride(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setIterations(@IntRange(from=1) int);
diff --git a/wear/protolayout/protolayout-expression/api/restricted_current.txt b/wear/protolayout/protolayout-expression/api/restricted_current.txt
index bd9565b..756cbe0 100644
--- a/wear/protolayout/protolayout-expression/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression/api/restricted_current.txt
@@ -14,7 +14,7 @@
   }
 
   public static final class AnimationParameterBuilders.AnimationParameters.Builder {
-    ctor public AnimationParameterBuilders.AnimationParameters.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationParameters.Builder();
     method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDelayMillis(@IntRange(from=0) long);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters.Builder setDurationMillis(@IntRange(from=0) long);
@@ -27,10 +27,10 @@
   }
 
   public static final class AnimationParameterBuilders.AnimationSpec.Builder {
-    ctor public AnimationParameterBuilders.AnimationSpec.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.AnimationSpec.Builder();
     method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setAnimationParameters(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
-    method public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
+    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec.Builder setRepeatable(androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable);
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static interface AnimationParameterBuilders.Easing {
@@ -53,7 +53,7 @@
   }
 
   public static final class AnimationParameterBuilders.Repeatable.Builder {
-    ctor public AnimationParameterBuilders.Repeatable.Builder();
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public AnimationParameterBuilders.Repeatable.Builder();
     method public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setForwardRepeatOverride(androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationParameters);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.expression.AnimationParameterBuilders.Repeatable.Builder setIterations(@IntRange(from=1) int);
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java
index 44eb9a7..a800b2a 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/AnimationParameterBuilders.java
@@ -140,6 +140,7 @@
                     AnimationParameterProto.AnimationSpec.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(-2136602843);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /** Sets animation parameters including duration, easing and repeat delay. */
@@ -158,6 +159,7 @@
              * Sets the repeatable mode to be used for specifying repetition parameters for the
              * animation. If not set, animation won't be repeated.
              */
+            @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
             public Builder setRepeatable(@NonNull Repeatable repeatable) {
                 mImpl.setRepeatable(repeatable.toProto());
@@ -261,6 +263,7 @@
                     AnimationParameterProto.AnimationParameters.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(-1301308590);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /**
@@ -491,7 +494,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         AnimationParameterProto.CubicBezierEasing toProto() {
             return mImpl;
@@ -525,6 +527,7 @@
                     AnimationParameterProto.CubicBezierEasing.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(856403705);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /**
@@ -704,6 +707,7 @@
                     AnimationParameterProto.Repeatable.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(2110475048);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /**
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java
index 36e1b0b..5a088b9 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/ConditionScopes.java
@@ -48,12 +48,14 @@
         }
 
         /** Sets the value to use as the value when true in a conditional expression. */
-        public @NonNull IfTrueScope<T, RawT> use(T valueWhenTrue) {
+        @NonNull
+        public IfTrueScope<T, RawT> use(T valueWhenTrue) {
             return new IfTrueScope<>(valueWhenTrue, conditionBuilder, rawTypeMapper);
         }
 
         /** Sets the value to use as the value when true in a conditional expression. */
-        public @NonNull IfTrueScope<T, RawT> use(RawT valueWhenTrue) {
+        @NonNull
+        public IfTrueScope<T, RawT> use(RawT valueWhenTrue) {
             return use(rawTypeMapper.apply(valueWhenTrue));
         }
     }
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
index 17d89ce..500985d 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
@@ -708,7 +708,7 @@
                 return this;
             }
 
-            /** Sets the name space for the state key. */
+            /** Sets the namespace for the state key. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
             public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -1255,7 +1255,7 @@
             /** Sets the value to start animating from. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
-            public AnimatableFixedInt32.Builder setFromValue(int fromValue) {
+            public Builder setFromValue(int fromValue) {
                 mImpl.setFromValue(fromValue);
                 mFingerprint.recordPropertyUpdate(1, fromValue);
                 return this;
@@ -1264,7 +1264,7 @@
             /** Sets the value to animate to. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
-            public AnimatableFixedInt32.Builder setToValue(int toValue) {
+            public Builder setToValue(int toValue) {
                 mImpl.setToValue(toValue);
                 mFingerprint.recordPropertyUpdate(2, toValue);
                 return this;
@@ -1398,7 +1398,7 @@
             /** Sets the value to watch, and animate when it changes. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
-            public AnimatableDynamicInt32.Builder setInput(@NonNull DynamicInt32 input) {
+            public Builder setInput(@NonNull DynamicInt32 input) {
                 mImpl.setInput(input.toDynamicInt32Proto());
                 mFingerprint.recordPropertyUpdate(
                         1, checkNotNull(input.getFingerprint()).aggregateValueAsInt());
@@ -2413,7 +2413,7 @@
 
             /** Returns whether digit grouping is used or not. */
             public boolean isGroupingUsed() {
-                return mInt32FormatOp.getGroupingUsed();
+                return mInt32FormatOp.isGroupingUsed();
             }
 
             /** Builder to create {@link IntFormatter} objects. */
@@ -2579,7 +2579,7 @@
          * locale. If not defined, defaults to false. For example, for locale en_US, using grouping
          * with 1234 would yield "1,234".
          */
-        public boolean getGroupingUsed() {
+        public boolean isGroupingUsed() {
             return mImpl.getGroupingUsed();
         }
 
@@ -2638,13 +2638,13 @@
                     + ", minIntegerDigits="
                     + getMinIntegerDigits()
                     + ", groupingUsed="
-                    + getGroupingUsed()
+                    + isGroupingUsed()
                     + "}";
         }
 
         /** Builder for {@link Int32FormatOp}. */
         public static final class Builder implements DynamicString.Builder {
-            final DynamicProto.Int32FormatOp.Builder mImpl =
+            private final DynamicProto.Int32FormatOp.Builder mImpl =
                     DynamicProto.Int32FormatOp.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(196209833);
 
@@ -2792,7 +2792,7 @@
                 return this;
             }
 
-            /** Sets the name space for the state key. */
+            /** Sets the namespace for the state key. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
             public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -3143,7 +3143,7 @@
          * locale. If not defined, defaults to false. For example, for locale en_US, using grouping
          * with 1234.56 would yield "1,234.56".
          */
-        public boolean getGroupingUsed() {
+        public boolean isGroupingUsed() {
             return mImpl.getGroupingUsed();
         }
 
@@ -3206,7 +3206,7 @@
                     + ", minIntegerDigits="
                     + getMinIntegerDigits()
                     + ", groupingUsed="
-                    + getGroupingUsed()
+                    + isGroupingUsed()
                     + "}";
         }
 
@@ -3736,7 +3736,7 @@
                 return this;
             }
 
-            /** Sets the name space for the state key. */
+            /** Sets the namespace for the state key. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
             public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -5012,7 +5012,7 @@
 
             /** Returns whether digit grouping is used or not. */
             public boolean isGroupingUsed() {
-                return mFloatFormatOp.getGroupingUsed();
+                return mFloatFormatOp.isGroupingUsed();
             }
 
             /** Builder to create {@link FloatFormatter} objects. */
@@ -5228,8 +5228,8 @@
         @NonNull
         public DynamicProto.DynamicBool toDynamicBoolProto(boolean withFingerprint) {
             if (withFingerprint) {
-                return DynamicProto.DynamicBool.newBuilder().
-                        setStateSource(mImpl)
+                return DynamicProto.DynamicBool.newBuilder()
+                        .setStateSource(mImpl)
                         .setFingerprint(checkNotNull(mFingerprint).toProto())
                         .build();
             }
@@ -5265,7 +5265,7 @@
                 return this;
             }
 
-            /** Sets the name space for the state key. */
+            /** Sets the namespace for the state key. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
             public Builder setSourceNamespace(@NonNull String sourceNamespace) {
@@ -6144,7 +6144,7 @@
                 return this;
             }
 
-            /** Sets the name space for the state key. */
+            /** Sets the namespace for the state key. */
             @RequiresSchemaVersion(major = 1, minor = 200)
             @NonNull
             public Builder setSourceNamespace(@NonNull String sourceNamespace) {
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
index 69c88e3..c3b5c45 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicDataBuilders.java
@@ -28,7 +28,6 @@
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInstant;
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
-import androidx.wear.protolayout.expression.DynamicBuilders.DynamicType;
 import androidx.wear.protolayout.expression.FixedValueBuilders.FixedBool;
 import androidx.wear.protolayout.expression.FixedValueBuilders.FixedColor;
 import androidx.wear.protolayout.expression.FixedValueBuilders.FixedDuration;
@@ -51,7 +50,7 @@
 
     /** Interface defining a dynamic data value. */
     @RequiresSchemaVersion(major = 1, minor = 200)
-    public interface DynamicDataValue<T extends DynamicType> {
+    public interface DynamicDataValue<T extends DynamicBuilders.DynamicType> {
         /** Get the protocol buffer representation of this object. */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
@@ -310,7 +309,7 @@
 
         /** Builder to create {@link DynamicDataValue} objects. */
         @RestrictTo(Scope.LIBRARY_GROUP)
-        interface Builder<T extends DynamicType> {
+        interface Builder<T extends DynamicBuilders.DynamicType> {
 
             /** Builds an instance with values accumulated in this Builder. */
             @NonNull
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
index ced6685..e16e059 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/FixedValueBuilders.java
@@ -78,7 +78,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         FixedProto.FixedInt32 toProto() {
             return mImpl;
@@ -144,6 +143,7 @@
             private final FixedProto.FixedInt32.Builder mImpl = FixedProto.FixedInt32.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(974881783);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /** Sets the value. */
@@ -203,7 +203,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         FixedProto.FixedString toProto() {
             return mImpl;
@@ -271,6 +270,7 @@
                     FixedProto.FixedString.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(1963352072);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /** Sets the value. */
@@ -333,7 +333,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         FixedProto.FixedFloat toProto() {
             return mImpl;
@@ -399,6 +398,7 @@
             private final FixedProto.FixedFloat.Builder mImpl = FixedProto.FixedFloat.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(-144724541);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /**
@@ -461,7 +461,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         FixedProto.FixedBool toProto() {
             return mImpl;
@@ -527,6 +526,7 @@
             private final FixedProto.FixedBool.Builder mImpl = FixedProto.FixedBool.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(-665116398);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /** Sets the value. */
@@ -587,7 +587,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         FixedProto.FixedColor toProto() {
             return mImpl;
@@ -653,6 +652,7 @@
             private final FixedProto.FixedColor.Builder mImpl = FixedProto.FixedColor.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(-1895809356);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /** Sets the color value, in ARGB format. */
@@ -733,7 +733,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         FixedProto.FixedInstant toProto() {
             return mImpl;
@@ -778,6 +777,7 @@
                     FixedProto.FixedInstant.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(-1986552556);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /**
@@ -860,7 +860,6 @@
         }
 
         /** Returns the internal proto instance. */
-        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         FixedProto.FixedDuration toProto() {
             return mImpl;
@@ -905,6 +904,7 @@
                     FixedProto.FixedDuration.newBuilder();
             private final Fingerprint mFingerprint = new Fingerprint(9029504);
 
+            @RequiresSchemaVersion(major = 1, minor = 200)
             public Builder() {}
 
             /** Sets duration in seconds. */
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/Constants.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/Constants.java
new file mode 100644
index 0000000..d6a206c
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/Constants.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License = 0 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 = 0 software
+ * distributed under the License is distributed on an "AS IS" BASIS = 0
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND = 0 either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.renderer.common;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Shared constants. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class Constants {
+
+    private Constants() {}
+
+    /** The reason why an update was requested. */
+    @IntDef({
+        UPDATE_REQUEST_REASON_UNKNOWN,
+        UPDATE_REQUEST_REASON_SYSUI_CAROUSEL,
+        UPDATE_REQUEST_REASON_FRESHNESS,
+        UPDATE_REQUEST_REASON_USER_INTERACTION,
+        UPDATE_REQUEST_REASON_UPDATE_REQUESTER,
+        UPDATE_REQUEST_REASON_CACHE_INVALIDATION,
+        UPDATE_REQUEST_REASON_RETRY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UpdateRequestReason {}
+
+    /** Unknown reason. */
+    public static final int UPDATE_REQUEST_REASON_UNKNOWN = 0;
+
+    /** Update triggered by SysUI Carousel. */
+    public static final int UPDATE_REQUEST_REASON_SYSUI_CAROUSEL = 1;
+
+    /** Update triggered by freshness. */
+    public static final int UPDATE_REQUEST_REASON_FRESHNESS = 2;
+
+    /** Update triggered by user interaction (e.g. clicking on the tile). */
+    public static final int UPDATE_REQUEST_REASON_USER_INTERACTION = 3;
+
+    /** Update triggered using update requester. */
+    public static final int UPDATE_REQUEST_REASON_UPDATE_REQUESTER = 4;
+
+    /** Update triggered due to clearing the cache. */
+    public static final int UPDATE_REQUEST_REASON_CACHE_INVALIDATION = 5;
+
+    /** Update triggered by retry policy. */
+    public static final int UPDATE_REQUEST_REASON_RETRY = 6;
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/NoOpProviderStatsLogger.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/NoOpProviderStatsLogger.java
new file mode 100644
index 0000000..f292c98
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/NoOpProviderStatsLogger.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.renderer.common;
+
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.wear.protolayout.proto.StateProto.State;
+import androidx.wear.protolayout.renderer.common.Constants.UpdateRequestReason;
+
+/** A No-Op implementation of {@link ProviderStatsLogger}. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class NoOpProviderStatsLogger implements ProviderStatsLogger {
+    private static final String TAG = "NoOpProviderStatsLogger";
+
+    /** Creates an instance of {@link NoOpProviderStatsLogger}. */
+    public NoOpProviderStatsLogger(@NonNull String reason) {
+        Log.i(TAG, "Instance used because " + reason);
+    }
+
+    /** No-op method. */
+    @Override
+    public void logLayoutSchemaVersion(int major, int minor) {}
+
+    /** No-op method. */
+    @Override
+    public void logStateStructure(@NonNull State state, boolean isInitialState) {}
+
+    /** No-op method. */
+    @Override
+    public void logIgnoredFailure(int failure) {}
+
+    /** No-op method. */
+    @Override
+    public void logInflationFailed(@InflationFailureReason int failureReason) {}
+
+    /** No-op method. */
+    @Override
+    @NonNull
+    public InflaterStatsLogger createInflaterStatsLogger() {
+        return new NoOpInflaterStatsLogger();
+    }
+
+    /** No-op method. */
+    @Override
+    public void logInflationFinished(@NonNull InflaterStatsLogger inflaterStatsLogger) {}
+
+    /** No-op method. */
+    @Override
+    public void logTileRequestReason(@UpdateRequestReason int updateRequestReason) {}
+
+    /** A No-Op implementation of {@link InflaterStatsLogger}. */
+    public static class NoOpInflaterStatsLogger implements InflaterStatsLogger {
+
+        private NoOpInflaterStatsLogger() {}
+
+        @Override
+        public void logMutationChangedNodes(int changedNodesCount) {}
+
+        @Override
+        public void logTotalNodeCount(int totalNodesCount) {}
+
+        /** No-op method. */
+        @Override
+        public void logDrawableUsage(@NonNull Drawable drawable) {}
+
+        /** No-op method. */
+        @Override
+        public void logIgnoredFailure(@IgnoredFailure int failure) {}
+
+        /** No-op method. */
+        @Override
+        public void logInflationFailed(@InflationFailureReason int failureReason) {}
+    }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProviderStatsLogger.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProviderStatsLogger.java
new file mode 100644
index 0000000..96a6fb0
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/ProviderStatsLogger.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.renderer.common;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.UiThread;
+import androidx.wear.protolayout.proto.StateProto.State;
+import androidx.wear.protolayout.renderer.common.Constants.UpdateRequestReason;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Logger used for collecting metrics. */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface ProviderStatsLogger {
+
+    /** Failures that doesn't cause the inflation to fail. */
+    @IntDef({
+        IGNORED_FAILURE_UNKNOWN,
+        IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION,
+        IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED,
+        IGNORED_FAILURE_DIFFING_FAILURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface IgnoredFailure {}
+
+    /** Unknown failure. */
+    int IGNORED_FAILURE_UNKNOWN = 0;
+
+    /** Failure applying the diff mutation. */
+    int IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION = 1;
+
+    /** Failure caused by exceeding animation quota. */
+    int IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED = 2;
+
+    /** Failure diffing the layout. */
+    int IGNORED_FAILURE_DIFFING_FAILURE = 3;
+
+    /** Failures that causes the inflation to fail. */
+    @IntDef({
+        INFLATION_FAILURE_REASON_UNKNOWN,
+        INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED,
+        INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface InflationFailureReason {}
+
+    /** Unknown failure. */
+    int INFLATION_FAILURE_REASON_UNKNOWN = 0;
+
+    /** Failure caused by exceeding maximum layout depth. */
+    int INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED = 1;
+
+    /** Failure caused by exceeding maximum expression node count. */
+    int INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED = 2;
+
+    /** Log the schema version of the received layout. */
+    void logLayoutSchemaVersion(int major, int minor);
+
+    /** Log Protolayout state structure. */
+    void logStateStructure(@NonNull State state, boolean isInitialState);
+
+    /** Log the occurrence of an ignored failure. */
+    @UiThread
+    void logIgnoredFailure(@IgnoredFailure int failure);
+
+    /** Log the reason for inflation failure. */
+    @UiThread
+    void logInflationFailed(@InflationFailureReason int failureReason);
+
+    /**
+     * Creates an {@link InflaterStatsLogger} and marks the start of inflation. The atoms will be
+     * logged to statsd only when {@link #logInflationFinished} is called.
+     */
+    @UiThread
+    @NonNull
+    InflaterStatsLogger createInflaterStatsLogger();
+
+    /** Makes the end of inflation and log the inflation results. */
+    @UiThread
+    void logInflationFinished(@NonNull InflaterStatsLogger inflaterStatsLogger);
+
+    /** Log tile request reason. */
+    void logTileRequestReason(@UpdateRequestReason int updateRequestReason);
+
+    /** Logger used for logging inflation stats. */
+    interface InflaterStatsLogger {
+        /** log the mutation changed nodes count for the ongoing inflation. */
+        @UiThread
+        void logMutationChangedNodes(int changedNodesCount);
+
+        /** Log the total nodes count for the ongoing inflation. */
+        @UiThread
+        void logTotalNodeCount(int totalNodesCount);
+
+        /**
+         * Log the usage of a drawable. This method should be called between {@link
+         * #createInflaterStatsLogger()} and {@link #logInflationFinished(InflaterStatsLogger)}.
+         */
+        @UiThread
+        void logDrawableUsage(@NonNull Drawable drawable);
+
+        /**
+         * Log the occurrence of an ignored failure. The usage of this method is not restricted to
+         * inflation start or end.
+         */
+        @UiThread
+        void logIgnoredFailure(@IgnoredFailure int failure);
+
+        /**
+         * Log the reason for inflation failure. This will make any future call {@link
+         * #logInflationFinished(InflaterStatsLogger)} a Noop.
+         */
+        @UiThread
+        void logInflationFailed(@InflationFailureReason int failureReason);
+    }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/RenderingArtifact.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/RenderingArtifact.java
new file mode 100644
index 0000000..8424728
--- /dev/null
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/common/RenderingArtifact.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.renderer.common;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger.InflaterStatsLogger;
+
+/** Artifacts resulted from the layout rendering. */
+@RestrictTo(Scope.LIBRARY_GROUP)
+public interface RenderingArtifact {
+
+    /** Creates a {@link RenderingArtifact} instance. */
+    @NonNull
+    static RenderingArtifact create(@NonNull InflaterStatsLogger inflaterStatsLogger) {
+        return new SuccessfulRenderingArtifact(inflaterStatsLogger);
+    }
+
+    /** Creates a {@link RenderingArtifact} instance for a skipped inflation. */
+    @NonNull
+    static RenderingArtifact skipped() {
+        return new SkippedRenderingArtifact();
+    }
+
+    /** Creates a {@link RenderingArtifact} instance for a failed inflation. */
+    @NonNull
+    static RenderingArtifact failed() {
+        return new FailedRenderingArtifact();
+    }
+
+    /** Artifacts resulted from a successful layout rendering. */
+    class SuccessfulRenderingArtifact implements RenderingArtifact {
+        @NonNull private final InflaterStatsLogger mInflaterStatsLogger;
+
+        private SuccessfulRenderingArtifact(@NonNull InflaterStatsLogger inflaterStatsLogger) {
+            mInflaterStatsLogger = inflaterStatsLogger;
+        }
+
+        /**
+         * Returns the {@link ProviderStatsLogger.InflaterStatsLogger} used log inflation stats.
+         * This will return {@code null} if the inflation was skipped or failed.
+         */
+        @NonNull
+        public InflaterStatsLogger getInflaterStatsLogger() {
+            return mInflaterStatsLogger;
+        }
+    }
+
+    /** Artifacts resulted from a skipped layout rendering. */
+    class SkippedRenderingArtifact implements RenderingArtifact {}
+
+    /** Artifacts resulted from a failed layout rendering. */
+    class FailedRenderingArtifact implements RenderingArtifact {}
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
index 08f0228..8f18ca8 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstance.java
@@ -20,6 +20,13 @@
 import static android.widget.FrameLayout.LayoutParams.UNSPECIFIED_GRAVITY;
 
 import static androidx.core.util.Preconditions.checkNotNull;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED;
+import static androidx.wear.protolayout.renderer.common.ProviderStatsLogger.INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED;
+
+import static com.google.common.util.concurrent.Futures.immediateCancelledFuture;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -41,6 +48,7 @@
 import androidx.wear.protolayout.expression.PlatformDataKey;
 import androidx.wear.protolayout.expression.pipeline.FixedQuotaManagerImpl;
 import androidx.wear.protolayout.expression.pipeline.PlatformDataProvider;
+import androidx.wear.protolayout.expression.pipeline.QuotaManager;
 import androidx.wear.protolayout.expression.pipeline.StateStore;
 import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement;
 import androidx.wear.protolayout.proto.LayoutElementProto.ArcLayoutElement.InnerCase;
@@ -52,7 +60,11 @@
 import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
 import androidx.wear.protolayout.renderer.ProtoLayoutVisibilityState;
 import androidx.wear.protolayout.renderer.common.LoggingUtils;
+import androidx.wear.protolayout.renderer.common.NoOpProviderStatsLogger;
 import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger.InflaterStatsLogger;
+import androidx.wear.protolayout.renderer.common.RenderingArtifact;
 import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
 import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater;
 import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater.InflateResult;
@@ -114,6 +126,7 @@
     @NonNull private final ListeningExecutorService mUiExecutorService;
     @NonNull private final ListeningExecutorService mBgExecutorService;
     @NonNull private final String mClickableIdExtra;
+    @NonNull private final ProviderStatsLogger mProviderStatsLogger;
     @Nullable private final LoggingUtils mLoggingUtils;
 
     @Nullable private final ProtoLayoutExtensionViewProvider mExtensionViewProvider;
@@ -219,10 +232,11 @@
          */
         @UiThread
         @NonNull
-        ListenableFuture<Void> postInflate(
+        ListenableFuture<RenderingArtifact> postInflate(
                 @NonNull ViewGroup attachParent,
                 @Nullable ViewGroup prevInflateParent,
-                boolean isReattaching);
+                boolean isReattaching,
+                InflaterStatsLogger inflaterStatsLogger);
     }
 
     /** Result of a {@link #renderOrComputeMutations} call when no changes are required. */
@@ -234,11 +248,12 @@
 
         @NonNull
         @Override
-        public ListenableFuture<Void> postInflate(
+        public ListenableFuture<RenderingArtifact> postInflate(
                 @NonNull ViewGroup attachParent,
                 @Nullable ViewGroup prevInflateParent,
-                boolean isReattaching) {
-            return Futures.immediateVoidFuture();
+                boolean isReattaching,
+                InflaterStatsLogger inflaterStatsLogger) {
+            return immediateFuture(RenderingArtifact.create(inflaterStatsLogger));
         }
     }
 
@@ -251,11 +266,12 @@
 
         @NonNull
         @Override
-        public ListenableFuture<Void> postInflate(
+        public ListenableFuture<RenderingArtifact> postInflate(
                 @NonNull ViewGroup attachParent,
                 @Nullable ViewGroup prevInflateParent,
-                boolean isReattaching) {
-            return Futures.immediateVoidFuture();
+                boolean isReattaching,
+                InflaterStatsLogger inflaterStatsLogger) {
+            return immediateFuture(RenderingArtifact.failed());
         }
     }
 
@@ -278,10 +294,11 @@
         @NonNull
         @Override
         @UiThread
-        public ListenableFuture<Void> postInflate(
+        public ListenableFuture<RenderingArtifact> postInflate(
                 @NonNull ViewGroup attachParent,
                 @Nullable ViewGroup prevInflateParent,
-                boolean isReattaching) {
+                boolean isReattaching,
+                InflaterStatsLogger inflaterStatsLogger) {
             InflateResult inflateResult =
                     checkNotNull(
                             mNewInflateParentData.mInflateResult,
@@ -292,7 +309,7 @@
             attachParent.addView(
                     inflateResult.inflateParent, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
             inflateResult.updateDynamicDataPipeline(isReattaching);
-            return Futures.immediateVoidFuture();
+            return immediateFuture(RenderingArtifact.create(inflaterStatsLogger));
         }
     }
 
@@ -318,10 +335,11 @@
         @NonNull
         @Override
         @UiThread
-        public ListenableFuture<Void> postInflate(
+        public ListenableFuture<RenderingArtifact> postInflate(
                 @NonNull ViewGroup attachParent,
                 @Nullable ViewGroup prevInflateParent,
-                boolean isReattaching) {
+                boolean isReattaching,
+                InflaterStatsLogger inflaterStatsLogger) {
             return mInflater.applyMutation(checkNotNull(prevInflateParent), mMutation);
         }
     }
@@ -345,6 +363,7 @@
         @NonNull private final String mClickableIdExtra;
 
         @Nullable private final LoggingUtils mLoggingUtils;
+        @NonNull private final ProviderStatsLogger mProviderStatsLogger;
         private final boolean mAnimationEnabled;
         private final int mRunningAnimationsLimit;
 
@@ -366,6 +385,7 @@
                 @Nullable ProtoLayoutExtensionViewProvider extensionViewProvider,
                 @NonNull String clickableIdExtra,
                 @Nullable LoggingUtils loggingUtils,
+                @NonNull ProviderStatsLogger providerStatsLogger,
                 boolean animationEnabled,
                 int runningAnimationsLimit,
                 boolean updatesEnabled,
@@ -384,6 +404,7 @@
             this.mExtensionViewProvider = extensionViewProvider;
             this.mClickableIdExtra = clickableIdExtra;
             this.mLoggingUtils = loggingUtils;
+            this.mProviderStatsLogger = providerStatsLogger;
             this.mAnimationEnabled = animationEnabled;
             this.mRunningAnimationsLimit = runningAnimationsLimit;
             this.mUpdatesEnabled = updatesEnabled;
@@ -468,6 +489,13 @@
             return mLoggingUtils;
         }
 
+        /** Returns the provider stats logger used for telemetry. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ProviderStatsLogger getProviderStatsLogger() {
+            return mProviderStatsLogger;
+        }
+
         /** Returns whether animations are enabled. */
         @RestrictTo(Scope.LIBRARY)
         public boolean getAnimationEnabled() {
@@ -529,6 +557,7 @@
             @Nullable private ProtoLayoutExtensionViewProvider mExtensionViewProvider;
             @NonNull private final String mClickableIdExtra;
             @Nullable private LoggingUtils mLoggingUtils;
+            @Nullable private ProviderStatsLogger mProviderStatsLogger;
             private boolean mAnimationEnabled = true;
             private int mRunningAnimationsLimit = DEFAULT_MAX_CONCURRENT_RUNNING_ANIMATIONS;
 
@@ -632,6 +661,15 @@
                 return this;
             }
 
+            /** Sets the provider stats logger used for telemetry. */
+            @RestrictTo(Scope.LIBRARY_GROUP)
+            @NonNull
+            public Builder setProviderStatsLogger(
+                    @NonNull ProviderStatsLogger providerStatsLogger) {
+                this.mProviderStatsLogger = providerStatsLogger;
+                return this;
+            }
+
             /**
              * Sets whether animation are enabled. If disabled, none of the animation will be
              * played.
@@ -715,6 +753,12 @@
                 if (mRendererResources == null) {
                     this.mRendererResources = mUiContext.getResources();
                 }
+
+                if (mProviderStatsLogger == null) {
+                    mProviderStatsLogger =
+                            new NoOpProviderStatsLogger(
+                                    "ProviderStatsLogger not provided to " + TAG);
+                }
                 return new Config(
                         mUiContext,
                         mRendererResources,
@@ -728,6 +772,7 @@
                         mExtensionViewProvider,
                         mClickableIdExtra,
                         mLoggingUtils,
+                        mProviderStatsLogger,
                         mAnimationEnabled,
                         mRunningAnimationsLimit,
                         mUpdatesEnabled,
@@ -754,24 +799,51 @@
         this.mWasFullyVisibleBefore = false;
         this.mAllowLayoutChangingBindsWithoutDefault =
                 config.getAllowLayoutChangingBindsWithoutDefault();
+        this.mProviderStatsLogger = config.getProviderStatsLogger();
 
         StateStore stateStore = config.getStateStore();
-        if (stateStore != null) {
-            mDataPipeline =
-                    config.getAnimationEnabled()
-                            ? new ProtoLayoutDynamicDataPipeline(
-                                    config.getPlatformDataProviders(),
-                                    stateStore,
-                                    new FixedQuotaManagerImpl(
-                                            config.getRunningAnimationsLimit(), "animations"),
-                                    new FixedQuotaManagerImpl(
-                                            DYNAMIC_NODES_MAX_COUNT, "dynamic nodes"))
-                            : new ProtoLayoutDynamicDataPipeline(
-                                    config.getPlatformDataProviders(), stateStore);
-            mDataPipeline.setFullyVisible(config.getIsViewFullyVisible());
-        } else {
+        if (stateStore == null) {
             mDataPipeline = null;
+            return;
         }
+
+        if (config.getAnimationEnabled()) {
+            QuotaManager nodeQuotaManager =
+                    new FixedQuotaManagerImpl(DYNAMIC_NODES_MAX_COUNT, "dynamic nodes") {
+                        @Override
+                        public boolean tryAcquireQuota(int quota) {
+                            boolean success = super.tryAcquireQuota(quota);
+                            if (!success) {
+                                mProviderStatsLogger.logInflationFailed(
+                                        INFLATION_FAILURE_REASON_EXPRESSION_NODE_COUNT_EXCEEDED);
+                            }
+                            return success;
+                        }
+                    };
+            mDataPipeline =
+                    new ProtoLayoutDynamicDataPipeline(
+                            config.getPlatformDataProviders(),
+                            stateStore,
+                            new FixedQuotaManagerImpl(
+                                    config.getRunningAnimationsLimit(), "animations") {
+                                @Override
+                                public boolean tryAcquireQuota(int quota) {
+                                    boolean success = super.tryAcquireQuota(quota);
+                                    if (!success) {
+                                        mProviderStatsLogger.logIgnoredFailure(
+                                                IGNORED_FAILURE_ANIMATION_QUOTA_EXCEEDED);
+                                    }
+                                    return success;
+                                }
+                            },
+                            nodeQuotaManager);
+        } else {
+            mDataPipeline =
+                    new ProtoLayoutDynamicDataPipeline(
+                            config.getPlatformDataProviders(), stateStore);
+        }
+
+        mDataPipeline.setFullyVisible(config.getIsViewFullyVisible());
     }
 
     @WorkerThread
@@ -780,7 +852,8 @@
             @NonNull Layout layout,
             @NonNull ResourceProto.Resources resources,
             @Nullable RenderedMetadata prevRenderedMetadata,
-            @NonNull ViewProperties parentViewProp) {
+            @NonNull ViewProperties parentViewProp,
+            @NonNull InflaterStatsLogger inflaterStatsLogger) {
         ResourceResolvers resolvers =
                 mResourceResolversProvider.getResourceResolvers(
                         mUiContext, resources, mUiExecutorService, mAnimationEnabled);
@@ -797,10 +870,10 @@
 
         if (sameFingerprint) {
             if (mPrevLayoutAlreadyFailingDepthCheck) {
-                throwExceptionForLayoutDepthCheckFailure();
+                handleLayoutDepthCheckFailure(inflaterStatsLogger);
             }
         } else {
-            checkLayoutDepth(layout.getRoot(), MAX_LAYOUT_ELEMENT_DEPTH);
+            checkLayoutDepth(layout.getRoot(), MAX_LAYOUT_ELEMENT_DEPTH, inflaterStatsLogger);
         }
 
         mPrevLayoutAlreadyFailingDepthCheck = false;
@@ -815,6 +888,7 @@
                         .setClickableIdExtra(mClickableIdExtra)
                         .setAllowLayoutChangingBindsWithoutDefault(
                                 mAllowLayoutChangingBindsWithoutDefault)
+                        .setInflaterStatsLogger(inflaterStatsLogger)
                         .setApplyFontVariantBodyAsDefault(true);
         if (mDataPipeline != null) {
             inflaterConfigBuilder.setDynamicDataPipeline(mDataPipeline);
@@ -886,6 +960,18 @@
         return new InflateParentData(result);
     }
 
+    @UiThread
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    public ListenableFuture<RenderingArtifact> renderLayoutAndAttach(
+            @NonNull Layout layout,
+            @NonNull ResourceProto.Resources resources,
+            @NonNull ViewGroup attachParent) {
+
+        return renderAndAttach(
+                layout, resources, attachParent, mProviderStatsLogger.createInflaterStatsLogger());
+    }
+
     /**
      * Render the layout for this layout and attach this layout instance to a {@code attachParent}
      * container. Note that this method may clear all of {@code attachParent}'s children before
@@ -911,6 +997,47 @@
             @NonNull Layout layout,
             @NonNull ResourceProto.Resources resources,
             @NonNull ViewGroup attachParent) {
+        SettableFuture<Void> result = SettableFuture.create();
+        ListenableFuture<RenderingArtifact> future =
+                renderLayoutAndAttach(layout, resources, attachParent);
+        if (future.isDone()) {
+            if (future.isCancelled()) {
+                return immediateCancelledFuture();
+            }
+            return immediateFuture(null);
+        } else {
+            future.addListener(
+                    () -> {
+                        if (future.isCancelled()) {
+                            result.cancel(/* mayInterruptIfRunning= */ false);
+                        } else {
+                            try {
+                                RenderingArtifact ignored = future.get();
+                                result.set(null);
+                            } catch (ExecutionException
+                                    | InterruptedException
+                                    | CancellationException e) {
+                                Log.e(TAG, "Failed to render layout", e);
+                                result.setException(e);
+                            }
+                        }
+                    },
+                    mUiExecutorService);
+        }
+        return result;
+    }
+
+    @UiThread
+    @SuppressWarnings({
+        "ReferenceEquality",
+        "ExecutorTaskName",
+    }) // layout == prevLayout is intentional (and enough in this case)
+    @NonNull
+    private ListenableFuture<RenderingArtifact> renderAndAttach(
+            @NonNull Layout layout,
+            @NonNull ResourceProto.Resources resources,
+            @NonNull ViewGroup attachParent,
+            @NonNull InflaterStatsLogger inflaterStatsLogger) {
         if (mLoggingUtils != null && mLoggingUtils.canLogD(TAG)) {
             mLoggingUtils.logD(TAG, "Layout received in #renderAndAttach:\n %s", layout.toString());
             mLoggingUtils.logD(
@@ -930,7 +1057,7 @@
 
         if (layout == mPrevLayout && mInflateParent != null) {
             // Nothing to do.
-            return Futures.immediateVoidFuture();
+            return Futures.immediateFuture(RenderingArtifact.skipped());
         }
 
         boolean isReattaching = false;
@@ -1000,10 +1127,11 @@
                                             layout,
                                             resources,
                                             prevRenderedMetadata,
-                                            parentViewProp));
+                                            parentViewProp,
+                                            inflaterStatsLogger));
             mCanReattachWithoutRendering = false;
         }
-        SettableFuture<Void> result = SettableFuture.create();
+        SettableFuture<RenderingArtifact> result = SettableFuture.create();
         if (!checkNotNull(mRenderFuture).isDone()) {
             ListenableFuture<RenderResult> rendererFuture = mRenderFuture;
             mRenderFuture.addListener(
@@ -1023,7 +1151,8 @@
                                                 checkNotNull(rendererFuture).get(),
                                                 /* isReattaching= */ false,
                                                 layout,
-                                                resources));
+                                                resources,
+                                                inflaterStatsLogger));
                             } catch (ExecutionException
                                     | InterruptedException
                                     | CancellationException e) {
@@ -1048,7 +1177,8 @@
                                 mRenderFuture.get(),
                                 isReattaching,
                                 layout,
-                                resources));
+                                resources,
+                                inflaterStatsLogger));
             } catch (ExecutionException | InterruptedException | CancellationException e) {
                 Log.e(TAG, "Failed to render layout", e);
                 result.setException(e);
@@ -1064,6 +1194,12 @@
      */
     public void invalidateCache() {
         mPrevResourcesVersion = null;
+        // Cancel any ongoing rendering which might have a reference to older app resources.
+        if (mRenderFuture != null && !mRenderFuture.isDone()) {
+            mRenderFuture.cancel(/* mayInterruptIfRunning= */ false);
+            mRenderFuture = null;
+            Log.w(TAG, "Cancelled ongoing rendering due to cache invalidation.");
+        }
     }
 
     @Nullable
@@ -1080,13 +1216,14 @@
     @UiThread
     @SuppressWarnings("ExecutorTaskName")
     @NonNull
-    private ListenableFuture<Void> postInflate(
+    private ListenableFuture<RenderingArtifact> postInflate(
             @NonNull ViewGroup attachParent,
             @Nullable ViewGroup prevInflateParent,
             @NonNull RenderResult renderResult,
             boolean isReattaching,
             @NonNull Layout layout,
-            @NonNull ResourceProto.Resources resources) {
+            @NonNull ResourceProto.Resources resources,
+            InflaterStatsLogger inflaterStatsLogger) {
         mCanReattachWithoutRendering = renderResult.canReattachWithoutRendering();
 
         if (renderResult instanceof InflatedIntoNewParentRenderResult) {
@@ -1101,9 +1238,10 @@
                             .inflateParent;
         }
 
-        ListenableFuture<Void> postInflateFuture =
-                renderResult.postInflate(attachParent, prevInflateParent, isReattaching);
-        SettableFuture<Void> result = SettableFuture.create();
+        ListenableFuture<RenderingArtifact> postInflateFuture =
+                renderResult.postInflate(
+                        attachParent, prevInflateParent, isReattaching, inflaterStatsLogger);
+        SettableFuture<RenderingArtifact> result = SettableFuture.create();
         if (!postInflateFuture.isDone()) {
             postInflateFuture.addListener(
                     () -> {
@@ -1114,20 +1252,24 @@
                                 | CancellationException e) {
                             result.setFuture(
                                     handlePostInflateFailure(
-                                            e, layout, resources, prevInflateParent, attachParent));
+                                            e,
+                                            layout,
+                                            resources,
+                                            prevInflateParent,
+                                            attachParent,
+                                            inflaterStatsLogger));
                         }
                     },
                     mUiExecutorService);
         } else {
             try {
-                postInflateFuture.get();
-                return Futures.immediateVoidFuture();
+                return immediateFuture(postInflateFuture.get());
             } catch (ExecutionException
                     | InterruptedException
                     | CancellationException
                     | ViewMutationException e) {
                 return handlePostInflateFailure(
-                        e, layout, resources, prevInflateParent, attachParent);
+                        e, layout, resources, prevInflateParent, attachParent, inflaterStatsLogger);
             }
         }
         return result;
@@ -1136,22 +1278,24 @@
     @UiThread
     @SuppressWarnings("ReferenceEquality") // layout == prevLayout is intentional
     @NonNull
-    private ListenableFuture<Void> handlePostInflateFailure(
+    private ListenableFuture<RenderingArtifact> handlePostInflateFailure(
             @NonNull Throwable error,
             @NonNull Layout layout,
             @NonNull ResourceProto.Resources resources,
             @Nullable ViewGroup prevInflateParent,
-            @NonNull ViewGroup parent) {
+            @NonNull ViewGroup parent,
+            InflaterStatsLogger inflaterStatsLogger) {
         // If a RuntimeError is thrown, it'll be wrapped in an UncheckedExecutionException
         Throwable e = error.getCause();
         if (e instanceof ViewMutationException) {
+            inflaterStatsLogger.logIgnoredFailure(IGNORED_FAILURE_APPLY_MUTATION_EXCEPTION);
             Log.w(TAG, "applyMutation failed." + e.getMessage());
             if (mPrevLayout == layout && parent == mAttachParent) {
                 Log.w(TAG, "Retrying full inflation.");
                 // Clear rendering metadata and prevLayout to force a full reinflation.
                 ProtoLayoutInflater.clearRenderedMetadata(checkNotNull(prevInflateParent));
                 mPrevLayout = null;
-                return renderAndAttach(layout, resources, parent);
+                return renderAndAttach(layout, resources, parent, inflaterStatsLogger);
             }
         } else {
             Log.e(TAG, "postInflate failed.", error);
@@ -1176,6 +1320,7 @@
     private void detachInternal() {
         if (mRenderFuture != null && !mRenderFuture.isDone()) {
             mRenderFuture.cancel(/* mayInterruptIfRunning= */ false);
+            mRenderFuture = null;
         }
         setLayoutVisibility(ProtoLayoutVisibilityState.VISIBILITY_STATE_INVISIBLE);
 
@@ -1227,9 +1372,12 @@
     }
 
     /** Returns true if the layout element depth doesn't exceed the given {@code allowedDepth}. */
-    private void checkLayoutDepth(LayoutElement layoutElement, int allowedDepth) {
+    private void checkLayoutDepth(
+            LayoutElement layoutElement,
+            int allowedDepth,
+            InflaterStatsLogger inflaterStatsLogger) {
         if (allowedDepth <= 0) {
-            throwExceptionForLayoutDepthCheckFailure();
+            handleLayoutDepthCheckFailure(inflaterStatsLogger);
         }
         List<LayoutElement> children = ImmutableList.of();
         switch (layoutElement.getInnerCase()) {
@@ -1245,28 +1393,32 @@
             case ARC:
                 List<ArcLayoutElement> arcElements = layoutElement.getArc().getContentsList();
                 if (!arcElements.isEmpty() && allowedDepth == 1) {
-                    throwExceptionForLayoutDepthCheckFailure();
+                    handleLayoutDepthCheckFailure(inflaterStatsLogger);
                 }
                 for (ArcLayoutElement element : arcElements) {
                     if (element.getInnerCase() == InnerCase.ADAPTER) {
-                        checkLayoutDepth(element.getAdapter().getContent(), allowedDepth - 1);
+                        checkLayoutDepth(
+                                element.getAdapter().getContent(),
+                                allowedDepth - 1,
+                                inflaterStatsLogger);
                     }
                 }
                 break;
             case SPANNABLE:
                 if (layoutElement.getSpannable().getSpansCount() > 0 && allowedDepth == 1) {
-                    throwExceptionForLayoutDepthCheckFailure();
+                    handleLayoutDepthCheckFailure(inflaterStatsLogger);
                 }
                 break;
             default:
                 // Other LayoutElements have depth of one.
         }
         for (LayoutElement child : children) {
-            checkLayoutDepth(child, allowedDepth - 1);
+            checkLayoutDepth(child, allowedDepth - 1, inflaterStatsLogger);
         }
     }
 
-    private void throwExceptionForLayoutDepthCheckFailure() {
+    private void handleLayoutDepthCheckFailure(InflaterStatsLogger inflaterStatsLogger) {
+        inflaterStatsLogger.logInflationFailed(INFLATION_FAILURE_REASON_LAYOUT_DEPTH_EXCEEDED);
         mPrevLayoutAlreadyFailingDepthCheck = true;
         throw new IllegalStateException(
                 "Layout depth exceeds maximum allowed depth: " + MAX_LAYOUT_ELEMENT_DEPTH);
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 1ca9a23..4997dde 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -28,7 +28,7 @@
 import static androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.getParentNodePosId;
 
 import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
-import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
 
 import static java.lang.Math.max;
 import static java.lang.Math.min;
@@ -180,9 +180,12 @@
 import androidx.wear.protolayout.renderer.ProtoLayoutTheme.FontSet;
 import androidx.wear.protolayout.renderer.R;
 import androidx.wear.protolayout.renderer.common.LoggingUtils;
+import androidx.wear.protolayout.renderer.common.NoOpProviderStatsLogger;
 import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer;
 import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.LayoutDiff;
 import androidx.wear.protolayout.renderer.common.ProtoLayoutDiffer.TreeNodeWithChange;
+import androidx.wear.protolayout.renderer.common.ProviderStatsLogger.InflaterStatsLogger;
+import androidx.wear.protolayout.renderer.common.RenderingArtifact;
 import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
 import androidx.wear.protolayout.renderer.inflater.RenderedMetadata.LayoutInfo;
@@ -301,6 +304,7 @@
     final String mClickableIdExtra;
 
     @Nullable private final LoggingUtils mLoggingUtils;
+    @NonNull private final InflaterStatsLogger mInflaterStatsLogger;
 
     @Nullable final Executor mLoadActionExecutor;
     final LoadActionListener mLoadActionListener;
@@ -528,6 +532,7 @@
         @NonNull private final String mClickableIdExtra;
 
         @Nullable private final LoggingUtils mLoggingUtils;
+        @NonNull private final InflaterStatsLogger mInflaterStatsLogger;
         @Nullable private final ProtoLayoutExtensionViewProvider mExtensionViewProvider;
         private final boolean mAnimationEnabled;
 
@@ -547,6 +552,7 @@
                 @Nullable ProtoLayoutExtensionViewProvider extensionViewProvider,
                 @NonNull String clickableIdExtra,
                 @Nullable LoggingUtils loggingUtils,
+                @NonNull InflaterStatsLogger inflaterStatsLogger,
                 boolean animationEnabled,
                 boolean allowLayoutChangingBindsWithoutDefault,
                 boolean applyFontVariantBodyAsDefault) {
@@ -562,6 +568,7 @@
             this.mAllowLayoutChangingBindsWithoutDefault = allowLayoutChangingBindsWithoutDefault;
             this.mClickableIdExtra = clickableIdExtra;
             this.mLoggingUtils = loggingUtils;
+            this.mInflaterStatsLogger = inflaterStatsLogger;
             this.mExtensionViewProvider = extensionViewProvider;
             this.mApplyFontVariantBodyAsDefault = applyFontVariantBodyAsDefault;
         }
@@ -638,6 +645,12 @@
             return mLoggingUtils;
         }
 
+        /** Stats logger used for telemetry. */
+        @NonNull
+        public InflaterStatsLogger getInflaterStatsLogger() {
+            return mInflaterStatsLogger;
+        }
+
         /** View provider for the renderer extension. */
         @Nullable
         public ProtoLayoutExtensionViewProvider getExtensionViewProvider() {
@@ -678,6 +691,7 @@
             @Nullable private String mClickableIdExtra;
 
             @Nullable private LoggingUtils mLoggingUtils;
+            @Nullable private InflaterStatsLogger mInflaterStatsLogger;
 
             @Nullable private ProtoLayoutExtensionViewProvider mExtensionViewProvider = null;
 
@@ -788,6 +802,14 @@
                 return this;
             }
 
+            /** Sets the stats logger used for telemetry. */
+            @NonNull
+            public Builder setInflaterStatsLogger(
+                    @NonNull InflaterStatsLogger inflaterStatsLogger) {
+                this.mInflaterStatsLogger = inflaterStatsLogger;
+                return this;
+            }
+
             /**
              * Sets whether a "layout changing" data bind can be applied without the
              * "value_for_layout" field being filled in, or being set to zero / empty. Defaults to
@@ -834,7 +856,11 @@
                 if (mClickableIdExtra == null) {
                     mClickableIdExtra = DEFAULT_CLICKABLE_ID_EXTRA;
                 }
-
+                if (mInflaterStatsLogger == null) {
+                    mInflaterStatsLogger =
+                            new NoOpProviderStatsLogger("No implementation was provided")
+                                    .createInflaterStatsLogger();
+                }
                 return new Config(
                         mUiContext,
                         mLayout,
@@ -847,6 +873,7 @@
                         mExtensionViewProvider,
                         checkNotNull(mClickableIdExtra),
                         mLoggingUtils,
+                        mInflaterStatsLogger,
                         mAnimationEnabled,
                         mAllowLayoutChangingBindsWithoutDefault,
                         mApplyFontVariantBodyAsDefault);
@@ -873,6 +900,7 @@
                 config.getAllowLayoutChangingBindsWithoutDefault();
         this.mClickableIdExtra = config.getClickableIdExtra();
         this.mLoggingUtils = config.getLoggingUtils();
+        this.mInflaterStatsLogger = config.getInflaterStatsLogger();
         this.mExtensionViewProvider = config.getExtensionViewProvider();
         this.mApplyFontVariantBodyAsDefault = config.getApplyFontVariantBodyAsDefault();
     }
@@ -1731,7 +1759,9 @@
 
         if (modifiers.hasTransformation()) {
             applyTransformation(
-                    wrapper == null ? view : wrapper, modifiers.getTransformation(), posId,
+                    wrapper == null ? view : wrapper,
+                    modifiers.getTransformation(),
+                    posId,
                     pipelineMaker);
         }
 
@@ -2565,8 +2595,7 @@
                     }
 
                     @Override
-                    public void onViewDetachedFromWindow(@NonNull View v) {
-                    }
+                    public void onViewDetachedFromWindow(@NonNull View v) {}
                 });
     }
 
@@ -3152,7 +3181,7 @@
      *     to the image view; otherwise returns null to indicate the failure of setting drawable.
      */
     @Nullable
-    private static Drawable setImageDrawable(
+    private Drawable setImageDrawable(
             ImageView imageView, Future<Drawable> drawableFuture, String protoResId) {
         try {
             return setImageDrawable(imageView, drawableFuture.get(), protoResId);
@@ -3169,8 +3198,10 @@
      *     null to indicate the failure of setting drawable.
      */
     @Nullable
-    private static Drawable setImageDrawable(
-            ImageView imageView, Drawable drawable, String protoResId) {
+    private Drawable setImageDrawable(ImageView imageView, Drawable drawable, String protoResId) {
+        if (drawable != null) {
+            mInflaterStatsLogger.logDrawableUsage(drawable);
+        }
         if (drawable instanceof BitmapDrawable
                 && ((BitmapDrawable) drawable).getBitmap().getByteCount()
                         > DEFAULT_MAX_BITMAP_RAW_SIZE) {
@@ -3300,7 +3331,7 @@
                     Log.w(
                             TAG,
                             "ArcLine length's value_for_layout is not a positive value. Element"
-                                + " won't be visible.");
+                                    + " won't be visible.");
                 }
                 sizeWrapper.setSweepAngleDegrees(sizeForLayout);
                 sizedLp.setAngularAlignment(
@@ -4079,8 +4110,9 @@
             Optional<ProtoLayoutDynamicDataPipeline.PipelineMaker> pipelineMaker) {
         if (dpProp.hasDynamicValue() && pipelineMaker.isPresent()) {
             try {
-                pipelineMaker.get().addPipelineFor(dpProp, dpProp.getValue(), posId,
-                        dynamicValueConsumer);
+                pipelineMaker
+                        .get()
+                        .addPipelineFor(dpProp, dpProp.getValue(), posId, dynamicValueConsumer);
             } catch (RuntimeException ex) {
                 Log.e(TAG, "Error building pipeline", ex);
                 staticValueConsumer.accept(dpProp.getValue());
@@ -4143,7 +4175,9 @@
                 pipelineMaker
                         .get()
                         .addPipelineFor(
-                                floatProp.getDynamicValue(), floatProp.getValue(), posId,
+                                floatProp.getDynamicValue(),
+                                floatProp.getValue(),
+                                posId,
                                 dynamicValueconsumer);
             } catch (RuntimeException ex) {
                 Log.e(TAG, "Error building pipeline", ex);
@@ -4548,7 +4582,7 @@
     /** Apply the mutation that was previously computed with {@link #computeMutation}. */
     @UiThread
     @NonNull
-    public ListenableFuture<Void> applyMutation(
+    public ListenableFuture<RenderingArtifact> applyMutation(
             @NonNull ViewGroup prevInflatedParent, @NonNull ViewGroupMutation groupMutation) {
         RenderedMetadata prevRenderedMetadata = getRenderedMetadata(prevInflatedParent);
         if (prevRenderedMetadata != null
@@ -4561,11 +4595,11 @@
         }
         if (groupMutation.isNoOp()) {
             // Nothing to do.
-            return immediateVoidFuture();
+            return immediateFuture(RenderingArtifact.create(mInflaterStatsLogger));
         }
 
         if (groupMutation.mPipelineMaker.isPresent()) {
-            SettableFuture<Void> result = SettableFuture.create();
+            SettableFuture<RenderingArtifact> result = SettableFuture.create();
             groupMutation
                     .mPipelineMaker
                     .get()
@@ -4575,7 +4609,7 @@
                             () -> {
                                 try {
                                     applyMutationInternal(prevInflatedParent, groupMutation);
-                                    result.set(null);
+                                    result.set(RenderingArtifact.create(mInflaterStatsLogger));
                                 } catch (ViewMutationException ex) {
                                     result.setException(ex);
                                 }
@@ -4584,7 +4618,7 @@
         } else {
             try {
                 applyMutationInternal(prevInflatedParent, groupMutation);
-                return immediateVoidFuture();
+                return immediateFuture(RenderingArtifact.create(mInflaterStatsLogger));
             } catch (ViewMutationException ex) {
                 return immediateFailedFuture(ex);
             }
@@ -4593,6 +4627,7 @@
 
     private void applyMutationInternal(
             @NonNull ViewGroup prevInflatedParent, @NonNull ViewGroupMutation groupMutation) {
+        mInflaterStatsLogger.logMutationChangedNodes(groupMutation.mInflatedViews.size());
         for (InflatedView inflatedView : groupMutation.mInflatedViews) {
             String posId = inflatedView.getTag();
             if (posId == null) {
@@ -4620,15 +4655,21 @@
             }
             // Remove the touch delegate to the view to be updated
             if (immediateParent.getTouchDelegate() != null) {
-                ((TouchDelegateComposite) immediateParent.getTouchDelegate())
-                        .removeDelegate(viewToUpdate);
+                TouchDelegateComposite delegateComposite =
+                        (TouchDelegateComposite) immediateParent.getTouchDelegate();
+                delegateComposite.removeDelegate(viewToUpdate);
 
                 // Make sure to remove the touch delegate when the actual clickable view is wrapped,
                 // for example ImageView inside the RatioViewWrapper
                 if (viewToUpdate instanceof ViewGroup
                         && ((ViewGroup) viewToUpdate).getChildCount() > 0) {
-                    ((TouchDelegateComposite) immediateParent.getTouchDelegate())
-                            .removeDelegate(((ViewGroup) viewToUpdate).getChildAt(0));
+                    delegateComposite.removeDelegate(((ViewGroup) viewToUpdate).getChildAt(0));
+                }
+
+                // If no more touch delegate left in the composite, remove it completely from the
+                // parent
+                if (delegateComposite.isEmpty()) {
+                    immediateParent.setTouchDelegate(null);
                 }
             }
             immediateParent.removeViewAt(childIndex);
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
index e6bf97c..6184ac8 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/TouchDelegateComposite.java
@@ -83,6 +83,10 @@
         mDelegates.remove(delegateView);
     }
 
+    boolean isEmpty() {
+        return mDelegates.isEmpty();
+    }
+
     @Override
     public boolean onTouchEvent(@NonNull MotionEvent event) {
         boolean eventForwarded = false;
@@ -125,7 +129,7 @@
     @Override
     @NonNull
     public AccessibilityNodeInfo.TouchDelegateInfo getTouchDelegateInfo() {
-        if (VERSION.SDK_INT >= VERSION_CODES.Q) {
+        if (VERSION.SDK_INT >= VERSION_CODES.Q && !mDelegates.isEmpty()) {
             Map<Region, View> targetMap = new ArrayMap<>(mDelegates.size());
             for (Map.Entry<View, DelegateInfo> entry : mDelegates.entrySet()) {
                 AccessibilityNodeInfo.TouchDelegateInfo info =
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
index 6db2dfb..d095d58 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/impl/ProtoLayoutViewInstanceTest.java
@@ -53,6 +53,7 @@
 import androidx.wear.protolayout.expression.pipeline.StateStore;
 import androidx.wear.protolayout.proto.LayoutElementProto.Layout;
 import androidx.wear.protolayout.proto.ResourceProto.Resources;
+import androidx.wear.protolayout.renderer.common.RenderingArtifact;
 import androidx.wear.protolayout.renderer.helper.TestDsl.LayoutNode;
 import androidx.wear.protolayout.renderer.impl.ProtoLayoutViewInstance.Config;
 
@@ -97,8 +98,8 @@
     @Test
     public void adaptiveUpdateRatesDisabled_attach_reinflatesCompletely() throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -108,7 +109,7 @@
         assertThat(layout1).hasSize(1);
 
         result =
-                mInstanceUnderTest.renderAndAttach(
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -120,8 +121,8 @@
     @Test
     public void adaptiveUpdateRatesEnabled_attach_appliesDiffOnly() throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -131,7 +132,7 @@
         assertThat(layout1).hasSize(1);
 
         result =
-                mInstanceUnderTest.renderAndAttach(
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -144,8 +145,8 @@
     @Test
     public void reattach_usesCachedLayoutForDiffUpdate() throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -156,7 +157,7 @@
         mInstanceUnderTest.detach(mRootContainer);
 
         result =
-                mInstanceUnderTest.renderAndAttach(
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -172,8 +173,8 @@
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
 
         // First one that does the full layout update.
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT2))), RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -184,7 +185,7 @@
 
         // Second one that applies mutation only.
         result =
-                mInstanceUnderTest.renderAndAttach(
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT3))), RESOURCES, mRootContainer);
         // Detach so it can't apply update.
         mInstanceUnderTest.detach(mRootContainer);
@@ -200,9 +201,8 @@
 
         // Render the first layout.
         Layout layout1 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT2)));
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
-                        layout1, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout1, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
@@ -227,9 +227,7 @@
         // not changed part of the layout was also changed in inflated View.
         Layout layout2 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT3)));
 
-        result =
-                mInstanceUnderTest.renderAndAttach(
-                        layout2, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
 
         // Make sure future is computing result.
         assertThat(result.isDone()).isFalse();
@@ -252,8 +250,8 @@
 
         // Render the first layout.
         Layout layout1 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT2)));
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout1, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
@@ -263,7 +261,7 @@
         assertThat(findViewsWithText(mRootContainer, TEXT2)).hasSize(1);
 
         Layout layout2 = layout(column(dynamicFixedText(TEXT1), dynamicFixedText(TEXT3)));
-        result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
         // Make sure future is computing result.
         assertThat(result.isDone()).isFalse();
         shadowOf(Looper.getMainLooper()).idle();
@@ -279,13 +277,13 @@
     public void adaptiveUpdateRatesEnabled_ongoingRendering_skipsPreviousLayout() {
         FrameLayout container = new FrameLayout(mApplicationContext);
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result1 =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result1 =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container);
         assertThat(result1.isDone()).isFalse();
 
-        ListenableFuture<Void> result2 =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result2 =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT3))), RESOURCES, container);
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -301,14 +299,14 @@
         FrameLayout container1 = new FrameLayout(mApplicationContext);
         FrameLayout container2 = new FrameLayout(mApplicationContext);
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container1);
 
         assertThrows(
                 IllegalStateException.class,
                 () ->
-                        mInstanceUnderTest.renderAndAttach(
+                        mInstanceUnderTest.renderLayoutAndAttach(
                                 layout(column(text(TEXT1), text(TEXT2))), RESOURCES, container2));
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -321,12 +319,14 @@
         FrameLayout container1 = new FrameLayout(mApplicationContext);
         FrameLayout container2 = new FrameLayout(mApplicationContext);
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result1 =
-                mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container1);
+        ListenableFuture<RenderingArtifact> result1 =
+                mInstanceUnderTest.renderLayoutAndAttach(
+                        layout(text(TEXT1)), RESOURCES, container1);
         mInstanceUnderTest.detach(container1);
 
-        ListenableFuture<Void> result2 =
-                mInstanceUnderTest.renderAndAttach(layout(text(TEXT1)), RESOURCES, container2);
+        ListenableFuture<RenderingArtifact> result2 =
+                mInstanceUnderTest.renderLayoutAndAttach(
+                        layout(text(TEXT1)), RESOURCES, container2);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertThat(result1.isCancelled()).isTrue();
@@ -341,13 +341,13 @@
             throws Exception {
         Layout layout = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
 
-        result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
 
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
@@ -360,14 +360,14 @@
         Layout layout1 = layout(text(TEXT1));
         Layout layout2 = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout1, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout1, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
 
         // Make sure we have an UnchangedRenderResult
-        result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
@@ -377,7 +377,7 @@
         assertThat(findViewsWithText(mRootContainer, TEXT1)).isEmpty();
         shadowOf(Looper.getMainLooper()).idle();
 
-        result = mInstanceUnderTest.renderAndAttach(layout2, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout2, RESOURCES, mRootContainer);
 
         assertThat(result.isDone()).isTrue();
         assertNoException(result);
@@ -388,8 +388,8 @@
     public void fullInflationResultCanBeReused() throws Exception {
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
         Layout layout = layout(text(TEXT1));
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
@@ -397,7 +397,7 @@
         ListenableFuture<?> renderFuture = mInstanceUnderTest.mRenderFuture;
 
         mInstanceUnderTest.detach(mRootContainer);
-        result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
 
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
@@ -409,15 +409,15 @@
             throws Exception {
         Layout layout = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
         List<View> textViews1 = findViewsWithText(mRootContainer, TEXT1);
         assertThat(textViews1).hasSize(1);
 
         mInstanceUnderTest.close();
-        result = mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
 
         assertThat(shadowOf(Looper.getMainLooper()).isIdle()).isFalse();
         shadowOf(Looper.getMainLooper()).idle();
@@ -431,8 +431,8 @@
     public void detach_clearsHostView() throws Exception {
         Layout layout = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
         assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
@@ -449,14 +449,14 @@
         Layout layout2 = layout(text(TEXT1));
         Resources resources2 = Resources.newBuilder().setVersion("2").build();
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout1, resources1, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout1, resources1, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
         assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
         View view1 = findViewsWithText(mRootContainer, TEXT1).get(0);
 
-        result = mInstanceUnderTest.renderAndAttach(layout2, resources2, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout2, resources2, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
@@ -472,15 +472,15 @@
         Layout layout2 = layout(text(TEXT1));
         Resources resources2 = Resources.newBuilder().setVersion("1").build();
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout1, resources1, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout1, resources1, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
         assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
         View view1 = findViewsWithText(mRootContainer, TEXT1).get(0);
 
         mInstanceUnderTest.invalidateCache();
-        result = mInstanceUnderTest.renderAndAttach(layout2, resources2, mRootContainer);
+        result = mInstanceUnderTest.renderLayoutAndAttach(layout2, resources2, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertNoException(result);
@@ -490,13 +490,28 @@
     }
 
     @Test
+    public void invalidateCache_ongoingInflation_oldInflationGetsCancelled() throws Exception {
+        Layout layout1 = layout(text(TEXT1));
+        Resources resources1 = Resources.newBuilder().setVersion("1").build();
+        setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout1, resources1, mRootContainer);
+
+        mInstanceUnderTest.invalidateCache();
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(result.isCancelled()).isTrue();
+        assertThat(findViewsWithText(mRootContainer, TEXT1)).isEmpty();
+    }
+
+    @Test
     public void adaptiveUpdateRatesEnabled_rootElementdiff_keepsElementCentered() throws Exception {
         int dimension = 50;
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
 
         // Full inflation.
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(
                                 column(
                                         props -> {
@@ -518,7 +533,7 @@
 
         // Diff update only for the root element.
         result =
-                mInstanceUnderTest.renderAndAttach(
+                mInstanceUnderTest.renderLayoutAndAttach(
                         layout(
                                 column(
                                         props -> {
@@ -546,8 +561,8 @@
     public void close_clearsHostView() throws Exception {
         Layout layout = layout(text(TEXT1));
         setupInstance(/* adaptiveUpdateRatesEnabled= */ true);
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
         assertThat(findViewsWithText(mRootContainer, TEXT1)).hasSize(1);
@@ -562,7 +577,9 @@
         setupInstance(/* adaptiveUpdateRatesEnabled= */ false);
         assertThrows(
                 ExecutionException.class,
-                () -> renderAndAttachLayout(layout(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH + 1))));
+                () ->
+                        renderLayoutAndAttachLayout(
+                                layout(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH + 1))));
     }
 
     @Test
@@ -573,8 +590,8 @@
         for (int i = 0; i < children.length; i++) {
             children[i] = recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH - 1);
         }
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         // MAX_LAYOUT_ELEMENT_DEPTH branches of depth MAX_LAYOUT_ELEMENT_DEPTH - 1.
                         // Total depth is MAX_LAYOUT_ELEMENT_DEPTH (if we count the head).
                         layout(box(children)), RESOURCES, mRootContainer);
@@ -591,7 +608,7 @@
         assertThrows(
                 ExecutionException.class,
                 () ->
-                        renderAndAttachLayout(
+                        renderLayoutAndAttachLayout(
                                 // Total number of views is = MAX_LAYOUT_ELEMENT_DEPTH  + 1 (span
                                 // text)
                                 layout(
@@ -599,8 +616,8 @@
                                                 MAX_LAYOUT_ELEMENT_DEPTH,
                                                 spannable(spanText("Hello"))))));
 
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         // Total number of views is = (MAX_LAYOUT_ELEMENT_DEPTH -1)  + 1 (span text)
                         layout(
                                 recursiveBox(
@@ -620,12 +637,12 @@
         assertThrows(
                 ExecutionException.class,
                 () ->
-                        renderAndAttachLayout(
+                        renderLayoutAndAttachLayout(
                                 // Total number of views is = 1 (Arc) + (MAX_LAYOUT_ELEMENT_DEPTH)
                                 layout(arc(arcAdapter(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH))))));
 
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(
                         // Total number of views is = 1 (Arc) + (MAX_LAYOUT_ELEMENT_DEPTH - 1)
                         // = MAX_LAYOUT_ELEMENT_DEPTH
                         layout(arc(arcAdapter(recursiveBox(MAX_LAYOUT_ELEMENT_DEPTH - 1)))),
@@ -637,9 +654,9 @@
         assertThat(mRootContainer.getChildCount()).isEqualTo(1);
     }
 
-    private void renderAndAttachLayout(Layout layout) throws Exception {
-        ListenableFuture<Void> result =
-                mInstanceUnderTest.renderAndAttach(layout, RESOURCES, mRootContainer);
+    private void renderLayoutAndAttachLayout(Layout layout) throws Exception {
+        ListenableFuture<RenderingArtifact> result =
+                mInstanceUnderTest.renderLayoutAndAttach(layout, RESOURCES, mRootContainer);
         shadowOf(Looper.getMainLooper()).idle();
         assertNoException(result);
     }
@@ -690,7 +707,8 @@
         return views;
     }
 
-    private static void assertNoException(ListenableFuture<Void> result) throws Exception {
+    private static void assertNoException(ListenableFuture<RenderingArtifact> result)
+            throws Exception {
         // Assert that result hasn't thrown exception.
         result.get();
     }
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
index da07985..1a46140 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
@@ -83,7 +83,6 @@
 import androidx.core.content.ContextCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.expression.AppDataKey;
 import androidx.wear.protolayout.expression.DynamicBuilders;
 import androidx.wear.protolayout.expression.pipeline.FixedQuotaManagerImpl;
@@ -203,6 +202,8 @@
 import androidx.wear.protolayout.proto.TypesProto.StringProp;
 import androidx.wear.protolayout.protobuf.ByteString;
 import androidx.wear.protolayout.renderer.ProtoLayoutTheme;
+import androidx.wear.protolayout.renderer.common.RenderingArtifact;
+import androidx.wear.protolayout.renderer.common.SeekableAnimatedVectorDrawable;
 import androidx.wear.protolayout.renderer.dynamicdata.ProtoLayoutDynamicDataPipeline;
 import androidx.wear.protolayout.renderer.helper.TestFingerprinter;
 import androidx.wear.protolayout.renderer.inflater.ProtoLayoutInflater.InflateResult;
@@ -222,6 +223,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowChoreographer;
 import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowPackageManager;
@@ -543,37 +545,41 @@
         int width = 10;
         int height = 12;
         byte[] payload = "Hello World".getBytes(UTF_8);
+        LayoutElement extension =
+                LayoutElement.newBuilder()
+                        .setExtension(
+                                ExtensionLayoutElement.newBuilder()
+                                        .setExtensionId("foo")
+                                        .setPayload(ByteString.copyFrom(payload))
+                                        .setWidth(
+                                                ExtensionDimension.newBuilder()
+                                                        .setLinearDimension(dp(width))
+                                                        .build())
+                                        .setHeight(
+                                                ExtensionDimension.newBuilder()
+                                                        .setLinearDimension(dp(height))
+                                                        .build()))
+                        .build();
         LayoutElement root =
                 LayoutElement.newBuilder()
-                    .setBox(
-                        Box.newBuilder()
-                            // Outer box's width and height left at default value of "wrap"
-                            .addContents(
-                                LayoutElement.newBuilder()
-                                    .setExtension(
-                                        ExtensionLayoutElement.newBuilder()
-                                            .setExtensionId("foo")
-                                            .setPayload(ByteString.copyFrom(payload))
-                                            .setWidth(
-                                                ExtensionDimension.newBuilder()
-                                                    .setLinearDimension(dp(width))
-                                                    .build())
-                                            .setHeight(
-                                                ExtensionDimension.newBuilder()
-                                                    .setLinearDimension(dp(height))
-                                                    .build()))))
+                        .setBox(
+                                Box.newBuilder()
+                                        // Outer box's width and height left at default value of
+                                        // "wrap"
+                                        .addContents(extension))
                         .build();
 
         FrameLayout rootLayout =
                 renderer(
-                    newRendererConfigBuilder(fingerprintedLayout(root))
-                        .setExtensionViewProvider(
-                            (extensionPayload, id) -> {
-                                TextView returnedView = new TextView(getApplicationContext());
-                                returnedView.setText("testing");
+                                newRendererConfigBuilder(fingerprintedLayout(root))
+                                        .setExtensionViewProvider(
+                                                (extensionPayload, id) -> {
+                                                    TextView returnedView =
+                                                            new TextView(getApplicationContext());
+                                                    returnedView.setText("testing");
 
-                                return returnedView;
-                            }))
+                                                    return returnedView;
+                                                }))
                         .inflate();
 
         // Check that the outer box is displayed and it has a child.
@@ -976,6 +982,23 @@
 
         // A column with a row (Spacer + Spacer) and Spacer, everything has weighted expand
         // dimension.
+
+        Row rowWithSpacers =
+                Row.newBuilder()
+                        .setWidth(expand())
+                        .setHeight(
+                                ContainerDimension.newBuilder()
+                                        .setExpandedDimension(expandWithWeight(heightWeight1))
+                                        .build())
+                        .addContents(
+                                LayoutElement.newBuilder()
+                                        .setSpacer(
+                                                buildExpandedSpacer(widthWeight1, DEFAULT_WEIGHT)))
+                        .addContents(
+                                LayoutElement.newBuilder()
+                                        .setSpacer(
+                                                buildExpandedSpacer(widthWeight2, DEFAULT_WEIGHT)))
+                        .build();
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setColumn(
@@ -983,26 +1006,13 @@
                                         .setWidth(expand())
                                         .setHeight(expand())
                                         .addContents(
-                                                LayoutElement.newBuilder()
-                                                        .setRow(
-                                                                Row.newBuilder()
-                                                                        .setWidth(expand())
-                                                                        .setHeight(
-                                                                                ContainerDimension.newBuilder()
-                                                                                        .setExpandedDimension(expandWithWeight(heightWeight1))
-                                                                                        .build())
-                                                                        .addContents(
-                                                                                LayoutElement.newBuilder()
-                                                                                        .setSpacer(
-                                                                                                buildExpandedSpacer(widthWeight1, DEFAULT_WEIGHT)))
-                                                                        .addContents(
-                                                                                LayoutElement.newBuilder()
-                                                                                        .setSpacer(
-                                                                                                buildExpandedSpacer(
-                                                                                                        widthWeight2, DEFAULT_WEIGHT)))))
+                                                LayoutElement.newBuilder().setRow(rowWithSpacers))
                                         .addContents(
                                                 LayoutElement.newBuilder()
-                                                        .setSpacer(buildExpandedSpacer(DEFAULT_WEIGHT, heightWeight2)))
+                                                        .setSpacer(
+                                                                buildExpandedSpacer(
+                                                                        DEFAULT_WEIGHT,
+                                                                        heightWeight2)))
                                         .build())
                         .build();
 
@@ -1034,9 +1044,9 @@
                                                         .setExpandedDimension(
                                                                 ExpandedDimensionProp
                                                                         .getDefaultInstance()))
-                                        .setWidth(SpacerDimension
-                                                .newBuilder()
-                                                .setLinearDimension(dp(width))))
+                                        .setWidth(
+                                                SpacerDimension.newBuilder()
+                                                        .setLinearDimension(dp(width))))
                         .build();
 
         FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
@@ -1543,17 +1553,17 @@
                 ContainerDimension.newBuilder().setLinearDimension(dp(childSize)).build();
 
         LayoutElement childBox =
-                LayoutElement.newBuilder().setBox(
-                        Box.newBuilder()
-                                .setWidth(childBoxSize)
-                                .setHeight(childBoxSize)
-                                .setModifiers(
-                                        Modifiers.newBuilder()
-                                                .setClickable(
-                                                        Clickable.newBuilder()
-                                                                .setId("foo")
-                                                                .setOnClick(
-                                                                        action))))
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(childBoxSize)
+                                        .setHeight(childBoxSize)
+                                        .setModifiers(
+                                                Modifiers.newBuilder()
+                                                        .setClickable(
+                                                                Clickable.newBuilder()
+                                                                        .setId("foo")
+                                                                        .setOnClick(action))))
                         .build();
 
         LayoutElement root =
@@ -1563,13 +1573,14 @@
                                         .setWidth(parentBoxSize)
                                         .setHeight(parentBoxSize)
                                         .addContents(childBox))
-                                        .build();
+                        .build();
 
         State.Builder receivedState = State.newBuilder();
         FrameLayout rootLayout =
                 renderer(
-                        newRendererConfigBuilder(fingerprintedLayout(root), resourceResolvers())
-                                .setLoadActionListener(receivedState::mergeFrom))
+                                newRendererConfigBuilder(
+                                                fingerprintedLayout(root), resourceResolvers())
+                                        .setLoadActionListener(receivedState::mergeFrom))
                         .inflate();
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -1627,8 +1638,7 @@
                                         .setWidth(
                                                 SpacerDimension.newBuilder()
                                                         .setLinearDimension(dp(spacerSize))
-                                                        .build()
-                                        ));
+                                                        .build()));
 
         //           |--clickable area child box 1 (5 - 35)--|
         //                                          |---clickable area child box 2 (30-60)--|
@@ -1647,8 +1657,7 @@
                                                                                 dp(clickTargetSize))
                                                                         .setMinimumClickableHeight(
                                                                                 dp(clickTargetSize))
-                                                                        .setOnClick(
-                                                                                action)
+                                                                        .setOnClick(action)
                                                                         .setId("foo1"))))
                         .build();
 
@@ -1666,8 +1675,7 @@
                                                                                 dp(clickTargetSize))
                                                                         .setMinimumClickableHeight(
                                                                                 dp(clickTargetSize))
-                                                                        .setOnClick(
-                                                                                action)
+                                                                        .setOnClick(action)
                                                                         .setId("foo2"))))
                         .build();
 
@@ -1692,8 +1700,9 @@
         State.Builder receivedState = State.newBuilder();
         FrameLayout rootLayout =
                 renderer(
-                        newRendererConfigBuilder(fingerprintedLayout(root), resourceResolvers())
-                                .setLoadActionListener(receivedState::mergeFrom))
+                                newRendererConfigBuilder(
+                                                fingerprintedLayout(root), resourceResolvers())
+                                        .setLoadActionListener(receivedState::mergeFrom))
                         .inflate();
 
         ShadowLooper.runUiThreadTasks();
@@ -1812,8 +1821,8 @@
 
         // Compute the mutation
         ViewGroupMutation mutation =
-                renderer.computeMutation(getRenderedMetadata(rootLayout),
-                        fingerprintedLayout(root2));
+                renderer.computeMutation(
+                        getRenderedMetadata(rootLayout), fingerprintedLayout(root2));
         assertThat(mutation).isNotNull();
         assertThat(mutation.isNoOp()).isFalse();
 
@@ -1848,8 +1857,9 @@
                         .setSpacer(
                                 Spacer.newBuilder()
                                         .setWidth(
-                                                SpacerDimension.newBuilder().setLinearDimension(
-                                                        dp(spacerSize)).build()));
+                                                SpacerDimension.newBuilder()
+                                                        .setLinearDimension(dp(spacerSize))
+                                                        .build()));
 
         int parentHeight = 45;
         int parentWidth = 125;
@@ -1916,13 +1926,13 @@
 
         // Compute the mutation
         ViewGroupMutation mutation =
-                renderer.computeMutation(getRenderedMetadata(rootLayout),
-                        fingerprintedLayout(root2));
+                renderer.computeMutation(
+                        getRenderedMetadata(rootLayout), fingerprintedLayout(root2));
         assertThat(mutation).isNotNull();
         assertThat(mutation.isNoOp()).isFalse();
 
         // Apply the mutation
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(rootLayout, mutation);
         shadowOf(getMainLooper()).idle();
         try {
@@ -1958,6 +1968,108 @@
     }
 
     @Test
+    @Config(minSdk = VERSION_CODES.Q)
+    public void inflateThenMutate_withClickableSizeChange_clickableModifier_extendClickTargetSize()
+    {
+        Action action = Action.newBuilder().setLoadAction(LoadAction.getDefaultInstance()).build();
+        int parentSize = 50;
+        ContainerDimension parentBoxSize =
+                ContainerDimension.newBuilder().setLinearDimension(dp(parentSize)).build();
+        ContainerDimension childBoxSize =
+                ContainerDimension.newBuilder().setLinearDimension(dp(parentSize / 2f)).build();
+
+        Modifiers testModifiers1 =
+                Modifiers.newBuilder()
+                        .setClickable(Clickable.newBuilder().setOnClick(action).setId("foo1"))
+                        .build();
+
+        // Child box has a size smaller than the minimum clickable size, touch delegation is
+        // required.
+        LayoutElement root =
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(parentBoxSize)
+                                        .setHeight(parentBoxSize)
+                                        .addContents(
+                                                LayoutElement.newBuilder()
+                                                        .setBox(
+                                                                Box.newBuilder()
+                                                                        .setWidth(childBoxSize)
+                                                                        .setHeight(childBoxSize)
+                                                                        .setModifiers(
+                                                                                testModifiers1))))
+                        .build();
+
+        State.Builder receivedState = State.newBuilder();
+        Renderer renderer =
+                renderer(
+                        newRendererConfigBuilder(fingerprintedLayout(root), resourceResolvers())
+                                .setLoadActionListener(receivedState::mergeFrom));
+        FrameLayout rootLayout = renderer.inflate();
+        ViewGroup parent = (ViewGroup) rootLayout.getChildAt(0);
+        // Confirm the touch delegation has happened.
+        assertThat(parent.getTouchDelegate()).isNotNull();
+        // Dispatch a click event to the parent View within the expanded clickable area;
+        // it should trigger the LoadAction...
+        receivedState.clearLastClickableId();
+        dispatchTouchEvent(parent, 5, 5);
+        expect.that(receivedState.getLastClickableId()).isEqualTo("foo1");
+
+        // Produce a new layout with child box specifies its minimum clickable size, NO touch
+        // delegation is required.
+        Modifiers testModifiers2 =
+                Modifiers.newBuilder()
+                        .setClickable(
+                                Clickable.newBuilder()
+                                        .setOnClick(action)
+                                        .setId("foo2")
+                                        .setMinimumClickableWidth(dp(parentSize / 2f))
+                                        .setMinimumClickableHeight(dp(parentSize / 2f)))
+                        .build();
+        LayoutElement root2 =
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(parentBoxSize)
+                                        .setHeight(parentBoxSize)
+                                        .addContents(
+                                                LayoutElement.newBuilder()
+                                                        .setBox(
+                                                                Box.newBuilder()
+                                                                        .setWidth(childBoxSize)
+                                                                        .setHeight(childBoxSize)
+                                                                        .setModifiers(
+                                                                                testModifiers2))))
+                        .build();
+
+        // Compute the mutation
+        ViewGroupMutation mutation =
+                renderer.computeMutation(getRenderedMetadata(rootLayout),
+                        fingerprintedLayout(root2));
+        assertThat(mutation).isNotNull();
+        assertThat(mutation.isNoOp()).isFalse();
+
+        // Apply the mutation
+        boolean mutationResult = renderer.applyMutation(rootLayout, mutation);
+        assertThat(mutationResult).isTrue();
+
+        // Verify that the parent removed the touch delegation.
+        // Keep an empty touch delegate composite will lead to failure when calling
+        // {@link TouchDelegateComposite#getTouchDelegateInfo}
+        assertThat(parent.getTouchDelegate()).isNull();
+
+        // Dispatch a click event to the parent View within the expanded clickable area;
+        // it should no longer trigger the LoadAction.
+        receivedState.clearLastClickableId();
+        dispatchTouchEvent(parent, 5, 5);
+        expect.that(receivedState.getLastClickableId()).isEmpty();
+        View box = parent.getChildAt(0);
+        dispatchTouchEvent(box, 1, 1);
+        expect.that(receivedState.getLastClickableId()).isEqualTo("foo2");
+    }
+
+    @Test
     public void inflate_clickable_withoutRippleEffect_rippleDrawableNotAdded() throws IOException {
         final String textContentsWithRipple = "clickable with ripple";
         final String textContentsWithoutRipple = "clickable without ripple";
@@ -1993,15 +2105,15 @@
 
         FrameLayout rootLayout =
                 renderer(
-                        fingerprintedLayout(
-                                LayoutElement.newBuilder()
-                                        .setColumn(
-                                                Column.newBuilder()
-                                                        .addContents(textElementWithRipple)
-                                                        .addContents(
-                                                                textElementWithoutRipple)
-                                                        .build())
-                                        .build()))
+                                fingerprintedLayout(
+                                        LayoutElement.newBuilder()
+                                                .setColumn(
+                                                        Column.newBuilder()
+                                                                .addContents(textElementWithRipple)
+                                                                .addContents(
+                                                                        textElementWithoutRipple)
+                                                                .build())
+                                                .build()))
                         .inflate();
 
         // Column
@@ -2025,6 +2137,49 @@
     }
 
     @Test
+    public void inflate_hiddenModifier_inhibitsClicks() {
+        final String textContents = "I am a clickable";
+
+        Action action = Action.newBuilder().setLoadAction(LoadAction.getDefaultInstance()).build();
+
+        LayoutElement root =
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .addContents(
+                                                LayoutElement.newBuilder()
+                                                        .setText(
+                                                                createTextWithVisibility(
+                                                                        textContents,
+                                                                        "back",
+                                                                        action,
+                                                                        true)))
+                                        .addContents(
+                                                LayoutElement.newBuilder()
+                                                        .setText(
+                                                                createTextWithVisibility(
+                                                                        textContents,
+                                                                        "front",
+                                                                        action,
+                                                                        false))))
+                        .build();
+
+        State.Builder receivedState = State.newBuilder();
+        FrameLayout rootLayout =
+                renderer(
+                                newRendererConfigBuilder(
+                                                fingerprintedLayout(root), resourceResolvers())
+                                        .setLoadActionListener(receivedState::mergeFrom))
+                        .inflate();
+
+        // Try to tap the stacked clickables.
+        dispatchTouchEvent(rootLayout, 5f, 5f);
+        shadowOf(Looper.getMainLooper()).idle();
+
+        expect.that(receivedState.getLastClickableId()).isEqualTo("back");
+    }
+
+    @Test
     public void inflate_arc_withLineDrawnWithArcTo() {
         LayoutElement root =
                 LayoutElement.newBuilder()
@@ -2151,8 +2306,8 @@
     @Test
     public void inflate_arc_withText_autoSize_notSet() {
         int lastSize = 12;
-        FontStyle.Builder style = FontStyle.newBuilder()
-                .addAllSize(buildSizesList(new int[]{10, 20, lastSize}));
+        FontStyle.Builder style =
+                FontStyle.newBuilder().addAllSize(buildSizesList(new int[] {10, 20, lastSize}));
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setArc(
@@ -2889,12 +3044,13 @@
                         .setFontStyle(FontStyle.newBuilder().addSize(sp(16)))
                         .setMaxLines(Int32Prop.newBuilder().setValue(6))
                         .setOverflow(
-                                TextOverflowProp.newBuilder().setValue(
-                                        TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
+                                TextOverflowProp.newBuilder()
+                                        .setValue(TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
         Layout layout1 =
                 fingerprintedLayout(
                         LayoutElement.newBuilder()
-                                .setBox(buildFixedSizeBoxWIthText(text1)).build());
+                                .setBox(buildFixedSizeBoxWIthText(text1))
+                                .build());
 
         Text.Builder text2 =
                 Text.newBuilder()
@@ -2904,18 +3060,19 @@
                         .setFontStyle(FontStyle.newBuilder().addSize(sp(4)))
                         .setMaxLines(Int32Prop.newBuilder().setValue(6))
                         .setOverflow(
-                                TextOverflowProp.newBuilder().setValue(
-                                        TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
+                                TextOverflowProp.newBuilder()
+                                        .setValue(TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
         Layout layout2 =
                 fingerprintedLayout(
                         LayoutElement.newBuilder()
-                                .setBox(buildFixedSizeBoxWIthText(text2)).build());
+                                .setBox(buildFixedSizeBoxWIthText(text2))
+                                .build());
 
         // Initial layout.
         Renderer renderer = renderer(layout1);
         ViewGroup inflatedViewParent = renderer.inflate();
-        TextView textView1 = (TextView) ((ViewGroup) inflatedViewParent
-                .getChildAt(0)).getChildAt(0);
+        TextView textView1 =
+                (TextView) ((ViewGroup) inflatedViewParent.getChildAt(0)).getChildAt(0);
 
         // Apply the mutation.
         ViewGroupMutation mutation =
@@ -2926,8 +3083,8 @@
         assertThat(mutationResult).isTrue();
 
         // This contains layout after the mutation.
-        TextView textView2 = (TextView) ((ViewGroup) inflatedViewParent
-                .getChildAt(0)).getChildAt(0);
+        TextView textView2 =
+                (TextView) ((ViewGroup) inflatedViewParent.getChildAt(0)).getChildAt(0);
 
         expect.that(textView1.getEllipsize()).isEqualTo(TruncateAt.END);
         expect.that(textView1.getMaxLines()).isEqualTo(2);
@@ -3028,7 +3185,7 @@
     @Test
     public void inflate_textView_autosize_set() {
         String text = "Test text";
-        int[] presetSizes = new int[]{12, 20, 10};
+        int[] presetSizes = new int[] {12, 20, 10};
         List<DimensionProto.SpProp> sizes = buildSizesList(presetSizes);
 
         LayoutElement textElement =
@@ -3036,16 +3193,16 @@
                         .setText(
                                 Text.newBuilder()
                                         .setText(string(text))
-                                        .setFontStyle(
-                                                FontStyle.newBuilder()
-                                                        .addAllSize(sizes)))
+                                        .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
                         .build();
         LayoutElement root =
-                LayoutElement.newBuilder().setBox(
-                        Box.newBuilder()
-                                .setWidth(expand())
-                                .setHeight(expand())
-                                .addContents(textElement)).build();
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(expand())
+                                        .setHeight(expand())
+                                        .addContents(textElement))
+                        .build();
 
         FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
         ViewGroup firstChild = (ViewGroup) rootLayout.getChildAt(0);
@@ -3076,16 +3233,16 @@
                                 Text.newBuilder()
                                         .setText(string(text))
                                         .setMaxLines(Int32Prop.newBuilder().setValue(4))
-                                        .setFontStyle(
-                                                FontStyle.newBuilder()
-                                                        .addAllSize(sizes)))
+                                        .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
                         .build();
         LayoutElement root =
-                LayoutElement.newBuilder().setBox(
-                        Box.newBuilder()
-                                .setWidth(expand())
-                                .setHeight(expand())
-                                .addContents(textElement)).build();
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(expand())
+                                        .setHeight(expand())
+                                        .addContents(textElement))
+                        .build();
 
         FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
         ViewGroup firstChild = (ViewGroup) rootLayout.getChildAt(0);
@@ -3099,23 +3256,23 @@
     public void inflate_textView_autosize_notSet() {
         String text = "Test text";
         int size = 24;
-        List<DimensionProto.SpProp> sizes = buildSizesList(new int[]{size});
+        List<DimensionProto.SpProp> sizes = buildSizesList(new int[] {size});
 
         LayoutElement textElement =
                 LayoutElement.newBuilder()
                         .setText(
                                 Text.newBuilder()
                                         .setText(string(text))
-                                        .setFontStyle(
-                                                FontStyle.newBuilder()
-                                                        .addAllSize(sizes)))
+                                        .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
                         .build();
         LayoutElement root =
-                LayoutElement.newBuilder().setBox(
-                        Box.newBuilder()
-                                .setWidth(expand())
-                                .setHeight(expand())
-                                .addContents(textElement)).build();
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(expand())
+                                        .setHeight(expand())
+                                        .addContents(textElement))
+                        .build();
 
         FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
         ViewGroup firstChild = (ViewGroup) rootLayout.getChildAt(0);
@@ -3129,23 +3286,23 @@
     public void inflate_textView_autosize_setDynamic_noop() {
         String text = "Test text";
         int lastSize = 24;
-        List<DimensionProto.SpProp> sizes = buildSizesList(new int[]{10, 30, lastSize});
+        List<DimensionProto.SpProp> sizes = buildSizesList(new int[] {10, 30, lastSize});
 
         LayoutElement textElement =
                 LayoutElement.newBuilder()
                         .setText(
                                 Text.newBuilder()
                                         .setText(dynamicString(text))
-                                        .setFontStyle(
-                                                FontStyle.newBuilder()
-                                                        .addAllSize(sizes)))
+                                        .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
                         .build();
         LayoutElement root =
-                LayoutElement.newBuilder().setBox(
-                        Box.newBuilder()
-                                .setWidth(expand())
-                                .setHeight(expand())
-                                .addContents(textElement)).build();
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(expand())
+                                        .setHeight(expand())
+                                        .addContents(textElement))
+                        .build();
 
         FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
         ArrayList<View> textChildren = new ArrayList<>();
@@ -3159,23 +3316,23 @@
     @Test
     public void inflate_textView_autosize_wrongSizes_noop() {
         String text = "Test text";
-        List<DimensionProto.SpProp> sizes = buildSizesList(new int[]{0, -2, 0});
+        List<DimensionProto.SpProp> sizes = buildSizesList(new int[] {0, -2, 0});
 
         LayoutElement textElement =
                 LayoutElement.newBuilder()
                         .setText(
                                 Text.newBuilder()
                                         .setText(string(text))
-                                        .setFontStyle(
-                                                FontStyle.newBuilder()
-                                                        .addAllSize(sizes)))
+                                        .setFontStyle(FontStyle.newBuilder().addAllSize(sizes)))
                         .build();
         LayoutElement root =
-                LayoutElement.newBuilder().setBox(
-                        Box.newBuilder()
-                                .setWidth(expand())
-                                .setHeight(expand())
-                                .addContents(textElement)).build();
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .setWidth(expand())
+                                        .setHeight(expand())
+                                        .addContents(textElement))
+                        .build();
 
         FrameLayout rootLayout = renderer(fingerprintedLayout(root)).inflate();
         ArrayList<View> textChildren = new ArrayList<>();
@@ -3219,8 +3376,8 @@
     public void inflate_spantext_ignoresMultipleSizes() {
         String text = "Test text";
         int firstSize = 12;
-        FontStyle.Builder style = FontStyle.newBuilder()
-                .addAllSize(buildSizesList(new int[]{firstSize, 10, 20}));
+        FontStyle.Builder style =
+                FontStyle.newBuilder().addAllSize(buildSizesList(new int[] {firstSize, 10, 20}));
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setSpannable(
@@ -4780,7 +4937,7 @@
 
         boolean applyMutation(ViewGroup parent, ViewGroupMutation mutation) {
             try {
-                ListenableFuture<Void> applyMutationFuture =
+                ListenableFuture<RenderingArtifact> applyMutationFuture =
                         mRenderer.applyMutation(parent, mutation);
                 shadowOf(Looper.getMainLooper()).idle();
                 applyMutationFuture.get();
@@ -5000,33 +5157,25 @@
 
         LayoutElement image = buildImage(protoResId, 30, 30);
 
-
-        BoolProp.Builder stateBoolPropBuilder = BoolProp
-                .newBuilder()
-                .setValue(
-                        true)
-                .setDynamicValue(
-                        DynamicBool
-                                .newBuilder()
-                                .setStateSource(
-                                        StateBoolSource
-                                                .newBuilder()
-                                                .setSourceKey(
-                                                        boolKey)));
-        LayoutElement.Builder boxBuilder = LayoutElement.newBuilder()
-                .setBox(
-                        Box.newBuilder()
-                                .addContents(image)
-                                .setModifiers(
-                                        Modifiers
-                                                .newBuilder()
-                                                .setHidden(stateBoolPropBuilder)));
+        BoolProp.Builder stateBoolPropBuilder =
+                BoolProp.newBuilder()
+                        .setValue(true)
+                        .setDynamicValue(
+                                DynamicBool.newBuilder()
+                                        .setStateSource(
+                                                StateBoolSource.newBuilder()
+                                                        .setSourceKey(boolKey)));
+        LayoutElement.Builder boxBuilder =
+                LayoutElement.newBuilder()
+                        .setBox(
+                                Box.newBuilder()
+                                        .addContents(image)
+                                        .setModifiers(
+                                                Modifiers.newBuilder()
+                                                        .setHidden(stateBoolPropBuilder)));
         LayoutElement root =
                 LayoutElement.newBuilder()
-                        .setRow(
-                                Row.newBuilder()
-                                        .addContents(boxBuilder)
-                                        .addContents(image))
+                        .setRow(Row.newBuilder().addContents(boxBuilder).addContents(image))
                         .build();
 
         FrameLayout layout = renderer(fingerprintedLayout(root)).inflate();
@@ -5059,7 +5208,8 @@
         assertThat(secondImage.getLeft()).isEqualTo(secondImageLeft);
     }
 
-    @Test   public void inflate_box_withVisibleModifier() {
+    @Test
+    public void inflate_box_withVisibleModifier() {
         final String protoResId = "android";
         final String boolKey = "bool-key";
 
@@ -5226,20 +5376,18 @@
                 ContainerDimension.newBuilder().setLinearDimension(dp(100.f).build()).build();
         ContainerDimension innerBoxSize =
                 ContainerDimension.newBuilder().setLinearDimension(dp(60.f).build()).build();
-        Box.Builder boxBuilder = Box.newBuilder()
-                .setWidth(expand())
-                .setHeight(wrap())
-                .setModifiers(
-                        Modifiers.newBuilder()
-                                .setTransformation(
-                                        transformation)
-                                .build())
-                .addContents(
-                        LayoutElement.newBuilder()
-                                .setBox(
-                                        Box.newBuilder()
-                                                .setWidth(innerBoxSize)
-                                                .setHeight(innerBoxSize)));
+        Box.Builder boxBuilder =
+                Box.newBuilder()
+                        .setWidth(expand())
+                        .setHeight(wrap())
+                        .setModifiers(
+                                Modifiers.newBuilder().setTransformation(transformation).build())
+                        .addContents(
+                                LayoutElement.newBuilder()
+                                        .setBox(
+                                                Box.newBuilder()
+                                                        .setWidth(innerBoxSize)
+                                                        .setHeight(innerBoxSize)));
         LayoutElement root =
                 LayoutElement.newBuilder()
                         .setBox(
@@ -5657,7 +5805,7 @@
                 renderer.computeMutation(
                         getRenderedMetadata(inflatedViewParent),
                         fingerprintedLayout(textFadeIn("World")));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
 
         // Idle for running code for starting animations.
@@ -5689,7 +5837,7 @@
                         getRenderedMetadata(inflatedViewParent),
                         fingerprintedLayout(
                                 getTextElementWithExitAnimation("World", /* iterations= */ 0)));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
 
         assertThat(mDataPipeline.getRunningAnimationsCount()).isEqualTo(0);
@@ -5715,7 +5863,7 @@
                         getRenderedMetadata(inflatedViewParent),
                         fingerprintedLayout(
                                 getTextElementWithExitAnimation("World", /* iterations= */ 1)));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
 
         assertThat(mDataPipeline.getRunningAnimationsCount()).isEqualTo(0);
@@ -5738,7 +5886,7 @@
                         getRenderedMetadata(inflatedViewParent),
                         fingerprintedLayout(
                                 getTextElementWithExitAnimation("World", /* iterations= */ 1)));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
 
         assertThat(mDataPipeline.getRunningAnimationsCount()).isEqualTo(0);
@@ -5764,7 +5912,7 @@
                         getRenderedMetadata(inflatedViewParent),
                         fingerprintedLayout(
                                 getTextElementWithExitAnimation("World", /* iterations= */ 10)));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
 
         shadowOf(getMainLooper()).idleFor(Duration.ofMillis(100));
@@ -5792,7 +5940,7 @@
                         getRenderedMetadata(inflatedViewParent),
                         fingerprintedLayout(
                                 getTextElementWithExitAnimation("World", /* iterations= */ 10)));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
         shadowOf(getMainLooper()).idle();
 
@@ -5821,7 +5969,7 @@
                         fingerprintedLayout(
                                 getMultipleTextElementWithExitAnimation(
                                         Arrays.asList("Hello"), /* iterations= */ 10)));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
 
         shadowOf(getMainLooper()).idleFor(Duration.ofMillis(100));
@@ -5850,7 +5998,7 @@
                         getRenderedMetadata(inflatedViewParent),
                         fingerprintedLayout(
                                 getTextElementWithExitAnimation("World", /* iterations= */ 10)));
-        ListenableFuture<Void> applyMutationFuture =
+        ListenableFuture<RenderingArtifact> applyMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, mutation);
 
         shadowOf(getMainLooper()).idleFor(Duration.ofMillis(100));
@@ -5863,7 +6011,7 @@
                                 getTextElementWithExitAnimation(
                                         "Second mutation", /* iterations= */ 10)));
 
-        ListenableFuture<Void> applySecondMutationFuture =
+        ListenableFuture<RenderingArtifact> applySecondMutationFuture =
                 renderer.mRenderer.applyMutation(inflatedViewParent, secondMutation);
 
         // the previous mutation should be finished
@@ -6222,9 +6370,12 @@
 
     private static Spacer.Builder buildExpandedSpacer(int widthWeight, int heightWeight) {
         return Spacer.newBuilder()
-                .setWidth(SpacerDimension.newBuilder().setExpandedDimension(expandWithWeight(widthWeight)))
+                .setWidth(
+                        SpacerDimension.newBuilder()
+                                .setExpandedDimension(expandWithWeight(widthWeight)))
                 .setHeight(
-                        SpacerDimension.newBuilder().setExpandedDimension(expandWithWeight(heightWeight)));
+                        SpacerDimension.newBuilder()
+                                .setExpandedDimension(expandWithWeight(heightWeight)));
     }
 
     private static ExpandedDimensionProp expandWithWeight(int weight) {
@@ -6255,4 +6406,15 @@
                                         .addContents(LayoutElement.newBuilder().setSpacer(spacer)))
                         .build());
     }
+
+    private static Text createTextWithVisibility(
+            String text, String id, Action action, boolean visibility) {
+        return Text.newBuilder()
+                .setText(string(text))
+                .setModifiers(
+                        Modifiers.newBuilder()
+                                .setVisible(BoolProp.newBuilder().setValue(visibility))
+                                .setClickable(Clickable.newBuilder().setId(id).setOnClick(action)))
+                .build();
+    }
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java b/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java
index 3aaad88..cb31832 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/CookieManagerCompat.java
@@ -39,6 +39,7 @@
      * <a href="https://httpwg.org/specs/rfc6265.html#sane-set-cookie-syntax">the RFC6265 spec.</a>
      *  eg. "name=value; domain=.example.com; path=/"
      *
+     * @param cookieManager The CookieManager instance to get info from.
      * @param url the URL for which the API retrieves all available cookies.
      * @return the cookies as a list of strings.
      */
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 46d466f..5c15f3a 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -119,6 +119,7 @@
      * {@link WebViewFeature#isFeatureSupported(String)}
      * returns true for {@link WebViewFeature#SAFE_BROWSING_ENABLE}.
      *
+     * @param settings The WebSettings object to update.
      * @param enabled Whether Safe Browsing is enabled.
      */
     @RequiresFeature(name = WebViewFeature.SAFE_BROWSING_ENABLE,
@@ -179,6 +180,7 @@
      * {@link WebViewFeature#isFeatureSupported(String)}
      * returns true for {@link WebViewFeature#DISABLED_ACTION_MODE_MENU_ITEMS}.
      *
+     * @param settings The WebSettings object to update.
      * @param menuItems an integer field flag for the menu items to be disabled.
      */
     @RequiresFeature(name = WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS,
@@ -425,6 +427,7 @@
      * is created.
      *
      * <p>
+     * @param settings The WebSettings object to update.
      * @param allow allow algorithmic darkening or not.
      *
      */
@@ -593,6 +596,7 @@
      * {@link WebViewFeature#isFeatureSupported(String)}
      * returns true for {@link WebViewFeature#ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY}.
      *
+     * @param settings The WebSettings object to update.
      * @param enabled Whether EnterpriseAuthenticationAppLinkPolicy should be enabled.
      */
     @RequiresFeature(name = WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY,
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index d3f6250..de2c728 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -160,6 +160,7 @@
      * {@link WebViewFeature#isFeatureSupported(String)}
      * returns true for {@link WebViewFeature#VISUAL_STATE_CALLBACK}.
      *
+     * @param webview The WebView to post to.
      * @param requestId An id that will be returned in the callback to allow callers to match
      *                  requests with callbacks.
      * @param callback  The callback to be invoked.
@@ -497,6 +498,7 @@
      * }
      * </pre
      *
+     * @param webview The WebView to post to.
      * @param message the WebMessage
      * @param targetOrigin the target origin.
      */
@@ -760,6 +762,7 @@
      * This method should only be called if {@link WebViewFeature#isFeatureSupported(String)}
      * returns true for {@link WebViewFeature#WEB_MESSAGE_LISTENER}.
      *
+     * @param webview The WebView object to remove from.
      * @param jsObjectName The JavaScript object's name that was previously passed to {@link
      *         #addWebMessageListener(WebView, String, Set, WebMessageListener)}.
      *