Merge "Limit supporting text width to text field width." into androidx-main
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
index a58ae85..b1fcd9c 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateCall.kt
@@ -38,47 +38,7 @@
 
 private const val CAPABILITY_NAME: String = "actions.intent.CREATE_CALL"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(CreateCall.Arguments::class.java, CreateCall.Arguments::Builder)
-        .setOutput(CreateCall.Output::class.java)
-        .bindOptionalParameter(
-            "call.callFormat",
-            { properties ->
-                Optional.ofNullable(
-                    properties[CreateCall.PropertyMapStrings.CALL_FORMAT.key]
-                        as Property<Call.CanonicalValue.CallFormat>
-                )
-            },
-            CreateCall.Arguments.Builder::setCallFormat,
-            TypeConverters.CALL_FORMAT_PARAM_VALUE_CONVERTER,
-            TypeConverters.CALL_FORMAT_ENTITY_CONVERTER
-        )
-        .bindRepeatedParameter(
-            "call.participant",
-            { properties ->
-                Optional.ofNullable(
-                    properties[CreateCall.PropertyMapStrings.PARTICIPANT.key]
-                        as Property<Participant>
-                )
-            },
-            CreateCall.Arguments.Builder::setParticipantList,
-            ParticipantValue.PARAM_VALUE_CONVERTER,
-            EntityConverter.of(PARTICIPANT_TYPE_SPEC)
-        )
-        .bindOptionalOutput(
-            "call",
-            { output -> Optional.ofNullable(output.call) },
-            ParamValueConverter.of(CALL_TYPE_SPEC)::toParamValue
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            CreateCall.ExecutionStatus::toParamValue
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.CREATE_CALL */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class CreateCall private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
@@ -216,4 +176,47 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "call.callFormat",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.CALL_FORMAT.key]
+                                as Property<Call.CanonicalValue.CallFormat>
+                        )
+                    },
+                    Arguments.Builder::setCallFormat,
+                    TypeConverters.CALL_FORMAT_PARAM_VALUE_CONVERTER,
+                    TypeConverters.CALL_FORMAT_ENTITY_CONVERTER
+                )
+                .bindRepeatedParameter(
+                    "call.participant",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.PARTICIPANT.key]
+                                as Property<Participant>
+                        )
+                    },
+                    Arguments.Builder::setParticipantList,
+                    ParticipantValue.PARAM_VALUE_CONVERTER,
+                    EntityConverter.of(PARTICIPANT_TYPE_SPEC)
+                )
+                .bindOptionalOutput(
+                    "call",
+                    { output -> Optional.ofNullable(output.call) },
+                    ParamValueConverter.of(CALL_TYPE_SPEC)::toParamValue
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
index 806e5ac..61ceb00 100644
--- a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/CreateMessage.kt
@@ -39,47 +39,7 @@
 
 private const val CAPABILITY_NAME: String = "actions.intent.CREATE_MESSAGE"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(CreateMessage.Arguments::class.java, CreateMessage.Arguments::Builder)
-        .setOutput(CreateMessage.Output::class.java)
-        .bindRepeatedParameter(
-            "message.recipient",
-            { properties ->
-                Optional.ofNullable(
-                    properties[CreateMessage.PropertyMapStrings.RECIPIENT.key]
-                        as Property<Recipient>
-                )
-            },
-            CreateMessage.Arguments.Builder::setRecipientList,
-            RecipientValue.PARAM_VALUE_CONVERTER,
-            EntityConverter.of(RECIPIENT_TYPE_SPEC)
-        )
-        .bindOptionalParameter(
-            "message.text",
-            { properties ->
-                Optional.ofNullable(
-                    properties[CreateMessage.PropertyMapStrings.MESSAGE_TEXT.key]
-                        as Property<StringValue>
-                )
-            },
-            CreateMessage.Arguments.Builder::setMessageText,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
-        )
-        .bindOptionalOutput(
-            "message",
-            { output -> Optional.ofNullable(output.message) },
-            ParamValueConverter.of(MESSAGE_TYPE_SPEC)::toParamValue
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            CreateMessage.ExecutionStatus::toParamValue
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.CREATE_MESSAGE */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class CreateMessage private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
@@ -218,4 +178,47 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindRepeatedParameter(
+                    "message.recipient",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.RECIPIENT.key]
+                                as Property<Recipient>
+                        )
+                    },
+                    Arguments.Builder::setRecipientList,
+                    RecipientValue.PARAM_VALUE_CONVERTER,
+                    EntityConverter.of(RECIPIENT_TYPE_SPEC)
+                )
+                .bindOptionalParameter(
+                    "message.text",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.MESSAGE_TEXT.key]
+                                as Property<StringValue>
+                        )
+                    },
+                    Arguments.Builder::setMessageText,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
+                .bindOptionalOutput(
+                    "message",
+                    { output -> Optional.ofNullable(output.message) },
+                    ParamValueConverter.of(MESSAGE_TYPE_SPEC)::toParamValue
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskHandler.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskHandler.kt
index 8b71429..790a750 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskHandler.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/task/TaskHandler.kt
@@ -50,7 +50,7 @@
             mutableTaskParamMap[paramName] =
                 TaskParamBinding(
                     paramName,
-                    GROUND_IF_NO_IDENTIFIER,
+                    GROUND_NEVER,
                     GenericResolverInternal.fromInventoryListener(listener),
                     converter,
                     null,
@@ -66,7 +66,7 @@
             mutableTaskParamMap[paramName] =
                 TaskParamBinding(
                     paramName,
-                    GROUND_IF_NO_IDENTIFIER,
+                    GROUND_NEVER,
                     GenericResolverInternal.fromInventoryListListener(listener),
                     converter,
                     null,
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
index d530c21..eeae92b 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetExerciseObservation.kt
@@ -26,44 +26,9 @@
 import java.time.LocalTime
 import java.util.Optional
 
-/** GetExerciseObservation.kt in interaction-capabilities-fitness */
-private const val CAPABILITY_NAME = "actions.intent.START_EXERCISE"
+private const val CAPABILITY_NAME = "actions.intent.GET_EXERCISE_OBSERVATION"
 
-// TODO(b/273602015): Update to use Name property from builtintype library.
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(
-            GetExerciseObservation.Arguments::class.java,
-            GetExerciseObservation.Arguments::Builder
-        )
-        .setOutput(GetExerciseObservation.Output::class.java)
-        .bindOptionalParameter(
-            "exerciseObservation.startTime",
-            { properties ->
-                Optional.ofNullable(
-                    properties[GetExerciseObservation.PropertyMapStrings.START_TIME.key]
-                        as Property<LocalTime>
-                )
-            },
-            GetExerciseObservation.Arguments.Builder::setStartTime,
-            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
-        )
-        .bindOptionalParameter(
-            "exerciseObservation.endTime",
-            { properties ->
-                Optional.ofNullable(
-                    properties[GetExerciseObservation.PropertyMapStrings.END_TIME.key]
-                        as Property<LocalTime>
-                )
-            },
-            GetExerciseObservation.Arguments.Builder::setEndTime,
-            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.GET_EXERCISE_OBSERVATION */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class GetExerciseObservation private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
@@ -134,4 +99,41 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        // TODO(b/273602015): Update to use Name property from builtintype library.
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(
+                    Arguments::class.java,
+                    Arguments::Builder
+                )
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "exerciseObservation.startTime",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.START_TIME.key]
+                                as Property<LocalTime>
+                        )
+                    },
+                    Arguments.Builder::setStartTime,
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
+                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                )
+                .bindOptionalParameter(
+                    "exerciseObservation.endTime",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.END_TIME.key]
+                                as Property<LocalTime>
+                        )
+                    },
+                    Arguments.Builder::setEndTime,
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
+                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
index c7482e1..cfacf6e 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/GetHealthObservation.kt
@@ -26,46 +26,12 @@
 import java.time.LocalTime
 import java.util.Optional
 
-/** GetHealthObservation.kt in interaction-capabilities-fitness */
-private const val CAPABILITY_NAME = "actions.intent.START_EXERCISE"
+private const val CAPABILITY_NAME = "actions.intent.GET_HEALTH_OBSERVATION"
 
-// TODO(b/273602015): Update to use Name property from builtintype library.
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(
-            GetHealthObservation.Arguments::class.java,
-            GetHealthObservation.Arguments::Builder
-        )
-        .setOutput(GetHealthObservation.Output::class.java)
-        .bindOptionalParameter(
-            "healthObservation.startTime",
-            { properties ->
-                Optional.ofNullable(
-                    properties[GetHealthObservation.PropertyMapStrings.START_TIME.key]
-                        as Property<LocalTime>
-                )
-            },
-            GetHealthObservation.Arguments.Builder::setStartTime,
-            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
-        )
-        .bindOptionalParameter(
-            "healthObservation.endTime",
-            { properties ->
-                Optional.ofNullable(
-                    properties[GetHealthObservation.PropertyMapStrings.END_TIME.key]
-                        as Property<LocalTime>
-                )
-            },
-            GetHealthObservation.Arguments.Builder::setEndTime,
-            TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
-            TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.GET_HEALTH_OBSERVATION */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class GetHealthObservation private constructor() {
+
     internal enum class PropertyMapStrings(val key: String) {
         START_TIME("healthObservation.startTime"),
         END_TIME("healthObservation.endTime"),
@@ -138,4 +104,41 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        // TODO(b/273602015): Update to use Name property from builtintype library.
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(
+                    Arguments::class.java,
+                    Arguments::Builder
+                )
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "healthObservation.startTime",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.START_TIME.key]
+                                as Property<LocalTime>
+                        )
+                    },
+                    Arguments.Builder::setStartTime,
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
+                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                )
+                .bindOptionalParameter(
+                    "healthObservation.endTime",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.END_TIME.key]
+                                as Property<LocalTime>
+                        )
+                    },
+                    Arguments.Builder::setEndTime,
+                    TypeConverters.LOCAL_TIME_PARAM_VALUE_CONVERTER,
+                    TypeConverters.LOCAL_TIME_ENTITY_CONVERTER
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
index f04ceee..f8b68c1 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/PauseExercise.kt
@@ -26,29 +26,9 @@
 import androidx.appactions.interaction.capabilities.core.properties.StringValue
 import java.util.Optional
 
-/** PauseExercise.kt in interaction-capabilities-fitness */
 private const val CAPABILITY_NAME = "actions.intent.PAUSE_EXERCISE"
 
-// TODO(b/273602015): Update to use Name property from builtintype library.
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(PauseExercise.Arguments::class.java, PauseExercise.Arguments::Builder)
-        .setOutput(PauseExercise.Output::class.java)
-        .bindOptionalParameter(
-            "exercise.name",
-            { properties ->
-                Optional.ofNullable(
-                    properties[PauseExercise.PropertyMapStrings.NAME.key]
-                        as Property<StringValue>
-                )
-            },
-            PauseExercise.Arguments.Builder::setName,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.PAUSE_EXERCISE */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class PauseExercise private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
@@ -110,4 +90,26 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        // TODO(b/273602015): Update to use Name property from builtintype library.
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "exercise.name",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.NAME.key]
+                                as Property<StringValue>
+                        )
+                    },
+                    Arguments.Builder::setName,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
index 1881a7c..a154fc2 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/ResumeExercise.kt
@@ -26,29 +26,9 @@
 import androidx.appactions.interaction.capabilities.core.properties.StringValue
 import java.util.Optional
 
-/** ResumeExercise.kt in interaction-capabilities-fitness */
 private const val CAPABILITY_NAME = "actions.intent.RESUME_EXERCISE"
 
-// TODO(b/273602015): Update to use Name property from builtintype library.
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(ResumeExercise.Arguments::class.java, ResumeExercise.Arguments::Builder)
-        .setOutput(ResumeExercise.Output::class.java)
-        .bindOptionalParameter(
-            "exercise.name",
-            { properties ->
-                Optional.ofNullable(
-                    properties[ResumeExercise.PropertyMapStrings.NAME.key]
-                        as Property<StringValue>
-                )
-            },
-            ResumeExercise.Arguments.Builder::setName,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.RESUME_EXERCISE */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class ResumeExercise private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
@@ -110,4 +90,26 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        // TODO(b/273602015): Update to use Name property from builtintype library.
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "exercise.name",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.NAME.key]
+                                as Property<StringValue>
+                        )
+                    },
+                    Arguments.Builder::setName,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
index 0847d33..adb2749 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StartExercise.kt
@@ -27,41 +27,9 @@
 import java.time.Duration
 import java.util.Optional
 
-/** StartExercise.kt in interaction-capabilities-fitness */
 private const val CAPABILITY_NAME = "actions.intent.START_EXERCISE"
 
-// TODO(b/273602015): Update to use Name property from builtintype library.
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(StartExercise.Arguments::class.java, StartExercise.Arguments::Builder)
-        .setOutput(StartExercise.Output::class.java)
-        .bindOptionalParameter(
-            "exercise.duration",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StartExercise.PropertyMapStrings.DURATION.key]
-                        as Property<Duration>
-                )
-            },
-            StartExercise.Arguments.Builder::setDuration,
-            TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
-            TypeConverters.DURATION_ENTITY_CONVERTER
-        )
-        .bindOptionalParameter(
-            "exercise.name",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StartExercise.PropertyMapStrings.NAME.key]
-                        as Property<StringValue>
-                )
-            },
-            StartExercise.Arguments.Builder::setName,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.START_EXERCISE */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class StartExercise private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
@@ -136,4 +104,38 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        // TODO(b/273602015): Update to use Name property from builtintype library.
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(StartExercise.Arguments::class.java, StartExercise.Arguments::Builder)
+                .setOutput(StartExercise.Output::class.java)
+                .bindOptionalParameter(
+                    "exercise.duration",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[StartExercise.PropertyMapStrings.DURATION.key]
+                                as Property<Duration>
+                        )
+                    },
+                    StartExercise.Arguments.Builder::setDuration,
+                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
+                    TypeConverters.DURATION_ENTITY_CONVERTER
+                )
+                .bindOptionalParameter(
+                    "exercise.name",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[StartExercise.PropertyMapStrings.NAME.key]
+                                as Property<StringValue>
+                        )
+                    },
+                    StartExercise.Arguments.Builder::setName,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
index 18efde0..5bbc5eb7 100644
--- a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/capabilities/fitness/fitness/StopExercise.kt
@@ -26,29 +26,9 @@
 import androidx.appactions.interaction.capabilities.core.properties.StringValue
 import java.util.Optional
 
-/** StopExercise.kt in interaction-capabilities-fitness */
-private const val CAPABILITY_NAME = "actions.intent.PAUSE_EXERCISE"
+private const val CAPABILITY_NAME = "actions.intent.STOP_EXERCISE"
 
-// TODO(b/273602015): Update to use Name property from builtintype library.
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(StopExercise.Arguments::class.java, StopExercise.Arguments::Builder)
-        .setOutput(StopExercise.Output::class.java)
-        .bindOptionalParameter(
-            "exercise.name",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StopExercise.PropertyMapStrings.NAME.key]
-                        as Property<StringValue>
-                )
-            },
-            StopExercise.Arguments.Builder::setName,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-            TypeConverters.STRING_VALUE_ENTITY_CONVERTER
-        )
-        .build()
-
+/** A capability corresponding to actions.intent.STOP_EXERCISE */
 @CapabilityFactory(name = CAPABILITY_NAME)
 class StopExercise private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
@@ -111,4 +91,26 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        // TODO(b/273602015): Update to use Name property from builtintype library.
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "exercise.name",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.NAME.key]
+                                as Property<StringValue>
+                        )
+                    },
+                    Arguments.Builder::setName,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
index 528a497..59ed86a 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
@@ -20,6 +20,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -29,34 +30,10 @@
 import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
-/** PauseTimer.kt in interaction-capabilities-productivity */
 private const val CAPABILITY_NAME = "actions.intent.PAUSE_TIMER"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(PauseTimer.Arguments::class.java, PauseTimer.Arguments::Builder)
-        .setOutput(PauseTimer.Output::class.java)
-        .bindRepeatedParameter(
-            "timer",
-            { properties ->
-                Optional.ofNullable(
-                    properties[PauseTimer.PropertyMapStrings.TIMER_LIST.key]
-                        as Property<TimerValue>
-                )
-            },
-            PauseTimer.Arguments.Builder::setTimerList,
-            TimerValue.PARAM_VALUE_CONVERTER,
-            TimerValue.ENTITY_CONVERTER
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            PauseTimer.ExecutionStatus::toParamValue,
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.PAUSE_TIMER */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class PauseTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
         TIMER_LIST("timer.timerList"),
@@ -178,4 +155,30 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindRepeatedParameter(
+                    "timer",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.TIMER_LIST.key]
+                                as Property<TimerValue>
+                        )
+                    },
+                    Arguments.Builder::setTimerList,
+                    TimerValue.PARAM_VALUE_CONVERTER,
+                    TimerValue.ENTITY_CONVERTER
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue,
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
index 94d203b..e174668 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
@@ -20,6 +20,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -29,34 +30,10 @@
 import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
-/** ResetTimer.kt in interaction-capabilities-productivity */
 private const val CAPABILITY_NAME = "actions.intent.RESET_TIMER"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(ResetTimer.Arguments::class.java, ResetTimer.Arguments::Builder)
-        .setOutput(ResetTimer.Output::class.java)
-        .bindRepeatedParameter(
-            "timer",
-            { properties ->
-                Optional.ofNullable(
-                    properties[ResetTimer.PropertyMapStrings.TIMER_LIST.key]
-                        as Property<TimerValue>
-                )
-            },
-            ResetTimer.Arguments.Builder::setTimerList,
-            TimerValue.PARAM_VALUE_CONVERTER,
-            TimerValue.ENTITY_CONVERTER
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            ResetTimer.ExecutionStatus::toParamValue
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.RESET_TIMER */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class ResetTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
         TIMER_LIST("timer.timerList"),
@@ -175,4 +152,30 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindRepeatedParameter(
+                    "timer",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.TIMER_LIST.key]
+                                as Property<TimerValue>
+                        )
+                    },
+                    Arguments.Builder::setTimerList,
+                    TimerValue.PARAM_VALUE_CONVERTER,
+                    TimerValue.ENTITY_CONVERTER
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
index d967e45..a190cc8 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
@@ -20,6 +20,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -29,34 +30,10 @@
 import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
-/** ResumeTimer.kt in interaction-capabilities-productivity */
 private const val CAPABILITY_NAME = "actions.intent.RESUME_TIMER"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(ResumeTimer.Arguments::class.java, ResumeTimer.Arguments::Builder)
-        .setOutput(ResumeTimer.Output::class.java)
-        .bindRepeatedParameter(
-            "timer",
-            { properties ->
-                Optional.ofNullable(
-                    properties[ResumeTimer.PropertyMapStrings.TIMER_LIST.key]
-                        as Property<TimerValue>
-                )
-            },
-            ResumeTimer.Arguments.Builder::setTimerList,
-            TimerValue.PARAM_VALUE_CONVERTER,
-            TimerValue.ENTITY_CONVERTER
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            ResumeTimer.ExecutionStatus::toParamValue
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.RESUME_TIMER */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class ResumeTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
         TIMER_LIST("timer.timerList"),
@@ -175,4 +152,30 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindRepeatedParameter(
+                    "timer",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.TIMER_LIST.key]
+                                as Property<TimerValue>
+                        )
+                    },
+                    Arguments.Builder::setTimerList,
+                    TimerValue.PARAM_VALUE_CONVERTER,
+                    TimerValue.ENTITY_CONVERTER
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
index fcd211c..7d71c22 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
@@ -20,6 +20,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.HostProperties
 import androidx.appactions.interaction.capabilities.core.ValueListener
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
@@ -35,78 +36,10 @@
 import java.time.Duration
 import java.util.Optional
 
-/** StartTimer.kt in interaction-capabilities-productivity */
 private const val CAPABILITY_NAME = "actions.intent.START_TIMER"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(StartTimer.Arguments::class.java, StartTimer.Arguments::Builder)
-        .setOutput(StartTimer.Output::class.java)
-        .bindOptionalParameter(
-            "timer.identifier",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StartTimer.PropertyMapStrings.IDENTIFIER.key]
-                        as Property<StringValue>
-                )
-            },
-            StartTimer.Arguments.Builder::setIdentifier,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-            TypeConverters.STRING_VALUE_ENTITY_CONVERTER,
-        )
-        .bindOptionalParameter(
-            "timer.name",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StartTimer.PropertyMapStrings.NAME.key]
-                        as Property<StringValue>
-                )
-            },
-            StartTimer.Arguments.Builder::setName,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-            TypeConverters.STRING_VALUE_ENTITY_CONVERTER,
-        )
-        .bindOptionalParameter(
-            "timer.duration",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StartTimer.PropertyMapStrings.DURATION.key]
-                        as Property<Duration>
-                )
-            },
-            StartTimer.Arguments.Builder::setDuration,
-            TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
-            TypeConverters.DURATION_ENTITY_CONVERTER,
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            StartTimer.ExecutionStatus::toParamValue,
-        )
-        .build()
-
-private val SESSION_BRIDGE = SessionBridge<StartTimer.ExecutionSession, StartTimer.Confirmation> {
-        session ->
-    val taskHandlerBuilder = TaskHandler.Builder<StartTimer.Confirmation>()
-    session.nameListener?.let {
-        taskHandlerBuilder.registerValueTaskParam(
-            "timer.name",
-            it,
-            TypeConverters.STRING_PARAM_VALUE_CONVERTER,
-        )
-    }
-    session.durationListener?.let {
-        taskHandlerBuilder.registerValueTaskParam(
-            "timer.duration",
-            it,
-            TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
-        )
-    }
-    taskHandlerBuilder.build()
-}
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.START_TIMER */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class StartTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
         TIMER_LIST("timer.timerList"),
@@ -265,4 +198,74 @@
     }
 
     class Confirmation internal constructor()
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "timer.identifier",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.IDENTIFIER.key]
+                                as Property<StringValue>
+                        )
+                    },
+                    Arguments.Builder::setIdentifier,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER,
+                )
+                .bindOptionalParameter(
+                    "timer.name",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.NAME.key]
+                                as Property<StringValue>
+                        )
+                    },
+                    Arguments.Builder::setName,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                    TypeConverters.STRING_VALUE_ENTITY_CONVERTER,
+                )
+                .bindOptionalParameter(
+                    "timer.duration",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.DURATION.key]
+                                as Property<Duration>
+                        )
+                    },
+                    Arguments.Builder::setDuration,
+                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
+                    TypeConverters.DURATION_ENTITY_CONVERTER,
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue,
+                )
+                .build()
+
+        private val SESSION_BRIDGE = SessionBridge<ExecutionSession, Confirmation> {
+                session ->
+            val taskHandlerBuilder = TaskHandler.Builder<Confirmation>()
+            session.nameListener?.let {
+                taskHandlerBuilder.registerValueTaskParam(
+                    "timer.name",
+                    it,
+                    TypeConverters.STRING_PARAM_VALUE_CONVERTER,
+                )
+            }
+            session.durationListener?.let {
+                taskHandlerBuilder.registerValueTaskParam(
+                    "timer.duration",
+                    it,
+                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
+                )
+            }
+            taskHandlerBuilder.build()
+        }
     }
+}
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
index 93f5b75..394935b 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
@@ -20,6 +20,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -29,34 +30,10 @@
 import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
-/** StopTimer.kt in interaction-capabilities-productivity */
 private const val CAPABILITY_NAME = "actions.intent.STOP_TIMER"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(StopTimer.Arguments::class.java, StopTimer.Arguments::Builder)
-        .setOutput(StopTimer.Output::class.java)
-        .bindRepeatedParameter(
-            "timer",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StopTimer.PropertyMapStrings.TIMER_LIST.key]
-                        as Property<TimerValue>
-                )
-            },
-            StopTimer.Arguments.Builder::setTimerList,
-            TimerValue.PARAM_VALUE_CONVERTER,
-            TimerValue.ENTITY_CONVERTER
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            StopTimer.ExecutionStatus::toParamValue
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.STOP_TIMER */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class StopTimer private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
         TIMER_LIST("timer.timerList"),
@@ -175,4 +152,30 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindRepeatedParameter(
+                    "timer",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.TIMER_LIST.key]
+                                as Property<TimerValue>
+                        )
+                    },
+                    Arguments.Builder::setTimerList,
+                    TimerValue.PARAM_VALUE_CONVERTER,
+                    TimerValue.ENTITY_CONVERTER
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
index 11e0682..9152d36 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartEmergencySharing.kt
@@ -21,6 +21,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -33,24 +34,10 @@
 import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
-/** StartEmergencySharing.kt in interaction-capabilities-safety */
 private const val CAPABILITY_NAME = "actions.intent.START_EMERGENCY_SHARING"
 
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(
-            StartEmergencySharing.Arguments::class.java,
-            StartEmergencySharing.Arguments::Builder
-        )
-        .setOutput(StartEmergencySharing.Output::class.java)
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            StartEmergencySharing.ExecutionStatus::toParamValue,
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.START_EMERGENCY_SHARING */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class StartEmergencySharing private constructor() {
     // TODO(b/267805819): Update to include the SessionFactory once Session API is ready.
     class CapabilityBuilder :
@@ -167,4 +154,20 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(
+                    Arguments::class.java,
+                    Arguments::Builder
+                )
+                .setOutput(Output::class.java)
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue,
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
index 9bb6f01..133dfde 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StartSafetyCheck.kt
@@ -23,6 +23,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
@@ -39,51 +40,10 @@
 import java.time.ZonedDateTime
 import java.util.Optional
 
-/** StartSafetyCheck.kt in interaction-capabilities-safety */
 private const val CAPABILITY_NAME = "actions.intent.START_SAFETY_CHECK"
 
-@Suppress("UNCHECKED_CAST")
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(StartSafetyCheck.Arguments::class.java, StartSafetyCheck.Arguments::Builder)
-        .setOutput(StartSafetyCheck.Output::class.java)
-        .bindOptionalParameter(
-            "safetyCheck.duration",
-            { properties ->
-                Optional.ofNullable(
-                    properties[StartSafetyCheck.PropertyMapStrings.DURATION.key]
-                        as Property<Duration>
-                )
-            },
-            StartSafetyCheck.Arguments.Builder::setDuration,
-            TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
-            TypeConverters.DURATION_ENTITY_CONVERTER
-        )
-        .bindOptionalParameter(
-            "safetyCheck.checkInTime",
-            { property ->
-                Optional.ofNullable(
-                    property[StartSafetyCheck.PropertyMapStrings.CHECK_IN_TIME.key]
-                        as Property<ZonedDateTime>
-                )
-            },
-            StartSafetyCheck.Arguments.Builder::setCheckInTime,
-            TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER,
-            TypeConverters.ZONED_DATETIME_ENTITY_CONVERTER
-        )
-        .bindOptionalOutput(
-            "safetyCheck",
-            { output -> Optional.ofNullable(output.safetyCheck) },
-            ParamValueConverter.of(SAFETY_CHECK_TYPE_SPEC)::toParamValue
-        )
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            StartSafetyCheck.ExecutionStatus::toParamValue
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.START_SAFETY_CHECK */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class StartSafetyCheck private constructor() {
     internal enum class PropertyMapStrings(val key: String) {
         DURATION("safetycheck.duration"),
@@ -265,4 +225,47 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        @Suppress("UNCHECKED_CAST")
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindOptionalParameter(
+                    "safetyCheck.duration",
+                    { properties ->
+                        Optional.ofNullable(
+                            properties[PropertyMapStrings.DURATION.key]
+                                as Property<Duration>
+                        )
+                    },
+                    Arguments.Builder::setDuration,
+                    TypeConverters.DURATION_PARAM_VALUE_CONVERTER,
+                    TypeConverters.DURATION_ENTITY_CONVERTER
+                )
+                .bindOptionalParameter(
+                    "safetyCheck.checkInTime",
+                    { property ->
+                        Optional.ofNullable(
+                            property[PropertyMapStrings.CHECK_IN_TIME.key]
+                                as Property<ZonedDateTime>
+                        )
+                    },
+                    Arguments.Builder::setCheckInTime,
+                    TypeConverters.ZONED_DATETIME_PARAM_VALUE_CONVERTER,
+                    TypeConverters.ZONED_DATETIME_ENTITY_CONVERTER
+                )
+                .bindOptionalOutput(
+                    "safetyCheck",
+                    { output -> Optional.ofNullable(output.safetyCheck) },
+                    ParamValueConverter.of(SAFETY_CHECK_TYPE_SPEC)::toParamValue
+                )
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
index 731aff5..88fe5f2 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopEmergencySharing.kt
@@ -22,6 +22,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -33,24 +34,10 @@
 import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
-/** StopEmergencySharing.kt in interaction-capabilities-safety */
 private const val CAPABILITY_NAME = "actions.intent.STOP_EMERGENCY_SHARING"
 
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(
-            StopEmergencySharing.Arguments::class.java,
-            StopEmergencySharing.Arguments::Builder
-        )
-        .setOutput(StopEmergencySharing.Output::class.java)
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            StopEmergencySharing.ExecutionStatus::toParamValue,
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.STOP_EMERGENCY_SHARING */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class StopEmergencySharing private constructor() {
     // TODO(b/267805819): Update to include the SessionFactory once Session API is ready.
     class CapabilityBuilder :
@@ -168,4 +155,20 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(
+                    Arguments::class.java,
+                    Arguments::Builder
+                )
+                .setOutput(Output::class.java)
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue,
+                )
+                .build()
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
index 3d3ffed..c1d9aef 100644
--- a/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
+++ b/appactions/interaction/interaction-capabilities-safety/src/main/java/androidx/appactions/interaction/capabilities/safety/StopSafetyCheck.kt
@@ -22,6 +22,7 @@
 import androidx.appactions.builtintypes.experimental.types.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.BaseExecutionSession
 import androidx.appactions.interaction.capabilities.core.Capability
+import androidx.appactions.interaction.capabilities.core.CapabilityFactory
 import androidx.appactions.interaction.capabilities.core.impl.BuilderOf
 import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder
@@ -33,21 +34,10 @@
 import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
-/** StopSafetyCheck.kt in interaction-capabilities-safety */
 private const val CAPABILITY_NAME = "actions.intent.STOP_SAFETY_CHECK"
 
-private val ACTION_SPEC =
-    ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
-        .setArguments(StopSafetyCheck.Arguments::class.java, StopSafetyCheck.Arguments::Builder)
-        .setOutput(StopSafetyCheck.Output::class.java)
-        .bindOptionalOutput(
-            "executionStatus",
-            { output -> Optional.ofNullable(output.executionStatus) },
-            StopSafetyCheck.ExecutionStatus::toParamValue
-        )
-        .build()
-
-// TODO(b/267806701): Add capability factory annotation once the testing library is fully migrated.
+/** A capability corresponding to actions.intent.STOP_SAFETY_CHECK */
+@CapabilityFactory(name = CAPABILITY_NAME)
 class StopSafetyCheck private constructor() {
     // TODO(b/267805819): Update to include the SessionFactory once Session API is ready.
     class CapabilityBuilder :
@@ -164,4 +154,17 @@
     class Confirmation internal constructor()
 
     sealed interface ExecutionSession : BaseExecutionSession<Arguments, Output>
+
+    companion object {
+        private val ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                .setArguments(Arguments::class.java, Arguments::Builder)
+                .setOutput(Output::class.java)
+                .bindOptionalOutput(
+                    "executionStatus",
+                    { output -> Optional.ofNullable(output.executionStatus) },
+                    ExecutionStatus::toParamValue
+                )
+                .build()
+    }
 }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
index 32f63ba..f60a494 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeImplPlugin.kt
@@ -161,6 +161,7 @@
                     error.add("ModifierNodeInspectableProperties")
                     error.add("ModifierParameter")
                     error.add("MutableCollectionMutableState")
+                    error.add("OpaqueUnitKey")
                     error.add("UnnecessaryComposedModifier")
                     error.add("FrequentlyChangedStateReadInComposition")
                     error.add("ReturnFromAwaitPointerEventScope")
diff --git a/buildSrc/repos.gradle b/buildSrc/repos.gradle
index 9b5077d..4ae93fa 100644
--- a/buildSrc/repos.gradle
+++ b/buildSrc/repos.gradle
@@ -65,6 +65,13 @@
                url("https://maven.pkg.jetbrains.space/public/p/compose/dev")
         }
         handler.mavenLocal()
+        // TODO(b/280646217): Remove after official release to gmaven.
+        handler.maven {
+            url("https://storage.googleapis.com/r8-releases/raw")
+            content {
+                includeModule("com.android.tools", "r8")
+            }
+        }
     }
     // Ordering appears to be important: b/229733266
     def androidPluginRepoOverride = System.getenv("GRADLE_PLUGIN_REPO")
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index 2482074..4777d20 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -15,9 +15,8 @@
  */
 
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,65 +24,15 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
-    if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api("androidx.annotation:annotation:1.1.0")
-
-        implementation("androidx.compose.runtime:runtime:1.2.1")
-        implementation("androidx.compose.ui:ui:1.2.1")
-        implementation("androidx.compose.ui:ui-unit:1.2.1")
-        implementation("androidx.compose.ui:ui-util:1.2.1")
-        implementation(libs.kotlinStdlib)
-        api(libs.kotlinCoroutinesCore)
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-        testImplementation(libs.kotlinCoroutinesCore)
-
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.testCore)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(project(":compose:animation:animation"))
-        androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.2.1")
-        androidTestImplementation(project(":compose:test-utils"))
-
-        lintPublish project(":compose:animation:animation-core-lint")
-
-        samples(project(":compose:animation:animation-core:animation-core-samples"))
-    }
-}
-
-if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-
-            jvmMain {
-                dependencies {
-                    implementation(libs.kotlinStdlib)
-                    api(libs.kotlinCoroutinesCore)
-                }
-            }
-
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(project(":compose:runtime:runtime"))
                 implementation(project(":compose:ui:ui"))
                 implementation(project(":compose:ui:ui-unit"))
@@ -91,45 +40,86 @@
                 implementation(libs.kotlinStdlibCommon)
                 api(libs.kotlinCoroutinesCore)
             }
+        }
 
-            androidMain {
-                dependencies {
-                    api("androidx.annotation:annotation:1.1.0")
-                    implementation(libs.kotlinStdlib)
-                }
+        commonTest {
+            dependencies {
             }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                implementation(libs.kotlinStdlib)
+                api(libs.kotlinCoroutinesCore)
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+                implementation(libs.kotlinStdlib)
+            }
+        }
+
+        if (desktopEnabled) {
             desktopMain {
+                dependsOn(jvmMain)
                 dependencies {
                     implementation(libs.kotlinStdlib)
+                    implementation(project(":compose:runtime:runtime"))
+                    implementation(project(":compose:ui:ui"))
+                    implementation(project(":compose:ui:ui-unit"))
+                    implementation(project(":compose:ui:ui-util"))
                 }
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.testCore)
+                implementation(libs.junit)
+                implementation(project(":compose:animation:animation"))
+                implementation("androidx.compose.ui:ui-test-junit4:1.2.1")
+                implementation(project(":compose:test-utils"))
+            }
+        }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.testRules)
                 implementation(libs.testRunner)
                 implementation(libs.junit)
                 implementation(libs.truth)
                 implementation(libs.kotlinCoroutinesCore)
             }
+        }
 
-            androidAndroidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.testCore)
-                implementation(libs.junit)
-                implementation(project(":compose:animation:animation"))
-                implementation(project(":compose:ui:ui-test-junit4"))
-                implementation(project(":compose:test-utils"))
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
             }
         }
     }
-    dependencies {
-        samples(project(":compose:animation:animation-core:animation-core-samples"))
-    }
+}
+
+dependencies {
+    lintPublish project(":compose:animation:animation-core-lint")
 }
 
 androidx {
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index c6597a7..bb4c4c1 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -15,7 +15,7 @@
  */
 
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
@@ -25,55 +25,15 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api("androidx.annotation:annotation:1.1.0")
-        api(project(":compose:animation:animation"))
-        api("androidx.compose.foundation:foundation-layout:1.2.1")
-        api("androidx.compose.runtime:runtime:1.2.1")
-        api("androidx.compose.ui:ui:1.2.1")
-        api("androidx.compose.ui:ui-geometry:1.2.1")
-
-        implementation("androidx.compose.ui:ui-util:1.2.1")
-        implementation(libs.kotlinStdlibCommon)
-        implementation("androidx.core:core-ktx:1.5.0")
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-
-        androidTestImplementation("androidx.compose.foundation:foundation:1.2.1")
-        androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.2.1")
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-
-        samples(project(":compose:animation:animation-graphics:animation-graphics-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
                 api(project(":compose:animation:animation"))
@@ -84,39 +44,84 @@
 
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
+        androidMain.dependencies {
+            api("androidx.annotation:annotation:1.1.0")
+            implementation("androidx.core:core-ktx:1.5.0")
+        }
 
-            androidMain.dependencies {
-                api("androidx.annotation:annotation:1.1.0")
-                implementation("androidx.core:core-ktx:1.5.0")
+        commonTest {
+            dependencies {
             }
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
             }
+        }
 
-            androidAndroidTest.dependencies {
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.kotlinStdlib)
+                    api(project(":compose:foundation:foundation-layout"))
+                    api(project(":compose:runtime:runtime"))
+                    api(project(":compose:ui:ui"))
+                    api(project(":compose:ui:ui-geometry"))
+                    implementation(project(":compose:ui:ui-util"))
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.testRules)
                 implementation(libs.testRunner)
                 implementation(libs.junit)
                 implementation(libs.truth)
-                implementation(project(":compose:foundation:foundation"))
-                implementation(project(":compose:ui:ui-test-junit4"))
+                implementation("androidx.compose.foundation:foundation:1.2.1")
+                implementation("androidx.compose.ui:ui-test-junit4:1.2.1")
                 implementation(project(":compose:test-utils"))
             }
         }
-    }
-    dependencies {
-        samples(project(":compose:animation:animation-graphics:animation-graphics-samples"))
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                }
+            }
+        }
     }
 }
 
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index 8d6b39f..f2b4f811 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,56 +23,15 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api("androidx.annotation:annotation:1.1.0")
-        api(project(":compose:animation:animation-core"))
-        api("androidx.compose.foundation:foundation-layout:1.2.1")
-        api("androidx.compose.runtime:runtime:1.2.1")
-        api("androidx.compose.ui:ui:1.2.1")
-        api("androidx.compose.ui:ui-geometry:1.2.1")
-
-        implementation("androidx.compose.ui:ui-util:1.2.1")
-        implementation(libs.kotlinStdlibCommon)
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-
-        androidTestImplementation("androidx.compose.foundation:foundation:1.2.1")
-        androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.2.1")
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-
-        lintPublish project(":compose:animation:animation-lint")
-
-        samples(project(":compose:animation:animation:animation-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
                 api(project(":compose:animation:animation-core"))
@@ -85,39 +42,84 @@
 
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
             }
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.kotlinStdlib)
+
+                    api(project(":compose:foundation:foundation-layout"))
+                    api(project(":compose:runtime:runtime"))
+                    api(project(":compose:ui:ui"))
+                    api(project(":compose:ui:ui-geometry"))
+
+                    implementation(project(":compose:ui:ui-util"))
+                }
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
+        jvmTest {
+            dependencies {
             }
+        }
 
-            androidAndroidTest.dependencies {
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.testRules)
                 implementation(libs.testRunner)
                 implementation(libs.junit)
                 implementation(libs.truth)
-                implementation(project(":compose:foundation:foundation"))
-                implementation(project(":compose:ui:ui-test-junit4"))
+                implementation("androidx.compose.foundation:foundation:1.2.1")
+                implementation("androidx.compose.ui:ui-test-junit4:1.2.1")
                 implementation(project(":compose:test-utils"))
             }
         }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
     }
-    dependencies {
-        samples(project(":compose:animation:animation:animation-samples"))
-    }
+}
+
+dependencies {
+    lintPublish project(":compose:animation:animation-lint")
 }
 
 androidx {
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index a754486..872eef6 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,94 +23,66 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-
-        api("androidx.annotation:annotation:1.1.0")
-        api(project(":compose:ui:ui"))
-        api("androidx.compose.ui:ui-unit:1.2.1")
-
-        implementation("androidx.compose.runtime:runtime:1.2.1")
-        implementation("androidx.compose.ui:ui-util:1.2.1")
-        implementation("androidx.core:core:1.7.0")
-        implementation("androidx.compose.animation:animation-core:1.2.1")
-        implementation(libs.kotlinStdlibCommon)
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-
-        androidTestImplementation(project(":compose:foundation:foundation"))
-        androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.2.1")
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation("androidx.activity:activity-compose:1.3.1")
-        // old version of common-java8 conflicts with newer version, because both have
-        // DefaultLifecycleEventObserver.
-        // Outside of androidx this is resolved via constraint added to lifecycle-common,
-        // but it doesn't work in androidx.
-        // See aosp/1804059
-        androidTestImplementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-
-        samples(project(":compose:foundation:foundation-layout:foundation-layout-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
                 api(project(":compose:ui:ui"))
                 implementation(project(":compose:runtime:runtime"))
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.core:core:1.7.0")
                 implementation("androidx.compose.animation:animation-core:1.2.1")
             }
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.kotlinStdlib)
+
+                    implementation(project(":compose:runtime:runtime"))
+                    implementation(project(":compose:ui:ui-util"))
+                }
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.truth)
+        jvmTest {
+            dependencies {
             }
+        }
 
-            androidAndroidTest.dependencies {
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:foundation:foundation"))
-                implementation(project(":compose:ui:ui-test-junit4"))
+                implementation("androidx.compose.ui:ui-test-junit4:1.2.1")
                 implementation(project(":compose:test-utils"))
                 implementation("androidx.activity:activity-compose:1.3.1")
 
@@ -121,9 +92,26 @@
                 implementation(libs.truth)
             }
         }
-    }
-    dependencies {
-        samples(project(":compose:foundation:foundation-layout:foundation-layout-samples"))
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
     }
 }
 
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index e367382..a57dfef 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -15,9 +15,8 @@
  */
 
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -26,80 +25,15 @@
     id("AndroidXPaparazziPlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        api("androidx.annotation:annotation:1.1.0")
-        api("androidx.compose.animation:animation:1.2.1")
-        api(project(":compose:runtime:runtime"))
-        api(project(":compose:ui:ui"))
-
-        implementation(libs.kotlinStdlibCommon)
-        implementation(project(":compose:foundation:foundation-layout"))
-        implementation(project(':emoji2:emoji2'))
-        implementation(project(':core:core'))
-        implementation("androidx.compose.ui:ui-graphics:1.2.1")
-        implementation("androidx.compose.ui:ui-text:1.2.1")
-        implementation("androidx.compose.ui:ui-util:1.2.1")
-
-        testImplementation(project(":compose:test-utils"))
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-        testImplementation(libs.kotlinCoroutinesTest)
-        testImplementation(libs.kotlinTest)
-        testImplementation(libs.mockitoCore4)
-        testImplementation(libs.kotlinReflect)
-        testImplementation(libs.mockitoKotlin4)
-
-        testImplementation(project(":constraintlayout:constraintlayout-compose"))
-
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(project(":internal-testutils-fonts"))
-        androidTestImplementation(project(":test:screenshot:screenshot"))
-        androidTestImplementation(project(":internal-testutils-runtime"))
-        androidTestImplementation(libs.testUiautomator)
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.testMonitor)
-        androidTestImplementation "androidx.activity:activity-compose:1.3.1"
-        androidTestImplementation "androidx.lifecycle:lifecycle-runtime:2.6.1"
-        androidTestImplementation "androidx.savedstate:savedstate:1.2.1"
-        androidTestImplementation(libs.espressoCore)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.kotlinTest)
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.dexmakerMockito)
-        androidTestImplementation(libs.mockitoCore)
-        androidTestImplementation(libs.mockitoKotlin)
-
-        lintChecks(project(":compose:foundation:foundation-lint"))
-        lintPublish(project(":compose:foundation:foundation-lint"))
-
-        samples(project(":compose:foundation:foundation:foundation-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(':compose:animation:animation'))
                 api(project(':compose:runtime:runtime'))
@@ -108,36 +42,57 @@
                 implementation(project(":compose:ui:ui-util"))
                 implementation(project(':compose:foundation:foundation-layout'))
             }
-            androidMain.dependencies {
-                api("androidx.annotation:annotation:1.1.0")
-                implementation(project(':emoji2:emoji2'))
-                implementation(project(":core:core"))
-            }
+        }
+        androidMain.dependencies {
+            api("androidx.annotation:annotation:1.1.0")
+            implementation(project(':emoji2:emoji2'))
+            implementation(project(":core:core"))
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
-            }
-
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.mockitoCore4)
-                implementation(libs.truth)
-                implementation(libs.kotlinReflect)
-                implementation(libs.mockitoKotlin4)
-            }
-
-            commonTest.dependencies {
+        commonTest {
+            dependencies {
                 implementation(libs.kotlinTest)
                 implementation(libs.kotlinCoroutinesTest)
             }
+        }
 
-            androidAndroidTest.dependencies {
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+                implementation(project(':emoji2:emoji2'))
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.kotlinStdlib)
+
+                    implementation(project(":compose:ui:ui-util"))
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+                implementation(project(":compose:ui:ui-test"))
+                implementation(project(":compose:ui:ui-test-junit4"))
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:test-utils"))
                 implementation(project(":internal-testutils-fonts"))
                 implementation(project(":test:screenshot:screenshot"))
@@ -157,20 +112,43 @@
                 implementation(libs.mockitoCore)
                 implementation(libs.mockitoKotlin)
             }
+        }
 
-            desktopTest.dependencies {
-                implementation(project(":compose:ui:ui-test-junit4"))
-                implementation(libs.truth)
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
                 implementation(libs.junit)
-                implementation(libs.skikoCurrentOs)
-                implementation(libs.mockitoCore)
-                implementation(libs.mockitoKotlin)
+                implementation(libs.truth)
+                implementation(libs.kotlinReflect)
+                implementation(project(":constraintlayout:constraintlayout-compose"))
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                    implementation(libs.truth)
+                    implementation(libs.junit)
+                    implementation(libs.skikoCurrentOs)
+                    implementation(libs.mockitoCore)
+                    implementation(libs.mockitoKotlin)
+                }
             }
         }
     }
-    dependencies {
-        samples(project(":compose:foundation:foundation:foundation-samples"))
-    }
+}
+
+dependencies {
+    lintChecks(project(":compose:foundation:foundation-lint"))
+    lintPublish(project(":compose:foundation:foundation-lint"))
 }
 
 // Screenshot tests related setup
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
index d0fc90e..603ac21 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
@@ -94,7 +94,10 @@
     @Test
     fun measureAndPlaceTwoItems() {
         val itemProvider = itemProvider({ 2 }) { index ->
-            Box(Modifier.fillMaxSize().testTag("$index"))
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .testTag("$index"))
         }
         rule.setContent {
             LazyLayout(itemProvider) {
@@ -118,8 +121,14 @@
     @Test
     fun measureAndPlaceMultipleLayoutsInOneItem() {
         val itemProvider = itemProvider({ 1 }) { index ->
-            Box(Modifier.fillMaxSize().testTag("${index}x0"))
-            Box(Modifier.fillMaxSize().testTag("${index}x1"))
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .testTag("${index}x0"))
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .testTag("${index}x1"))
         }
 
         rule.setContent {
@@ -143,7 +152,10 @@
     @Test
     fun updatingitemProvider() {
         var itemProvider by mutableStateOf(itemProvider({ 1 }) { index ->
-            Box(Modifier.fillMaxSize().testTag("$index"))
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .testTag("$index"))
         })
 
         rule.setContent {
@@ -166,7 +178,10 @@
 
         rule.runOnIdle {
             itemProvider = itemProvider({ 2 }) { index ->
-                Box(Modifier.fillMaxSize().testTag("$index"))
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag("$index"))
             }
         }
 
@@ -178,7 +193,10 @@
     fun stateBaseditemProvider() {
         var itemCount by mutableStateOf(1)
         val itemProvider = itemProvider({ itemCount }) { index ->
-            Box(Modifier.fillMaxSize().testTag("$index"))
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .testTag("$index"))
         }
 
         rule.setContent {
@@ -228,7 +246,11 @@
             }
         }
         val itemProvider = itemProvider({ 1 }) { index ->
-            Box(Modifier.fillMaxSize().testTag("$index").then(modifier))
+            Box(
+                Modifier
+                    .fillMaxSize()
+                    .testTag("$index")
+                    .then(modifier))
         }
         var needToCompose by mutableStateOf(false)
         val prefetchState = LazyLayoutPrefetchState()
@@ -335,13 +357,15 @@
     fun nodeIsReusedWithoutExtraRemeasure() {
         var indexToCompose by mutableStateOf<Int?>(0)
         var remeasuresCount = 0
-        val modifier = Modifier.layout { measurable, constraints ->
-            val placeable = measurable.measure(constraints)
-            remeasuresCount++
-            layout(placeable.width, placeable.height) {
-                placeable.place(0, 0)
+        val modifier = Modifier
+            .layout { measurable, constraints ->
+                val placeable = measurable.measure(constraints)
+                remeasuresCount++
+                layout(placeable.width, placeable.height) {
+                    placeable.place(0, 0)
+                }
             }
-        }.fillMaxSize()
+            .fillMaxSize()
         val itemProvider = itemProvider({ 2 }) {
             Box(modifier)
         }
@@ -376,6 +400,52 @@
     }
 
     @Test
+    fun nodeIsReusedWhenRemovedFirst() {
+        var itemCount by mutableStateOf(1)
+        var remeasuresCount = 0
+        val modifier = Modifier
+            .layout { measurable, constraints ->
+                val placeable = measurable.measure(constraints)
+                remeasuresCount++
+                layout(placeable.width, placeable.height) {
+                    placeable.place(0, 0)
+                }
+            }
+            .fillMaxSize()
+        val itemProvider = itemProvider({ itemCount }) {
+            Box(modifier)
+        }
+
+        rule.setContent {
+            LazyLayout(itemProvider) { constraints ->
+                val node = if (itemCount == 1) {
+                    measure(0, constraints).first()
+                } else {
+                    null
+                }
+                layout(10, 10) {
+                    node?.place(0, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(remeasuresCount).isEqualTo(1)
+            // node will be kept for reuse
+            itemCount = 0
+        }
+
+        rule.runOnIdle {
+            // node should be now reused
+            itemCount = 1
+        }
+
+        rule.runOnIdle {
+            assertThat(remeasuresCount).isEqualTo(1)
+        }
+    }
+
+    @Test
     fun regularCompositionIsUsedInPrefetchTimeCalculation() {
         val itemProvider = itemProvider({ 1 }) {
             Box(Modifier.fillMaxSize())
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/ContextMenu.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/ContextMenu.android.kt
index e5d86d8..077a75f 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/ContextMenu.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/ContextMenu.android.kt
@@ -20,8 +20,10 @@
 import androidx.compose.foundation.text.selection.TextFieldSelectionManager
 import androidx.compose.runtime.Composable
 
+// TODO (b/269341173) remove inline once these composables are non-trivial
+
 @Composable
-internal actual fun ContextMenuArea(
+internal actual inline fun ContextMenuArea(
     manager: TextFieldSelectionManager,
     content: @Composable () -> Unit
 ) {
@@ -29,7 +31,7 @@
 }
 
 @Composable
-internal actual fun ContextMenuArea(
+internal actual inline fun ContextMenuArea(
     manager: SelectionManager,
     content: @Composable () -> Unit
 ) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
index 4023a81..9b61189 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.ReusableContentHost
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -95,13 +96,11 @@
             val index = itemProvider.findIndexByKey(key, lastKnownIndex).also {
                 lastKnownIndex = it
             }
-
-            if (index < itemProvider.itemCount) {
-                val key = itemProvider.getKey(index)
-                if (key == this.key) {
-                    StableSaveProvider(StableValue(saveableStateHolder), StableValue(key)) {
-                        itemProvider.Item(index)
-                    }
+            val indexIsUpToDate =
+                index < itemProvider.itemCount && itemProvider.getKey(index) == key
+            ReusableContentHost(active = indexIsUpToDate) {
+                StableSaveProvider(StableValue(saveableStateHolder), StableValue(key)) {
+                    itemProvider.Item(index)
                 }
             }
             DisposableEffect(key) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
index 54b93c4..5068c23 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextAnnotatedStringNode.kt
@@ -384,7 +384,7 @@
                         decoration = textDecoration
                     )
                 } else {
-                    val overrideColorVal = overrideColor?.invoke() ?: Color.Unspecified
+                    val overrideColorVal = overrideColor?.produce() ?: Color.Unspecified
                     val color = if (overrideColorVal.isSpecified) {
                         overrideColorVal
                     } else if (style.color.isSpecified) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
index fccf555..e046446 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/modifiers/TextStringSimpleNode.kt
@@ -300,7 +300,7 @@
                         textDecoration = textDecoration
                     )
                 } else {
-                    val overrideColorVal = overrideColor?.invoke() ?: Color.Unspecified
+                    val overrideColorVal = overrideColor?.produce() ?: Color.Unspecified
                     val color = if (overrideColorVal.isSpecified) {
                         overrideColorVal
                     } else if (style.color.isSpecified) {
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt
index b285b1c..fff404c 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/window/WindowDraggableArea.desktop.kt
@@ -36,6 +36,7 @@
  * WindowDraggableArea is a component that allows you to drag the window using the mouse.
  *
  * @param modifier The modifier to be applied to the layout.
+ * @param content The content lambda.
  */
 @Composable
 fun WindowScope.WindowDraggableArea(
diff --git a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
index dd46067..5c61695 100644
--- a/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
+++ b/compose/lint/common-test/src/main/java/androidx/compose/lint/test/Stubs.kt
@@ -219,6 +219,69 @@
         """
     )
 
+    val Composables: TestFile = bytecodeStub(
+        filename = "Composables.kt",
+        filepath = "androidx/compose/runtime",
+        checksum = 0x92d0959f,
+        source = """
+        package androidx.compose.runtime
+
+        @Composable
+        inline fun <T> key(
+            @Suppress("UNUSED_PARAMETER")
+            vararg keys: Any?,
+            block: @Composable () -> T
+        ) = block()
+
+        @Composable
+        inline fun ReusableContent(
+            key: Any?,
+            content: @Composable () -> Unit
+        ) {
+            content()
+        }
+
+        @Composable
+        inline fun ReusableContentHost(
+            active: Boolean,
+            crossinline content: @Composable () -> Unit
+        ) {
+            if (active) { content() }
+        }
+        """,
+        """
+        META-INF/main.kotlin_module:
+        H4sIAAAAAAAA/2NgYGBmYGBgBGIOBijgMuWSSMxLKcrPTKnQS87PLcgvTtUr
+        Ks0rycxNFeJ1BgskJuWkFnuXCHEFpeam5ialFnmXcPFxsZSkFpcIsYUASe8S
+        JQYtBgDjkUhNXwAAAA==
+        """,
+        """
+        androidx/compose/runtime/ComposablesKt.class:
+        H4sIAAAAAAAA/51VzXLbVBT+rvwnK04sy05xXEhT103tGCrHpfzUaaBNKfHg
+        BiY2gWlW17IaFNtSR5I9ZZdhwzN0yxPArrBgMmHHg/AUDMORLCdO7GkK49H5
+        P+d+55wr+c9/fvsdwPv4jGGVmx3bMjovVM3qP7ccXbUHpmv0dXXL13m7pztf
+        uDEwBvmQD7na4+aB+mX7UNfIGmIIdfXvGT4v7jcuumuNruX2DFM9HPbVZwNT
+        cw3LdNTHgVSplaZTGFobrXvT9s3/UX+j3GrVNmslogw3G2/QKcXdaFj2gXqo
+        u22bG1SNm6bl8lHlHcvdGfR6FBWmph0REsPyBAbDdHXb5D21bro2JRuaE0OC
+        YVH7Tte6QfZX3OZ9nQIZbhVn9HRmaXpFDmqlvQQWkJQwD5kh0u5ZWleEcv7o
+        Ge3HkGGIGubQ6uoMmeKMaSdwBW/NYRFZBrFgFJ4V/GWyOkN6xrwZVi5bKUNy
+        Vx/4w9yyaBymy3B3VpuXXY09hkf/PW9j7P/aNFxv91Sm8LqN0kKCxce0EV4R
+        eZqWP4ypTpRZM0lfCNu2HAotFJ++SY93Lg2b1VJ2FrzRuVFOaUNaOHvKkBrn
+        PtFd3uEuJ7hCfxiit595RPQIKLbrCQI5XxieVCGps87w1/HRmnR8JAmyMGJE
+        PJ4VRo9IjzwX8JTHcyWKyQkVVhVlIRfOskqompLDuXklrJC1Ejn5KSqI0e2T
+        H8U/XjEKLckxPzwqi8Tj1bQYfl34Q6oqbEv5sHh8JEvVK/JcLqGIIlP8oyqJ
+        /PjI+VGNbWmqhqfKCyc/CDEpIp68rFaY122VeYNQxgObvNGsxbAw8Tm83aU5
+        h7esDk052TBMfWfQb+t2y3N6JSyN9/a4bXh6YIw3jQOTuwOb5Ku7o+9O3Rwa
+        jkHuB2cXku7NRe/p9+Jc2HzT5Vr3CX8eHCA1rYGt6Y8NT1kKauxN1cc6BIS9
+        pSOEJUQQJW2DtCbZvc0vrilzr5AqK2miofvlX7HE8LN3OXCfaJRmtIA4Nkle
+        oYQFxJDDVfJSKiS87ZdehIJ3KPITPy+GT4NMkfgDeuYFUuL+zfPoEpZxLcCx
+        G+CQy8qNCQTf/HIKQSIuQkYSqVMYIv0KAQyZurrpw5CRn4CxMhvG9QkYq7gV
+        wGgHMDJjGLmXkCaghPBwVOxvpNkErCzSVOcMVgKlAFYGayj7sDLnYBWnYMWF
+        U0gCtnxawyPi35L1XeruvX2E6rhdh1pHBet1VHGnTn/nd/fBHHyAD/eRdLDs
+        4CMHEZ/mHXzsQHSw6mDNt9xzIPmC4iD6Lx9Lm/oRCAAA
+        """
+    )
+
     val Modifier: TestFile = bytecodeStub(
         filename = "Modifier.kt",
         filepath = "androidx/compose/ui",
@@ -382,7 +445,7 @@
     val Remember: TestFile = bytecodeStub(
         filename = "Remember.kt",
         filepath = "androidx/compose/runtime",
-        checksum = 0xc78323f1,
+        checksum = 0x736631c7,
         source = """
         package androidx.compose.runtime
 
@@ -392,72 +455,71 @@
         inline fun <T> remember(calculation: () -> T): T = calculation()
 
         @Composable
-        inline fun <T, V1> remember(
-            v1: V1,
-            calculation: () -> T
+        inline fun <T> remember(
+            key1: Any?,
+            crossinline calculation: () -> T
         ): T = calculation()
 
         @Composable
-        inline fun <T, V1, V2> remember(
-            v1: V1,
-            v2: V2,
-            calculation: () -> T
+        inline fun <T> remember(
+            key1: Any?,
+            key2: Any?,
+            crossinline calculation: () -> T
         ): T = calculation()
 
         @Composable
-        inline fun <T, V1, V2, V3> remember(
-            v1: V1,
-            v2: V2,
-            v3: V3,
-            calculation: () -> T
+        inline fun <T> remember(
+            key1: Any?,
+            key2: Any?,
+            key3: Any?,
+            crossinline calculation: () -> T
         ): T = calculation()
 
         @Composable
         inline fun <V> remember(
             vararg inputs: Any?,
-            calculation: () -> V
+            crossinline calculation: () -> V
         ): V = calculation()
         """,
-"""
-        META-INF/main.kotlin_module:
-        H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM3ApcYlkZiXUpSfmVKhl5yfW5BfnKpX
-        VJpXkpmbKsQVlJqbmpuUWuRdwqXJJYyhrjRTSMgZwk7xzU/JTMsEK+XjYilJ
-        LS4RYgsBkt4lSgxaDACMRj6sewAAAA==
-        """,
         """
-        androidx/compose/runtime/RememberKt.class:
-        H4sIAAAAAAAAAK1WXVPbRhQ9K38J29jCfJSYJiFgyndkG5q2MZBSWhpPCekE
-        j9opT8JWqMCWMlrZk0emL33pH+hrf0Ef0z50GPrWH9XpXVlgg4UJTTzW3qvr
-        u+ecu3el9T///vkXgFV8xTCtWzXHNmuv1ardeGVzQ3Walms2DPWF0TAaB4bz
-        jRsDY1CO9Jau1nXrUH1+cGRUKRpikB0/i2F1bufYduumpR61GurLplV1Tdvi
-        6rbv5UvzO1cxSgyba5XHvfGNm8DWFiuV0kZpnkaGmZ1rq9jy7vWDukF50zu2
-        c6geGe6Bo5uEpluW7ept5F3b3W3W65SVqOr1arPuxWXEGe51STEt13Asva6W
-        LdchDLPKY0gyjFZ/NKrHPsi3uqM3DFesyuxcb3FdkT0Bclia15JIIR3HIJTL
-        fAGlx5BhiJpWyz42GEbmApY1iRGMJjCMMYbBnJl7mev0iZUZJm9qFcN2kPD/
-        0+AfAhusFQK7XtEKN7Fc6rzUKjBkgmi/77/w71IRf/uKtOK1ZVa04i1LLTIc
-        vV1V76fOX96xTm2lb/EVbeWWC7DC8PXc/nuqrrKmBcq7PT6p1DyVWsl7MF81
-        XS5jlmE4AIth6BzumeHqNd3VRW2NVoheyUwMEXpGj4UjUfy1Kbw8ebUCY+7p
-        yVhckkNxaVwim4xLyhBdpyfx05PsDNms9JRNheXTE4UVk4qUlTPhDIXyoadn
-        P8t/v2GnJ2e/RSUlnF25nEyGKZFiVIlSMNJvaiy7GTSVRkmRLwCiygBZuR9Q
-        PPv8eiAaQ0qiBy6qJMkm+sEOZtfbsKk2bKo4pqSzyYwss0x4nOWH8sqUZ7tA
-        UldBMmc/SbF4RD77tZhnYu3p0WMV8abxW9f9ppS0ghiKYqAdyjRGjUTi/Px8
-        eOwyhLfsGr2s0zumZew2RbgiziSBaNNxo+mOKe794MCeeWjpbtMhf+JF+yQr
-        Wy2Tm/TzZufQYshd/fXi6LmUFt+zm07V2DYF+h1/jtaDhwIkhCE+EdyhK0p3
-        63S3RXGJbHohk3iDodDawh/4gOF3sUmxQWOUKpYRwxPyxygm/HGCYGIS4siS
-        /dzLjmHzIh/4gq4YrRkGyBGMEz7jM0oVOz+92GZcXwxkHPQYJyn1nNGTibu4
-        55XR5mY+94c93AMivcN+32f/zl+H9FKbfWMpkH3EY1+g1HP20BX2B+R11kDy
-        dUz26EiEvAkdJVO+kgNKj5BVlttKHoWXA6QMUGlPvL90EfLbUoR+5UKKciFF
-        QY48yfOEqJAvarpH1GB7K3TLmvFl7fntGV3IzJGsfk1KUS3nTUp1NWkUs5j3
-        4EcvNemjXh2Sr6A9SrQdxbiGL8nWKLpAyhb3ESpjqYxlGvGwDBX5Mm3o4j4Y
-        xwpW9zHMEeH4mCPO8YgjyvEJx12OCY5POR5w3Of4jCPHMcXxmGOWo+R9Z/4D
-        oSNh5zILAAA=
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGIOBijgUuOSSMxLKcrPTKnQS87PLcgvTtUr
+                Ks0rycxNFeIKSs1NzU1KLfIu4eLjYilJLS4RYgsBkt4lSgxaDAC9VMzjUAAA
+                AA==
+                """,
+        """
+                androidx/compose/runtime/RememberKt.class:
+                H4sIAAAAAAAA/61WWXPbVBT+rrzJjhfFWUgcCCFL62yV44YCdeoSAqEe3MDU
+                HsNMnmRbTeVF6kiyKW8ZXvgNvPILeCw8MJnwxo9iOFeWHW9JXdIHXZ17dO53
+                vnO/u+iff//8C8A+vmRYU/SqaWjVV3LFaL40LFU2W7qtNVX5mdpUm2XV/MYO
+                gDFINaWtyA1FP5O/LdfUCnk9DKLpRjHsJ/N1w25oulxrN+XnLb1ia4Zuyceu
+                lcps5ocxMgyHB8WHo/7sm8AOtovFTDazSS3DRv7aKo6cvlJuqBS3ljfMM7mm
+                2mVT0QhN0XXDVjrIJ4Z90mo0KGqqojQqrYbjFxFiWO6joum2aupKQ87ptkkY
+                WsUKIMwwV3mhVuouyHeKqTRVm8/K3eRocX2eAgc5y2yWwogiFkIE0mC+MaUH
+                EGfwa3rbqKsMs8kx0xrGLOamMIN5hsi6tv58/UonlmNYeZNUDMfjiP8fgQvX
+                Cfy28AOSr98kJUngKu6tqz/tMcTHEfvhZmluU7M6ec23mgVeXpqhNhnwu6nt
+                x9vUdutq7zN8nTx9R6UUD0pjS3l7fOJZcniWMs7efNmyLRFJhpkxWAzTXbin
+                qq1UFVshn9Bse+hUZrwReQPaq3VuCPTxlcatFFnVPcZeXJwnQoLoCQkLAr3p
+                kS7OyQiQMR2ijxvUTwhP2KpXvDiXWDosCQkx7o2TK+V5cvmL+PdrdnF++Ztf
+                kLyJvaFgUfIlvAss5U9HRe/1AwOJzLUDRUmcBCKY+GoCCFEKTQI2lXjcAQt3
+                wMLpeSmSCMdFkcWd4ano6ihMeBgmdvmzEAj5xMtf0ynG55s2GCvyM8TVrP+U
+                5BqV6MroXpb36jat0yOjSidzLK/p6kmLu4v8OOIQBt0tJcXUeN91Bgvama7Y
+                LZPspWedayuntzVLo8+HV8canXnDX3v3zEBYqGC0zIp6rHH0RXdMaQQPexDg
+                5esMPizS46feAfWOyC/QO7YVn3qNac/B1h94j+F3vhLxiFo/VS0igCzZ8+Tj
+                9gJBMD4IISTo/diJDuDzXjxwSE+AJgxBMnjGJTfjUwrlyzu23cn4aHtsxoiT
+                cYVCuxkdmljGh04ZndzMzf3+SO6IQJ0PnI3VZbDiMvjenYvYTodBdmcsg1mH
+                wRaFdhl4hhisknU1D4LL5aMRLjOeHpdBRmsuozIN89Fb2u0weuDdHUMpSGVm
+                nX85H9kdSrwOqUdJ6lGSsEGW4FicnMcltz5CbtE7RG6Q4h2XYsGVbW4rvkkU
+                bxIvSiy64kX7xJtDkqYTjtUv3t3x4gV7PAR84bQZWq7AKXm3idnOKTw57OZw
+                LwcZqRyt8XQO97FPARY+xoNTSBZ8Fj6xELLwqQW/hWULn1lYsrBqYcXChoU1
+                Cw8tJB3/nf8A7cabHS4LAAA=
         """
     )
 
     val SnapshotState: TestFile = bytecodeStub(
         filename = "SnapshotState.kt",
         filepath = "androidx/compose/runtime",
-        checksum = 0x9907976f,
+        checksum = 0x3a5656cc,
         source = """
         package androidx.compose.runtime
 
@@ -500,6 +562,29 @@
             }
         }
 
+        @Composable
+        fun <T> produceState(
+            initialValue: T,
+            key1: Any?,
+            key2: Any?,
+            producer: suspend ProduceStateScope<T>.() -> Unit
+        ): State<T> {
+            return object : State<T> {
+                override val value = initialValue
+            }
+        }
+
+        @Composable
+        fun <T> produceState(
+            initialValue: T,
+            vararg keys: Any?,
+            producer: suspend ProduceStateScope<T>.() -> Unit
+        ): State<T> {
+            return object : State<T> {
+                override val value = initialValue
+            }
+        }
+
         interface ProduceStateScope<T> : MutableState<T> {
             suspend fun awaitDispose(onDispose: () -> Unit): Nothing
         }
@@ -516,181 +601,487 @@
         """,
         """
                 META-INF/main.kotlin_module:
-                H4sIAAAAAAAAAGNgYGBmYGBgBGJ2KM3Apc8ln5iXUpSfmVKhl5yfW5BfnKqX
-                mJeZm1iSmZ8HFClKFeJxBPMTk3JSvUu4zLkkMDQUleaVZOamCnEFpeam5ial
-                FnmXCPEH5yUWFGfklwSXJJaANCpg0ViaqVeal1kixOJS4F2ixKDFAAA4Rqdc
-                pAAAAA==
+                H4sIAAAAAAAA/2NgYGBmYGBgBGIOBijgMueSSMxLKcrPTKnQS87PLcgvTtUr
+                Ks0rycxNFeIKSs1NzU1KLfIuEeIPzkssKM7ILwkuSSxJ9S7h4uNiKUktLhFi
+                CwGS3iVKDFoMAJF5eAthAAAA
                 """,
                 """
                 androidx/compose/runtime/DerivedState.class:
-                H4sIAAAAAAAAAIVRTW/TQBB9Yzuxk4bghhbSAKVCQk044FJxQKSqhPgQkVIh
-                NVGElNM2XtJtHLvybqIe81s48CM4IKtHfhRinKIKERUuM/Nm37zdffPj57fv
-                AF5gh/BExGGaqPAiGCXT80TLIJ3FRk1l8Famai7DnhFGuiDC0UH/VfdMzEUQ
-                iXgcfDw5kyPTPlxtdW/UXIod9PvtwzbB/3vQhUPY/vewiyLBG0szENFMEjaa
-                rdUHEArNFt/CTH3N3GyuElsDQrHJzLxY704SE6k4OJJGhMIInremc5utojy4
-                BJpw60LlaI+r8DnhXbaolq26Vc4Wy2R5Be9zPVs8dbxs4dO+V3Nq1gfas47r
-                vt2wXmaLT5dfq5dfipWG4zl+4bHjFX03F9sn7N5s3Z/r4IdRn7DzH6NzH+ZX
-                n/d7sTjXp4lZHjybGEKpp8axMLOUj8u9ZJaO5HsVMdg6vhIZKK1OIvk6jhMe
-                Ukms2XoLBRBcNsDiZXkoMXqYI5QZr6FyjW/B/l3Z2F7mB3jE+Q0zqqxyewi7
-                A7+DdY6o5eFOBxvYHII07uLekLeHusaWRkPjvs5hSWNNo/ILV3pR08ICAAA=
+                H4sIAAAAAAAA/4VRTW/TQBB9Yzuxk4bghhbSAG1BQiQccKk4IFJVQnyISKmQ
+                mihCymkbL2Ebx668m6jH/BYO/AgOyOqRH4UYp6hCRIXLzLzZN29Hb378/PYd
+                wHM8IDwScZgmKjwPRsn0LNEySGexUVMZvJGpmsuwZ4SRLohwdNB/2T0VcxFE
+                Ih4HH05O5ci0D1db3Ws1l2IH/X77sE3w/x504RC2/z3sokjwxtIMRDSThI1m
+                a3UBQqHZ4l+Yqa+Ym81VYmtAKDaZmRfr3UliIhUHR9KIUBjB89Z0brNVlAcv
+                DyDQhPvnKkd7XIXPCG+zRbVs1a1ytlgmyyt4n+rZ4onjZQuf9r2aU7Pe0551
+                XPfthvUiW3y8+Fq9+FKsNBzP8QsPHa/ou7nYPuHx9f79eRPejvqE3f+4nZsx
+                v3TA78XiTH9OzPLh6cQQSj01joWZpfxc7iWzdCTfqYjB1vGlyEBpdRLJV3Gc
+                8JBKYs3+WyiwDy4bYPHFPJQYbecIZcZrqFzhG7B/VzZ2lvk+djm/ZkaVVW4O
+                YXfgd7DeQQ23uMRGB5u4PQRp3EF9yCfElkZD467GPZ3DksaaRuUXZ2oGC8cC
+                AAA=
                 """,
                 """
                 androidx/compose/runtime/DerivedStateImpl.class:
-                H4sIAAAAAAAAAI1R227TQBA9u3Yc16Spm6ZXyq1QmqQUl4oH1EZBXFQ1UgCp
-                iSJEn7aJ1W6T2JXXifqYr+AD+AKQQEg8oKiPfBRi7EQVECTy4Lkcn5kzs/Pj
-                57fvAB5jiyEvvGbgy+aF0/A7575ynaDrhbLjOi/dQPbcZjUUoVvunLeTYAy1
-                Ym23ciZ6wmkL78R5c3zmNsK90jhUmahvsVbbK+0x2H/XJ6EzrE/UIwmDwShK
-                T4Ylhvnc+Cz5OhFyJBUFWi5fT8HENQsJpBgSPdHuugyZ8boU0piZAofNoIen
-                UjFsTrZX9F60lnnihvVh+2wuPy5A6rk8zUVMdcVMFineekq7aPGv2UrLD9vS
-                c165oWiKUBDGOz2NLsgik2RgLYIuZJRtU9R8xPBu0E9bfIlbg37suGmYfGnQ
-                L+jmoG+zHTOjZ/gB2+bPpzOGra3wJ4P+5QeD2/rh6ih9e/k+TZBtcdtc0c2E
-                bazpZtLWI4UdEq0xbEz2HNGFq544V6d+GAMPWyHDVFWeeCLsBrS0/sJvkpup
-                SM993e0cu0FNHLfjs/gN0a6LQEb5CLSqfjdouPsySpYPh4p1qST9feZ5PklI
-                31PYptslQE9KH4+OSX6DHoljGRrFJqLr5ggpkefkrcJXTBc2v2D2U8zLkzWI
-                CaoukF0YspDBHBBHv3e1KMpiftTToSyqTBQ+Y/bjP9ulhoRRu2GTBcIWrwbb
-                HQ1m/Hco42ooA0t/DKWNIg2bsb+PB+T3ibFC2tePoJWxWsYNsrgZmVtl3Mad
-                IzCFNdw9wpTCnMI9hXWFtIrSrMK8wqLCzC+1g+gKTQQAAA==
+                H4sIAAAAAAAA/41S204TURRd58x0OoylDOWOeEORtgiDxAcDTY2XEJtUTWjT
+                GHk6tBM40M6QOacNj/0KP8Av0ERj4oNpePSjjHvahqg1sS97r71mn732ZX78
+                /PYdwCNsMeRE0IhC2bjw6mHrPFS+F7UDLVu+98KPZMdvVLTQfql13kyCMVQL
+                1d3yqegIrymCY+/N0alf13vFUao8Vt1CtbpX3GNw/36fhMmwNlaNJCwGqyAD
+                qYsMc9nRXnI1SsiSVAyMbK6Wgo1rDhJIMSQ6otn2GTKj71JIY2oCHC6DqU+k
+                YtgYb654XzSWfezr2qD8bDY3KkDq2Rz1RZnqKjNZILz5hGYx+p+my2ehbsrA
+                e+Vr0RBaEMdbHYMuyGJjxwYM7Iz4CxlH24QaDxne9bpphy9yp9ftO25bNl/s
+                dfOm3eu6bMfOmBn+km3zZ5MZyzWW+eNe9/KDxV3zYGUYvr18nybKdbhrL5t2
+                wrVWTTvpmrHCDolWGdbH20l85kogztVJqPvE1plmmKjI40DodkSTm8/DBrmp
+                sgz81+3WkR9VxVGzf5uwLpo1Eck4HpJOJWxHdX9fxsHSwUCxJpWkr0+DICQJ
+                GQYK23TABO2H/l9CdFHyWVoSxxIMwjbiE+eIKZLn5J38V0zmN75g+lM/L0/W
+                okzQ6w2y84MsZDATr57Q71UdQrOYG9b04suQT+Q/Y/rjP8ulBgnDcoMi88Qt
+                XDW2O2zM+m9T1lVTFhb/aMoYIgMP+n4dm+T3KWOZtK8fwihhpYQbJdzELYK4
+                XcIdrB6CKdzFvUNMKMworCncV0irOJxVmFNYUJj6BfKpA8lSBAAA
                 """,
                 """
                 androidx/compose/runtime/MutableState.class:
-                H4sIAAAAAAAAAIVRwW7TQBB9s3ZiJw3BDS2kAUqFhEg44FJxQKSqhBCISImQ
-                miiqlNM2McGNs66y66hHfwsHPoIDsnrkoxDjFFWIqHCZmbf75s3umx8/v30H
-                8BJ7hCdSTRZxOLnwx/H8PNaBv0iUCeeB30uMPI2CvpEmcECE3uHgdfdMLqUf
-                STX1P56eBWPTPlo/6t6ouRI7HAzaR22C93ejA5uw++9mB0WCOw3MUEZJQNhq
-                ttYfQCg0WzyFmfqaud1cJ7aGhGKTmXmx2Z3FJgqV3wuMnEgjuV/MlxZbRXlw
-                CDTjo4swR/tcTV4Q3mVptSzqopylqyTcgvupnqXPbDdLPTpwa3ZNfKB9cVz3
-                rIZ4laUnl1+rl1+KlYbt2l7hse0WPScXOyA8vdm6P9fBD6MBYe8/Ruc+LK8+
-                7/WVPNefY7O6eD4zhFI/nCppkgVfl/txshgH78OIwc7xlcgw1CFPfKNUzE1h
-                rDRbL1AAwWEDBC/LRYnRwxyhzHgDlWt8C9bvysLuKj/AI85vmVFlldsjWB14
-                HWxyRC0PdzrYwvYIpHEX90a8PdQ1djQaGvd1DksaGxqVX2k2HnjCAgAA
+                H4sIAAAAAAAA/4VR0WoTURA9c3eT3aQxbmOradRaBTHxwa3FBzGlIKIYSBCa
+                EIQ83SZrvM1mt+TeDX3cb/HBj/BBlj76UeJsKkUM1Zc7c+aeOTOc+fHz23cA
+                L/CQ8FhGk0WsJuf+OJ6fxTrwF0lk1Dzwe4mRJ2HQN9IEDojQOxy86p7KpfRD
+                GU39Dyenwdi0j9ZL3Ws1V2KHg0H7qE3w/m50YBN2/93soEhwp4EZyjAJCFvN
+                1voChEKzxVOYqa+Y2811YmtIKDaZmSeb3VlsQhX5vcDIiTSS+8V8abFVlD9u
+                /oBAM66fqxztczZ5TnibpdWyqItylq6CcAvup3qWPrXdLPXowK3ZNfGe9sVx
+                3bMa4mWWfrz4Wr34Uqw0bNf2Co9st+g5udgB4cn1/v15E96OBoS9/7idm7G8
+                dMDrR/JMf47N6uPZzBBKfTWNpEkW/F3ux8liHLxTIYOd40uRodKKJ76Oopib
+                VBxp9l+gwD44bIDgi7koMdrNEcqMN1C5wjdg/c4sPFjF+9jj+IYZVVa5OYLV
+                gdfBZgc13OIUWx1s4/YIpHEH9RGfEDsaDY27Gvd0DksaGxqVX4ySaL/HAgAA
                 """,
                 """
                 androidx/compose/runtime/MutableStateImpl.class:
-                H4sIAAAAAAAAAI1RXU8TQRQ9M7vdLrWUpXwjfqFIW8RF4oOBpkZNjE2KJrRp
-                jDwN7QYG2l3SmRIe+yv8Af4CTTQmPpiGR3+U8c62IWpN7MPej7Pn3nPv3B8/
-                v30H8BibDHkRNjuRbF74jah9FqnA73RDLduBv9fV4rAVVLXQQbl91kqCMdSK
-                tZ3KiTgXfkuER/6bw5OgoXdLo1BlrL7FWm23tMvg/V2fhM2wNlaPJBwGpyhD
-                qUsMc7nRWfJ1IuRIygRWLl9Pw8W1FBJIMyTORasbMGRH69LIYGoCHB6DrY+l
-                YtgYby/zXrSWexTo+qD9bC4/KkDquTzNRUx1xUwWKd58SrtY8a/pymmkWzL0
-                9wItmkILwnj73KILMmOSDOyUoAtpsi2Kmo8Y3vV7mRRf5Kl+L3bcdVy+2O8V
-                bLff89i2m7Wz/BXb4s8ns45nLfMn/d7lB4d79v7KMH17+T5DkJfinrtsuwnP
-                WbXdpGcbhW0SrTGsj/cc5sLVUJyp40jHwMNTzTBRlUeh0N0OLW2/iJrkpioy
-                DF5324dBp2aqzVmihmjVRUeafAimqlG30wheSpMs7Q8U61JJ+vssDCOSkFGo
-                sEW3S4CelD5ujkl+nR6JYwkWxS7MdXOElMhz8qnCV0wWNr5g+lPMy5N1iAnM
-                oxDbmIUsZoA4+r1riqJZzA17+pSZykThM6Y//rNdekAYths0mSds4WqwneFg
-                zn+Hcq6GcrD4x1DWMLKwEfv7eED+JTGWSfv6AawyVsq4QRY3jblVxm3cOQBT
-                WMXdA0wozCjcU1hTyCiTzirMKSwoTP0Cc/1T9U0EAAA=
+                H4sIAAAAAAAA/41S204TURRdZ2Y6HcbSDuWOeEORtgiDxAcDTY2aEJsUTWjT
+                GHk6tBM40M6QnlPCY7/CD/ALNNGY+GAaHv0o4z7Thqg1sS97r71mn732ZX78
+                /PYdwBNsMuR52OxEonnpN6L2eSQDv9MNlWgH/n5X8aNWUFVcBeX2eSsJxlAr
+                1nYqp/yC+y0eHvtvjk6DhtotjVKVseoWa7Xd0i6D9/f7JCyG1bFqJGEz2EUR
+                ClVimM2N9pKvU0KOpDQwc/l6Cg5uuEggxZC44K1uwJAdfZdCGpkJGPAYLHUi
+                JMP6eHPpfdFYznGg6oPyM7n8qACp5/LUF2XK68xkkfDGM5rFjD9NVc4i1RKh
+                vx8o3uSKE2e0L0y6INPG0QYM7Iz4S6GjLULNxwzv+r20aywYbr8XO8OxHWOh
+                3ytYTr/nsW0na2WNV2zLeDGZtT1zyXja7119sA3POlgehm+v3qeJ8lzDc5Ys
+                J+HZK5aT9CytsE2iNYa18Xaiz1wN+bk8iVRMbJ4phomqOA656nZocutl1CSX
+                qYgweN1tHwWdmn6tbxM1eKvOO0LHQ9KtRt1OI9gTOlg8GCjWhRT09XkYRiQh
+                olBiiw6YoP3Q/0uILko+R0sysAiTsAN94jwxJfIGebfwFZOF9S+Y+hTnFcja
+                lAnMYT22cRaymNarJ/R7VZfQDGaHNX19GfKJwmdMffxnudQgYVhuUGSOuPnr
+                xnaGjdn/bcq+bsrGwh9NmUNk4lHs17BBfo8ylkj75iHMMpbLuFXGbdwhiLtl
+                3MPKIZjEfTw4xITEtMSqxEOJtNThjMSsxLxE5hfups0nUgQAAA==
                 """,
                 """
                 androidx/compose/runtime/ProduceStateScope.class:
-                H4sIAAAAAAAAAI1T328SQRCeXSh3INUr/gJarVqNSox3Ep+EEI2GFEO1EfSF
-                p+U4cOHYJbd72Efin+KDf4PxwRB8848yzkFpY7G2DzezM/PNN/vju1+/v/8A
-                gKewQ6DARCeQvHNgu3I4ksqzg1BoPvTs/UB2QtdraKa9hitHngGEQLPcfFbv
-                szGzfSZ69tt233N1qbKaqp9KvBdq1vYXxOVms1QpEbBO9hsQJ3DvXBwGJAik
-                2SfG9SuuIhhu80F9ILXPhd0fD+1uKFzNpVB29XDllJZ1VwYy1Fx4yn4pkVyE
-                LAKUHq4eiUD3LNrysv5ecLyWs6aUC5V/D7pbl0HP7nu6HTCOA5gQUrPFsDeh
-                70eHR9jO/2BSR0hEbSx3sedp1mGaYY4OxzHUAImMQYAMMHXAo8jBVecJnnU6
-                2UrRLE1NJ0fOIsuIWlHG7Gank0LcnE4sUjQz8QzdJQ59vW3F8tSJF9PWWn6e
-                dQwnsTv7+vznNzKdzL4kqGXOPtN4ipq5aFqRwKPT9bIiRNw+aRK4fz6JIRoI
-                JKU4EkdmeR/HakAFNgQbqY9Sz5seDzT2NHhPMB0G2LP5bkFdE2OuOHK/OL5q
-                fK6T1X0WsKGnveAvWKohw8D1qtxHxtxhz4cVPpQzhTXctBG9EP4HJiQhBjcx
-                opCCbfQJrF5Afwu/dYpBOoLO7RIYg9tzfwPuoK9idR1JL7YgVoNLNbDQwkZk
-                MjW4DFdaQBRchWstSCq4riCrIKfAVJBXsKlga75I/gEnsHRUOwQAAA==
+                H4sIAAAAAAAA/41T328SQRCeXSgcSPWKv4BWq7ZGJcY7iU9CiEbTFENrI+gL
+                T8tx4MKxS273sI/EP8UH/wbjgyH45h9lnINeG4u1fbid2ZlvvtnZ/e7X7+8/
+                AOAZbBMoMtHxJe8cWo4cjqRyLT8Qmg9d68CXncBxG5ppt+HIkZsEQqBZaT6v
+                99mYWR4TPettu+86ulxdDtXPJN4LNGt7C+JKs1mulgmYp+uTECdw/0IcSUgQ
+                yLBPjOvXXIUwPObD+kBqjwurPx5a3UA4mkuhrJ0jzy5HeUf6MtBcuMp6JZFc
+                BCwElB8tj0Sgex5tJcq/Fxyv5bwulWL1342269LvWX1Xt33GsQETQmq2aLYf
+                eF44PMK2/geTOkQiai06xZ6rWYdphjE6HMdQAyRcjHABAmSA8UMe7mz0Ok9x
+                4OlkI01zND2dHBuTRDtqhhGjm5tOinFjOjFJycjGs3SX2PTNphkrUDteypgr
+                hXnUTtqJ3dnXFz+/kelk9iVBTWP2mcbT1MiH3UoEHp8tmiU14gykSeDBxXSG
+                aBwwJcWxQrLRpZxIAmXYEGykPko9L3oy0FjT4D3BdOBjzfq7BXVNjLniyP3y
+                5L7xzU5nD5jPhq52/b9g6YYMfMfd4R4y5o9qPizxoaYprOChk+Hb4M9gQApi
+                sIk7Cmm4gzaB2Uto7+K3SnGTmT9juEbAGNyb29uwhXYHs6tIerkFsRpcqYFZ
+                gzXIogtXa3ANrreAKLgBN1uQUpBTkFdQUGAoWFewoeDW3En9Ae+dQkpABAAA
                 """,
                 """
                 androidx/compose/runtime/SnapshotStateKt$produceState$1.class:
-                H4sIAAAAAAAAAI1T3U4TURD+znb7w1poqYCAf6gVtkVZICZqCiSGSNJYNaGk
-                MeFq2V3Kge1Zsnu24bJP4QP4BJpoTLwwDZc+lHHOtjEoCF7s/GXmm2/OzP74
-                +e07gCdYZXhqCzcMuHtiOUHnOIg8K4yF5B3Pagr7ODoIZFPa0nsly8dh4MaO
-                l7jllSwYFTcO7a5t+bZoW2/3Dj1H1hr/xlOFazs7tY0aQ/Hvwix0hjuXF2eR
-                YcisccHlBsOkeb57pUUJJvVQRsqstPLI4ZqBNPIM6a7txx5D6XxdHmMojEBD
-                kUGXBzxieH7JJJe+DE03WlYcue23Bh1zbU8OzQmzcr49cTMrxJo4J3K8cRRI
-                nwvrtSdt15Y2xbRON0VLY0pkGdgRhU648pbJclcY1vu9UaPfM7RpzdByepX1
-                ezljut9bzZX0kvas31tm21NFbVaZ707f66cfMoahFdOzei5V1BUI3cPcFQsk
-                Jub/PkwWZYb82ddh2L9gaxdEhvMfdjvWfiwcyQMRWVtDa7VWuYplHvNYoDP7
-                g9HSkWQYafK2sGUcEhl9M3BJFRpceG/izp4X7th7fnIigaO2F3LlD4P5uhBe
-                uOnbUeTRgRReCscPIi7atKWDwGUwmkEcOt4WV9kz2wNCLR5xKn8hREAc1BxY
-                oUNLg4H+IZTU5ZGu0iI1TNMHOll1iotkbZFWEaP6FaPVxS8Y/5TkPSI5BrX8
-                eehYoPx5PCZvapBNqNeBxJo4g26QNZnkKGyLPEY6Xf2M8Y+/YTNJcCGByw8S
-                hnADkBvkLyXQLGkGzBAUiMZDmMOcFJYTXaFRgXXKnKGq2V2k6rhZxy2SuK3E
-                nTruYm4XLMI93N9FJlLmgwhjESYjTEUo/AJAwj8ArQQAAA==
+                H4sIAAAAAAAA/41T3U4TURD+znb7w1poqYCAiqhVtkVZqCZqCiSGSNJYNaGk
+                MeFq6S7lQHuW7DltuOxT+AA+gSYaEy9Mw6UPZZyzbQwKAhc7f5n55pszsz9/
+                ff8B4CmeMDxzhRcG3Dt2GkH7KJC+E3aE4m3fqQn3SO4HqqZc5b9W+aMw8DoN
+                P3LzK0kwKq4euF3Xabmi6bzbPfAbqlz9P54uXN3eLq+XGbL/FiZhMsxdXJxE
+                giGxygVX6wyT9tnuhTol2NRDGzG7UE8jhWsW4kgzxLtuq+Mz5M7WpTGGzAgM
+                ZBlMtc8lw4sLJrnwZWi60bzmyN1WfdAx1fTV0JywC2fbEze7QKyJcyTHq4eB
+                anHhvPGV67nKpZjR7sZoaUyLlBZgYIcUP+baWybLW2FY6/dGrX7PMqYNy0iZ
+                Rdbvpazpfq+Uypk543m/t8y2prLGrDbfn3wwTz4mLMvIxmfNVCxrapASw/wl
+                WyQ69lVfJ4kHDOnTT8Swd87qzokMH+Gg23b2OqKheCCkszm0SuXCZSzTWIBN
+                t/YXo6VDxTBS403hqk5IZMyNwCOVqXLhv+20d/1w291tRXcSNPQKQ679YTBd
+                EcIPN1qulD5dSeaVaLQCyUWTVrUfeAxWLeiEDX+T6+yZrQGhOpecyl8KERAH
+                PQdW6NritEP6kZDT50d6kRZpYJo+2jH0PT4ia5O0jljFbxgtLn7F+Oco7zHJ
+                MejlP4RJo46QXiJvapBNqNf1mZA1cQrdImsyytHYjr4i0vHiF4x/+gObiIIL
+                EVx6kDCEG4DcIN+JoFnUDJjBMkmTKBSGOTEaUesiSqTXKHOGqmZ3EKvgZgW3
+                KriNOTJxp4J53N0Bk7iH+ztISG3mJcYkJiWmJDK/AeLwkAKyBAAA
+                """,
+                """
+                androidx/compose/runtime/SnapshotStateKt$produceState$2.class:
+                H4sIAAAAAAAA/41T0U4TURA9d7ttl7XQUgEBFVGrbouyUEzUFEgMkaSxakKb
+                xoSnZbuWC+1dsnu34bFf4Qf4BZpoTHwwDY9+lHHutjEoCjzs3DOTmTPn3pn9
+                8fPbdwCPscbwxBGtwOetY9v1u0d+6NlBJCTvenZdOEfhvi/r0pHeS1k4CvxW
+                5HqxWyinwai4duD0HLvjiLb9Zu/Ac2Wl9n8+VbjeaFQ2Kwy5vwvT0BkWzi9O
+                I8WQWueCy02Gaets92KTEizqoUDCKjYzMHDFRBIZhmTP6UQeQ/5sXQYTyI5B
+                Q45Bl/s8ZHh2zk3OfRm63XhBaeROpznsaLQ9OYJTVvFse9JmFUk1aY7tZO3Q
+                lx0u7FeedFqOdCimdXsJGhpTxlAGDOyQ4sdceSuEWqsMG4P+uDnom9qsZmqG
+                XmKDvmHODvplI6/ntaeD/grbmclp8wq+PXmvn3xImaaWS87rRiKnK5Iyw+IF
+                UyQ51mVfJ417DJnTT8Rw/I/RXSoyepaDXtd+FwlXcl+E9vYIlSvFi3Rn8AAW
+                bd8fGpcPJcNYnbeFI6OA5OlbfouObI0L73XU3fOChrPXiTfHd9VQA678UTBT
+                FcILtjpOGHq0N9kXwu34IRdtGt6+32Iw634UuN42V9lzO0NBTR5yKn8uhE8a
+                1D2wSvuXpKnSr4W8Wkg6l2i0Gmbpo6lDbehDQtt0qohZ+orx0tIXTH6K8x6R
+                nYBahzJ0rFF+GcvkzQyzifWqWhxCU6fYTULTcY7ittVe0Zksfcbkx9+0qTi4
+                FtNlhgkjuiHJNfLtmJrFzYA5rJDVcR/FUU6CrqjOEskCNihzjqrmd5Go4noV
+                N6q4iQWCuFXFIm7vgoW4g7u7SIUKFkJMhJgOMRMi+wttlNMGxAQAAA==
+                """,
+                """
+                androidx/compose/runtime/SnapshotStateKt$produceState$3.class:
+                H4sIAAAAAAAA/41T3U4TURD+znbbLmuhpQJCVUStui3KQjFRU2hiiCSNVRNK
+                GpNeLdu1HGjPkt3Thss+hQ/gE2iiMfHCNFz6UMY528agIHix85eZb74zM/vj
+                57fvAB5jneGJI1qBz1vHtut3j/zQs4OekLzr2XXhHIX7vqxLR3ovZf4o8Fs9
+                14vc/HoSjIprB07fsTuOaNtv9g48V5Zr/8ZThRu7u+VKmSHzd2ESOsPixcVJ
+                JBgSG1xwWWGYtc52LzQowaIeyohZhUYKBq6YiCPFEO87nZ7HkD1bl8IU0hPQ
+                kGHQ5T4PGZ5d8JILJ0Ovm8wrjtzpNEYdjbYnx+aMVTjbnrhZBWJNnCM5XTv0
+                ZYcL+5UnnZYjHYpp3X6MlsaUMJQAAzuk+DFX3ipZrTWGzeFg0hwOTG1eMzVD
+                L7LhwDDnh4OSkdWz2tPhYJXtzGW0nDLfnrzXTz4kTFPLxHO6EcvoCqTEsHTJ
+                FomO9b/TSeIeQ+r0iBja56yuec4tjadw0O/a73rCldwXob09tkrlwmU0U3gA
+                i47tD0orh5Jhos7bwpG9gNjoW36LVLrGhfe6193zgl1nrxMdiu+qHQZc+eNg
+                qiqEF2x1nDD06EzSL4Tb8UMu2rSrfb/FYNb9XuB621xlL+yMCDV4yKn8uRA+
+                cVDvwBqdW5yWSH8Ssur+SC/TJjXM00dLhjrIh2Rtk1YRs/gVk8XlL5j+FOU9
+                IjkFtf1N6KhQ/iZWyJsbZRPqVXUnZM2cQjfJmo1yFLatzoh0vPgZ0x9/wyai
+                YCWCS40SxnAjkGvk2xE0i5oBC1glqeM+CuOcGD1R6SJKEUWaBlXlmohVcb2K
+                G1XcxCKZuFXFEm43wULcwd0mEqEy8yGmQsyGmAuR/gWU18KgswQAAA==
                 """,
                 """
                 androidx/compose/runtime/SnapshotStateKt.class:
-                H4sIAAAAAAAAAKVYW1PbVhD+jm1sIxwQBgKIBEjiBDAXG5ombXBJU3LB5drg
-                0KY0TYQtQGBLro5MkzemD33sj+gvaJ+SNDMdJn3rT+mP6HSPLBvb2AZSz0jn
-                tvudb/fs2dX473//+BPATRwwjKpGxjL1zMtY2szlTa7FrIJh6zkttm6oeb5r
-                2uu2amuLdgCMQd5TD9RYVjV2Yqtbe1qaZr0M7RnN0g+0jCO5us0wO7pUKzg7
-                ttRwp/sV6rMMjxKpOyf150ZTqbOCJEh0jpCuLZnWTmxPs7csVTd4TDUMk9Z1
-                k/orpr1SyGZJauxMmMlcPhtAK4M/oRu6PcfQU8/KjRDaEJIg4QLD9TMhB9DB
-                0HKgZgsaQ/gkJjk4V7DVraz2oQ5erlD/YAdXgpQc3Nh1ldJF112U0NvcKZU6
-                AfSTMyrtXtK5LWyfGm1CsipohQaRTDYw9zwwJYOjZ1cJYJDBO1qMiGEJQ7jC
-                0Flp0bKaFwZNnpkJKRCH54nFOvZs/C8bCTmRWpxNbZxyrLVKAdyQMCIsC+Ut
-                M1NIFy1j2K4ToXVm9k07qxuxvYNcbLtgpItX86Hbm2kWjqVg/qdxMJ97v8Rk
-                4/3WKsxbT5t59w5MliDTpmUWbN3QeGzeJBWj4CSaRFngCWUNUhivQ/ZUO0vh
-                d72x3LwzFrFFcpFmiY+ynisWdM/MCmKKYbDCObpha5ahZmNJw7YIQU/zAOKU
-                89K7WnrfzZ1rqqXmNBJkGGl+3OsCZMfJjjP4SMI0bjLcPmvpiVSGVmQ6gFsS
-                botUMtjcawF8SnEpsrWuZjeK+dW3r72aZhg+LfLIOTua7Sq9GD0tDhuHmqVt
-                Z2kcW6QAymuW/YoOu06KzzcI49MCY9yJjHNsn4hSuJGSKGT2rs7ngrhXjARn
-                OYh5hu7ROhxDmMODNtzBQ4YLET2yHTl2EEtSkYoIuIrJ4dMvb0CoED6D0thf
-                xI6XQXebeKSqzJ3nROrVcYYfz30kJ0vkOQ9GVGC6IysItUHBVyVHH5vvOvl4
-                YuTMVb+ztPOyZqsZ1VZpzpM78NKXIBOvAB3jvuh4aP6lLnp04T2ZacbeHB2u
-                SEeHkqfPI3mCPqctD71u6zluZVosrZce2VOlTnrKEMkpwbAv7FnwxNlVX/Do
-                UPbM+GWvQhPvf/V7ZJ8SllvKIn5XRBmUA0q7M9nqvKV4sLjUSg2TJUJuK2uF
-                6gFfUJ7J7WWRjmMRWYjMBOVOxdfH4uGZSblLGQuysBQuCfeU+vHe+MWwP+zI
-                xbsFbLBvIfDXG3Z06OzRr9yUFQHnokfL2xH+gIsvyZcUf5i8Eb+88P5nyVEc
-                VBLykEL4tYqh+ooly8oAw+9/8pCbg/3i/GaaBknNtzdLMUyc7/Nq/FwfL2yR
-                HrpcEJ+6bkBWZt0mm5+owLMVebxB+SWRwZLIg5e2RpXMNEr7pV45GHIVzal9
-                m6rEvJmhy9WxRIArhdyWZqXEVRKczbSoJZYuxu5k67q+Y6h2waL+wOMi26Rx
-                oHOdlu8dl16qy7Wr5RJaJRZKGoZmzWdVzjUaSutmwUprD3WxWb8LsXECnqqq
-                Bz6IH309owV+eJGj0S1qyeMIvYP0NPoa7UeQfxMXHQa9/c6aDFNIFOXQiTC1
-                eUcmgB9cqSC1/ehC90ncXoHbdwSlFrevIW5PDe4ALrm4w7QqfsF3GHr6GlcF
-                JqvAVFyEyzUI1xA5iTBCCKO1CJddhOs1CGOIkgcFwhohiRQYngjH3uDjd7gt
-                LPzkCHeqLfTjhmPhcFEas46Fopeghzm9KXxGGsUdx50dJepNCH70WPR0OFkY
-                k867SGUOd11jnrnH2hMNf0FUJsL36e2di77FI4ZqNu2IOWyEFe10cgtIOrx6
-                8CUWHV49WHJ59WAey2Venzs8urzu/tVcVrDqcsmRaAu1vVVcbvmik2/x2IPf
-                y2yEhR0UIRfpS810/mxooXEQ68RIGNOLFJ44jHrLjHpdRqInIsXrcltzuPX4
-                6nADCXFnO/qedMb9sB2lLArU/kLzG7T115vwJvFNEk/pjW+T2MR3SfLs95tg
-                HM/xYhNXOFo4VI4tjk4OP8cAR5rjGkeGQ+PY5ujieMLRzTHGscgxy5Hg2OGY
-                4tjl0Dn2nOE+R5RjjmOJY55jmeMuxwrH6n8UZ3xFehEAAA==
+                H4sIAAAAAAAA/91XbVMb1xV+riSkZS3DshgD6xgTW7ZBGISJa7dGpXVwiFXA
+                L4HQONhNFmmBBWlX3bsiuG+haZt/0Q/tL0g/JalnOoz7rT+l/QudTs9d7UpC
+                b0j2ZKZTzazu3XvPec5zXvbs3X/8569/A3ALf2CY0K2cY5u5w1TWLhRtbqSc
+                kuWaBSO1ZulFvmu7a67uGstuDIxB2dMP9FRet3ZSj7b2jCythhn6coZjHhg5
+                T/LRNsP8xEq94PzkSktL92vU5xneT6/fbdRfmFhf7xQkTaILhHRlxXZ2UnuG
+                u+XopsVTumXZtG/aNH9ouw9L+TxJTXaEmSkU8zH0MkTTpmW6CwxDzbzciOMM
+                4jJknGW42hFyDP0MPQd6vmQwqI2YFOBCydW38sbrBni1Rv21A1wLEgS4dehq
+                pcuhOy9juH1QanViGKVg1Pq9YnJX+D4z0YbkiaIVGkQy08LdbmACh5Odq8Qw
+                xhCeKFfEuIxLeJthoNajVb0oHJrumAkpEIdP0stN/Nl4Ix8JOb2+PL++cUpa
+                65ViuCbjuvAsXnTsXClb9oxhu0mFNlnZt928aaX2Dgqp7ZKVLT+aS/5srl05
+                BsX8z9bF3LW99HRre49r3FvL2kX/GZgOILO2Y5dc0zJ4atEmFavkNZp0ReBD
+                6hqkMNWE7Kl+BuV3tbXconcvaovkEu0aH3U9X0zyc+ZImGEYqwmOabmGY+n5
+                VMZyHUIwszyGWep52V0ju+/3zse6oxcMEmS43j7dawJkx+uOc3hHxk3cYrjT
+                6asnUVtaiZsx3JZxR7SSsfZRi+EHVJeiW5t6fqPcXyP7xoubDOOnVR7DYWcV
+                /F3U9L+7qun/uyp/zcKYi+HHMu6JwhBZnmPYaZLCze8kY/9qnbHuDf7PJ0iE
+                l0tYeu1UvRPDAxkZkarBJvGhzrRjuP4T++nEaeFvHWDH2M7TfWqZwlY0HPcF
+                udjEWrFF9k4Lx5QXjy7Mp5MUZFISp0h31+QLEh6V27C3LeEJw7mJJhzjWMHa
+                GdzFOsPZhJnYTlQDxDJ0QkwIuJrF8dNrNiZUCJ9Bax0vYscroLttInLijNlN
+                Rpodohk+6zoljefTLhMjjr/0gnqG+Blo+CQIdNV9P8jVhesdH7kHAsurhqvn
+                dFentVDhIEyfYUz8SeIPlMt9MQnR5qEpZvTKDeVustCfjo8+ko+P5NBISA5J
+                EW+s3Ib9MVQdFbEZLASXEqoqKAMVHLrXLpGCJqkRNfQgNMsuR6TjIyU0F1XC
+                Gi28+nM0pEQ0VempiER9EW1MiWl93mKv9y/PSuWtXhqYIhPymYpWvBnwWW1H
+                6auI9FdFFCEyJykDWmSEzao0G/Rn08o5bVJiqqwGaueD+ezI7LAaVT252SFh
+                QBp9EPv7N+z4yLOmac87s/YGNi5o++1snFfe0uKqRNhlhIuX39jimHZLuSTA
+                fVvJinHyaNz3SFbe1qIqZXv28oNXX8qe4hUtrSQ0wq9XjDdXDDJXAbj66osQ
+                lZE0Kgp1ru0jUfeZz6iV3ejuS26qq+8ktkwXtRKIr2r/8as94LUx3vCWna85
+                MrZ4xZLIWCDy3qFr0KHZtgJ76y88DOUEzZl9l96li3aOWkn/CgE+LBW2DGdd
+                NA7B2c6KY6tjint/sXfN3LF0t+TQ/MIHZbYZ68DkJm3fq57y6ROgfrdyWj8h
+                Fs9YluEs5nXODbqV1+ySkzWWTGFs1IfYaICnA3wIEdGy0ItR9CCKMH5Fd7dp
+                pIgj/hLy0+TX6DuG8pXoaPg1/Ue9PQW/ERJlOQxApfFzTyaGI19KonEUgzjX
+                iDsscEeOodXjjrTEHarDvYC3fNxx2hU/6SUuPf0al7/yunAVU/MRLtYhXEGi
+                EeE6IUzUI1z0Ea7WIUwiSREUCI8JSfR69Yaa+gbfe4k7wsPvH+PuSQ+juOZ5
+                OF6WxrznoZil6WLebAY/JI2yxSnPokyzG4IfXb+lq99752Da+w+oLPhUPiZo
+                kVZ12qdyT1B5twmVOY9KsizdlMoiXaEKqbBP6kdNSQ1FakidpHbfp7bhR2lk
+                Sn2fqAWxygiCP2kgGCeT1ViNVAiOYAnLfrXUxuq9drGSagitYNVP/HP/ERhK
+                qo89Qh/Qf3gh+S0+ZDjJpo+sBuHqoyrfwE89XkP4CE89XkMU+nLghvAEmxVe
+                Dz0eg+EKi9rgPCMOZS4FEu2hcfgEl9uR5PS3+DSEv1TYCA/7yYXzhCwY3SK1
+                foLTiZFwZhhbyHqMhiuMhn1GYiaeqiCVPwtS18gNJPSF9/iE6UsdHtt7/pjx
+                x995IL/E72n8I+nliIqxiXAG2xnsZLALM4M97GeQR2ETjMOCvYlrHD0cRY6f
+                cwxwRDkucDgcVzg4h8tR4hjkyHKc45jkeMoxz5HmOOBY5Jjh+IzjkOOFt/IL
+                jgWOJMeSd3ufY4XjY44nHJscqxzPOJ7/F4J6TSkwFwAA
                 """,
                 """
                 androidx/compose/runtime/SnapshotStateList.class:
-                H4sIAAAAAAAAAI1QXUsbQRQ9M5tkdY26ftTGj9pXG8Q1UhCsCFYoBLYWmpCX
-                PE2yg45JZmRnInnc3+I/6FOhD7L46I8S70ZftC/OwLn3nDncj3l4/HcH4Cs+
-                M9SFTlKjkknUN6NrY2WUjrVTIxm1tLi2l8a1nHAyVtb5YAw7x+2j+ErciGgo
-                9EX0q3cl++7byf8SQ/hW81FiqBwrrdwJg7fzpVNFBX6AMmYYSu5SWYbd+P0T
-                UZOleGDcUOnop3QiEU6Qxkc3Hu3HCvAZ2ICkiSrYPmVJg7bIs2rAazzIs4CH
-                BHlWy7N6aSbPQnbA9/n38v1thYde4T+gEm1G9RC+mmBv4GjqM5NIhsVYaXk+
-                HvVk2ha9ISnLsemLYUekquAv4mxLXWjhxinlQcuM0778oYqH9d/PO3aUVeQ8
-                1dpQC2W0RQOcPqg4NEfxX4TrxKIpB8r1v5j9QwnHBmFlKm7SBarPBgSYo+hh
-                a+ry8Gkaa9imeEieKnnmu/CaWGhikRBhAUtNLGOlC2axig9dlCzmLNYsPlr4
-                TxVS9+dFAgAA
+                H4sIAAAAAAAA/41Qy0ocQRQ9VT0v24n2+IijJpqlGcRWEQQVIQrCQJuAM8xm
+                VjXThdY8qqSrRlz2t/gHWQVcSOMyHxW8Pbox2WRz7j2nDvfeU7//PD4BOMAX
+                hobQcWJUfB/2zfjWWBkmE+3UWIYtLW7tjXEtJ5yMlHVlMIatk/ZRNBB3IhwJ
+                fR3+6A1k3x2f/isxBH9rZRQYSidKK3fK4G197VRRQtlHERWGgrtRlmE7+v+L
+                aEktGho3Ujq8lE7EwgnS+PjOo3wsh0oOYGBD0u9Vznapi/coSpZWfV7nfpb6
+                PCDI0nqWNgqVLA3YPt/lZ8XnhxIPvNy/TyPaLJ8UvDtjZ+jo9HMTS4b5SGn5
+                fTLuyaQteiNSFiLTF6OOSFTO38SZlrrWwk0S6v2WmSR9eaHyh9Wr16AdZRU5
+                v2ltaIUy2mIPnH7pLUr+aYRrxMIpB4qNX5j5SQ3HOmFpKq7jE2H11QAfs1Q9
+                fJ66PGxM6yo2qR6Sp0qeD114Tcw1Md9EgBq1WGhiEUtdMItlfOyiYDFrsWJR
+                tyi/AEh/yvNKAgAA
                 """,
                 """
                 androidx/compose/runtime/SnapshotStateMap.class:
-                H4sIAAAAAAAAAI1QTU8bMRB99ibZsKSwQKGBftBjoVIXUE80QqKVkCKWVmqq
-                veTkZC0wSexo7SCO+1v4Bz1V6qFa9ciPqjoOvbTlgCW/N/P8bM/M7a/vPwC8
-                xUuGHaHzwqj8OhmaydRYmRQz7dREJj0tpvbCuJ4TTp6JaQjG0OmcHqaX4kok
-                Y6HPk0+DSzl077J7tKP/JYb4Xy1EjaHRUVq5I4bg1U7WQgNhhDqaDDV3oSzD
-                6/TBRdIfK+nIuLHSyZl0IhdOkMYnVwF1zDyEDGxE0rXy2R5F+T5DUpVLEW/z
-                iDdpx1UZVWW7KndrzaqMGRGL+QHfC97Xf940eFzz1w7opVPaGaOnEf9Vy5uR
-                o/o/mFwyLKdKy4+zyUAWX8RgTMpqaoZinIlC+fyPuNBT51q4WUFx1DOzYihP
-                lD/Y/HzXbaasIuex1oa+UEZb7IPTqPyiOvzkCLcoS+Y5UN/9hoWvFHA8JWzM
-                xRd4Rti6MyDCInGA53NXQKeeN7FNfEieFnke9RF0sdTFMiFiDytdrGKtD2bx
-                GOt91C0WLTYsnli0LcLftbcdUGUCAAA=
+                H4sIAAAAAAAA/42Qy04bMRSGf3tyY0hhoFxCb7S7QqUOoK4gQqKVKkUMrdRU
+                s8nKyVhgktjR2EEs51n6Bl0hdYFGLHkoxHFg08uiC//nnM+/j318e/frGsAH
+                vGHYEjrLjcou44EZT4yVcT7VTo1l3NViYs+M6zrh5ImY1MEY2u3j/eRcXIh4
+                JPRp/LV/LgfuIP0HO/wbMUR/sjoqDLW20sodMgRvt9ImaqiHqKLBUHFnyjK8
+                S/77kXTHUjI0bqR0fCKdyIQTxPj4IqCJmZeGFzCwIfFL5asdyrJdhrgsFkLe
+                4iFv0IrKIiyLVllsVxplETEKLOJ7fCf4WL35UeNRxR/bo07HtFLmm0a/Pej9
+                0NEQn0wmGRYTpeWX6bgv8++iPyKynJiBGKUiV75+hHNddaqFm+aUh10zzQfy
+                s/IbG98eRk6VVeQ80trQFcpoi11w+q/Hqfz3kT6nKp7VQHX7CnM/KeF4QVqb
+                wVd4Sdp8MCDEPMWAqHcF2JzFZ3hNcZ88TfI86SHoYKGDxQ4iLFGK5Q6eYqUH
+                ZrGKtR6qFvMW6xYtiw2L+j3Q7/VyagIAAA==
                 """,
                 """
                 androidx/compose/runtime/State.class:
-                H4sIAAAAAAAAAH1QTUvDQBB9k7RpjF/xu1YQj9WDUfEgfoEXoVARbBGhp7Vd
-                69p0I91t8Zjf4sEf4UGCR3+UOFFPKu7hzbw3O7Nv5+39+QXALlYIq0J3Bonq
-                PETtpH+fGBkNhtqqvowaVlhZAhGqh839+p0YiSgWuhudX9/Jtj04/i0Rwp9a
-                CQWC35X2UsRDSZivrv/VV6yuN5scZ+q9xMZKR2fSio6wgjWnP3LZLuVQIlCP
-                pQeVsy3OOtuEwyydCpyyE2Rp4IQ5+K5/U87SDc/P0pDWaMfZci5mQ7fi7GXp
-                1etT4fXR8yoFvxAW8xk7hLX6/5tgI9QktoHi6OsrYUOLe3Ob2M/6Zs8Sxhqq
-                q4UdDrgcNJLhoC1PVcxk+eJr1qUy6jqWJ1on3KQSbTx+H0Xkh3hVHnjlKDNz
-                4MP9zlwsf8YlVDge8Y0x7glacGsYr2GCEZM5TNUwjbAFMpjBbAuewZzBvMGC
-                waLJaekDuDg1Tf4BAAA=
+                H4sIAAAAAAAA/31Qy07jQBCsthPHmJeT5RECQhzDHtaAOKx4SXtBihSERCKE
+                lNOQDGGIM0aZScTR38KBj+CALI77USvaYU+AuHR1VU/3VPfff88vAPaxQdgU
+                ujdKVO8h6ibD+8TIaDTWVg1l1LLCyhKIUD9qHzTvxEREsdD96Pz6Tnbt4cln
+                iRB+1EooEPy+tJciHkvCUn37q75ifbvdZiw3B4mNlY7OpBU9YQVrznDisl3K
+                g58HEGjA+oPK2Q5nvV3CUZYuBE7VCbI0cMI8+K5/U83Sn56fpSFt0Z6z41xU
+                Qrfm/M7Sq9enwuuj59UKfiEs5jP2CFvN78/BbqhNuYHi5H2fsKXFvblN7LT+
+                a2AJMy3V18KOR1wOWsl41JWnKmaydvE+61IZdR3LP1on3KQSbTz+H0VMd+N7
+                eeC7Y42ZAx/u/8xFbYpVrDMe84sZ7gk6cBuYbWCugXkscIrFBkKUOyCDCn50
+                4BksGSwbrBismpyW3gBNs/uhAwIAAA==
+                """
+    )
+
+    val Effects: TestFile = bytecodeStub(
+        filename = "Effects.kt",
+        filepath = "androidx/compose/runtime",
+        checksum = 0xb63b1aec,
+        """
+            package androidx.compose.runtime
+
+            @Composable
+            fun SideEffect(
+                effect: () -> Unit
+            ) {
+                effect()
+            }
+
+            class DisposableEffectScope {
+                inline fun onDispose(
+                    crossinline onDisposeEffect: () -> Unit
+                ): DisposableEffectResult = object : DisposableEffectResult {
+                    override fun dispose() {
+                        onDisposeEffect()
+                    }
+                }
+            }
+
+            interface DisposableEffectResult {
+                fun dispose()
+            }
+
+            private class DisposableEffectImpl(
+                private val effect: DisposableEffectScope.() -> DisposableEffectResult
+            )
+
+            @Composable
+            @Deprecated("Provide at least one key", level = DeprecationLevel.ERROR)
+            fun DisposableEffect(
+                effect: DisposableEffectScope.() -> DisposableEffectResult
+            ): Unit = error("Provide at least one key.")
+
+            @Composable
+            fun DisposableEffect(
+                key1: Any?,
+                effect: DisposableEffectScope.() -> DisposableEffectResult
+            ) {
+                remember(key1) { DisposableEffectImpl(effect) }
+            }
+
+            @Composable
+            fun DisposableEffect(
+                key1: Any?,
+                key2: Any?,
+                effect: DisposableEffectScope.() -> DisposableEffectResult
+            ) {
+                remember(key1, key2) { DisposableEffectImpl(effect) }
+            }
+
+            @Composable
+            fun DisposableEffect(
+                key1: Any?,
+                key2: Any?,
+                key3: Any?,
+                effect: DisposableEffectScope.() -> DisposableEffectResult
+            ) {
+                remember(key1, key2, key3) { DisposableEffectImpl(effect) }
+            }
+
+            @Composable
+            fun DisposableEffect(
+                vararg keys: Any?,
+                effect: DisposableEffectScope.() -> DisposableEffectResult
+            ) {
+                remember(*keys) { DisposableEffectImpl(effect) }
+            }
+
+            internal class LaunchedEffectImpl(
+                private val task: suspend () -> Unit
+            )
+
+            @Deprecated("Provide at least one key", level = DeprecationLevel.ERROR)
+            @Composable
+            fun LaunchedEffect(
+                block: suspend () -> Unit
+            ): Unit = error("Provide at least one key")
+
+            @Composable
+            fun LaunchedEffect(
+                key1: Any?,
+                block: suspend () -> Unit
+            ) {
+                remember(key1) { LaunchedEffectImpl(block) }
+            }
+
+            @Composable
+            fun LaunchedEffect(
+                key1: Any?,
+                key2: Any?,
+                block: suspend () -> Unit
+            ) {
+                remember(key1, key2) { LaunchedEffectImpl(block) }
+            }
+
+            @Composable
+            fun LaunchedEffect(
+                key1: Any?,
+                key2: Any?,
+                key3: Any?,
+                block: suspend () -> Unit
+            ) {
+                remember(key1, key2, key3) { LaunchedEffectImpl(block) }
+            }
+
+            @Composable
+            fun LaunchedEffect(
+                vararg keys: Any?,
+                block: suspend () -> Unit
+            ) {
+                remember(*keys) { LaunchedEffectImpl(block) }
+            }
+        """,
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGIOBijgMuSSSMxLKcrPTKnQS87PLcgvTtUr
+                Ks0rycxNFeJ0TUtLTS4p9i4R4gpKzU3NTUot8i7h4uNiKUktLhFiCwGS3iVK
+                DFoMAHVSFrpbAAAA
+                """,
+                """
+                androidx/compose/runtime/DisposableEffectImpl.class:
+                H4sIAAAAAAAA/51TS08UQRD+enbZx4iyLPJGQEFZQJgFvS0hUYRkkxUNS4gJ
+                p2a2gV5me8h0L8Eb0Yu/w3/gwWg8GMLRH2Ws3ocgxgAmM9VV1f1VfV1V/ePn
+                t+8AnuIJwxxXlSiUlWPPD2uHoRZeVFdG1oT3Qmqy+U4gVnd3hW+KtcMgCcaQ
+                qfIj7gVc7Xmvdqq0k0SMIbEklTTLDBO50kFoAqm86lHN260r38hQaW+tpS0U
+                prcYPlx1ammudG1qZT88FIXZ6wM2hK4HprDcoDJRCqM9ryrMTsQlUeBKhYY3
+                6ayHZr0eBAW6n2ggU0gzjF6gLpURkeKBV1QmIrj0dRK3GHr9feEftPCvecRr
+                gg4yTOVKl+tXuOAp2yB7xKsTt3HHRSe6GGI5a3eg20UcWYbxqyrciTTupuGg
+                lyFu9qVm8K5fHdtpuvH7qzp00wb9R38YutssXgrDK9xw8jm1oxgNMLMiZQUY
+                2AH5j6W18qRVFhii05MR1xlwXCdzeuLS19Av/KcnKWfg9GTRybPno9n+jDPU
+                k41nnXy8ITvysbOPCSeVsDKT3Bj71/6bs3dxq1E8m3mRWT7ZNu/zrjDkb1oy
+                hoUbV43ms5179dgIGslQtUlsvm0EdZsAPX9gGNJluae4qUeCYXijGbuojqSW
+                FPnZ+WOgUVoJK3SoqySVWK/XdkS0abPby4Y+D7Z4JK3dck5ejvX7FfwR1C2H
+                9cgXa9JiBluYrb+yI0/jHG+0Omunm6xZshwM4jGtCfKnmoNADyWBGObIKtG+
+                Q2tmNut+RWbmC3pmZj+j71MDOU/yDp1MUAwXQ+ii1SNfXxODfgzY2SLN5mOt
+                fEliAiRZK6GDhYacwSKtK+QdIgLD24gVMVLEvSJGMUYqxou4jwfbYBoTmNxG
+                SmNA46FGWuORxpRGTmNaI/ELZm5MUZ8FAAA=
+                """,
+                """
+                androidx/compose/runtime/DisposableEffectResult.class:
+                H4sIAAAAAAAA/5VPzU4CMRicrwvsuiou/qIPQPTiAjHx4MlEjWswJphw4VTY
+                YgrL1tBCOPJcHgxnH8r4LTyBSTOd+X4605/fr28ANzgjxDJPZ0any3hopp/G
+                qng2z52eqvhBW9ZykKnH0UgNXVfZeeZ8ECEay4WMM5l/xG+DMfd8eAQ/3Wwo
+                gnd51SPUOhPjMp3Hr8rJVDp5RxDThcfWVEBQAAg04fpSF6rJLG0RGutVNRR1
+                EYpovQr5iEgEo/p61RZNegkicSGa3nOjmG4TWp1/foKDsG+4LdnriWPxbuaz
+                oXrSGec/727Xe9pqXr3Pc+Ok0ya3FbZECZvgJUIZFWYCJxs8xinft/y0z52g
+                Dy/BToIwwS72mGI/QRUHfZBFhFofJYtDiyOLMuMfURqWxJYBAAA=
+                """,
+                """
+                androidx/compose/runtime/DisposableEffectScope$onDispose$1.class:
+                H4sIAAAAAAAA/8VUXVPTQBQ9mxZaQoHwIQIqVkFtA5Km4hcwzDBYxmpRh2p9
+                4CltQ1mabpgk7fDk9CfpjI6jD06f/VGON0mRjjry8eJD9t69e/bs3bP35vuP
+                r98ALGOVYcUQVcfm1SOtYjcObdfUnKbweMPUnnCX5kbZMnN7e2bFK1bsQ3Pe
+                FmHcnNdjYAzKgdEyNMsQNe1l+YBgMUQYtDOz7phu06JNfQz9a1xwb51hLlWo
+                257FhXbQamh7TVHxuC1cbavrZVbTJYbsaai14/U3RLu6HmxSTi4Qns+QPO2w
+                BGQMDkBCgiGSSpcSiGFYRhQjDFFvn7sMa4WLy0iPEKuGE4bZf+cSwwTpxEXL
+                rhN4IpUu/C4/ZTuJy4O4hCl6hnMqxDB6HNk2PaNqeAbFpEYrQuXC/CHuD2Bg
+                dYofcX/2nryqzpDqtEfkTluWpqTAKJLKOu24PNVpZ6UMexZXpBkpE3k66eOz
+                DPrZNQuLhFKhk5fOp3QMKYaBX3IzFE8vrnMnloCKBQY5DLpLdSortfcYLjzT
+                EYalFe2mUzGfmOVmLXfkmcKlE+lefS3DalJy74rbG696aOTnAYesFpPH3pa8
+                kNSTPZCLV56sFmR9Tl/U9eUVmuRkUqrIa8Lwmg4lE920q2RGClyYL5qNsum8
+                9skYxgp2xbBKhsP9eTeYyAthOpuW4bomdcRITlQs2+WiRpW0b1dJnfDuW9xH
+                T/xNCIbpnTD3Enc5sW4IYXtG8D4MV7predH6YxU6tWeUqqMfTFH8fiVfp+KU
+                MEMfdRjiZLMUWScrkZXVhU8YUj9D+RDg7tFIu9GHYfozgrooQGEUY37Jk9fL
+                OkDeOCFZwPnY7wiyg+pHDH3BNMPbE1I5IFICKp84EUK7xP24H2BYgAKm8YDG
+                KNJYxMOA4y4ekf3PlUFXBOVDr0ACXd1FJI9reczmcR1JcnEjj5uY2wVzMY9b
+                u4i6vnvbxbiLO1ihzb5WS/RpASjzE1u+Yp2EBgAA
+                """,
+                """
+                androidx/compose/runtime/DisposableEffectScope.class:
+                H4sIAAAAAAAA/51UW2/TSBT+xrnYNaVJw603CgsBWgq1E9gLpCBBF7RBoSAC
+                lVCfJs60TOuMkcep+ljxsP9hX/cX7D4VLdIqKm/8KMQZJy3d7kMpljzn/p05
+                c87Mp8///AvgNu4wzHPVjiPZ3vKCqPM20sKLuyqRHeH9KjXJvBWKR6urIkia
+                QfRW2GAMxXW+yb2QqzXvWWudTDYyDPkFqWRynyEzM7s8jBzyLrKwGbLJG6kZ
+                /MbxUtUYhiLVNwmG5kxjI0pCqbz1zY632lVBIiOlvccDzq/NfnuCF0J3w4Qy
+                tI5CXdizv6Liave/K8nlRhSveesiacVcEjhXKkp4P9FSlCx1w5C8CvvF9sMd
+                FBimD+xOqkTEiodeXSUx4chA2xhlOBO8EcHGAOg5j3lHkCPDtZnG4UbVDmia
+                BmStZnp1CqddlHCG4e7xelTe33O5YuMclXp0l9LZGHcxhgkG75inaWOKYaQs
+                y6vlA7PB6gwXj0rMMLrn8lQkvM0TTjqrs5mhu8DM4pgFBLdB+i1pJJ+4doVh
+                pbc94VpjlmsVe9uu5VipYNhUZ431tquWzx7mdv/Mk/hkqpiZsPxsdcTJFnMT
+                Tilbsnzbz/+2+7vz8T3rbe++s2w35+z+UfWZSVFlJnHlO4artFfUwUrdvpOe
+                30jo/i1GbTqkQkMqsdTttET80uCY0Cjg4TKPpZEHyqGmXFM86cbET77oZ6+r
+                TaklmR98nVyG8mHr/uz9x224rpSIF0OutSDRbUbdOBCPpUk2PoBY/h88KrDo
+                +TCfRSdDrwmtPkmeaRDR3PUdOH+n5gqt+VR5AlVah/sOGIJLdJS0wwRlgpeR
+                gWnr6blS8T3OZu59wNjruR1M9nD+r30sl6iDEboWpRTvIsU4hDGNC2Sh6AGy
+                4QpkZbiVxp6kJ7W/kxGiP9Jvs4GQwU8pMKOxN984fk5DPPxCdJH0P9CGL60g
+                U8flOsp1XMFVYnGtjhnMroBpXMfcChwNV+OGRl7jhMZNjYLGPGm+AB149ZDV
+                BQAA
+                """,
+                """
+                androidx/compose/runtime/EffectsKt.class:
+                H4sIAAAAAAAA/+1Y21Mb1xn/Vhe0LALL4q6kjmJIA8JYWnExWBjH5hKrljGV
+                bKhL63QRC16QdhXtSgYnbdxmOtOX/APpQ2f63Je8JG4z43qat/5Rnf7O0UpI
+                aAHBOEwfyoz2fHvOd/l9t3P28O///OM1EU3S7wW6quhbRUPb2o9mjXzBMNVo
+                saRbWl6NLm1vq1nLvG/5SBAosKuUlWhO0XeiDzd3seAjt0BSRttSK4wCDY2k
+                9gwrp+nR3XI+ul3Ss5Zm6GZ02aZiidE1geKncc1V1x/rmpWY50IfpI6FucDf
+                lc2cmgCElFHcie6q1mZR0aBU0XXDUioGVgxrpZTLgatN5YhFkgS6UgdG0y21
+                qCu5aFK3ihDXsqaP/AL1Zp+p2T1bflUpKnkVjAJ9OJI6GpVE3UyGKdkBfj91
+                0SWJOinQaM/BeR8FgU/Ty8aeKlDPyGizBT/1UG8HdVOfQOHTIo7ELWqmHaBW
+                EyXzmP/pNK658eOzctRoJmsU1MRY6wJp1SzlqunvriJZVAtFNatY6hY88+VV
+                01R2EKeB1aJRRiWGFSucUxXTChu6Gt5TDwTy5tSymhNo8KgKeJBiS1DkXUqn
+                H6YFeu8w1slcTt1RchkUj7q0n1ULjN9HV6HoOFvXRRoWSLSMSt6PZs+uBj/9
+                lD6UyEMjyPOchhKfR4GNOJdNhMYkGqJrpyZahhdTTtXYQp6/PrvcxWV++KSG
+                RjfafT/esvZkvpDzUZzFNiTRBE0KNDasDSvD40U1r+Y31eJ4beMbPio8LAsk
+                JAXqhMT2cFVAIA/Sj6VgcyAF+vjkTeIMifrb29B0cak7Q1jjlRBieNKaj+eK
+                3+sfR/f/ZEQnKhHFMD2ycb5w/fkcghcXC+adKdJS9cukZGm56J1iUTnAmf0x
+                9lZoP3i4LdCokxvJUYdJPyXpZxLdo/tnCjV2kHZNL5Qsc1gr46xy0CxQV0pB
+                jJ6pW9UzWGvhdLUZskbRgHu6auJjB6HRS3wDPGSwP5TGmg1XYuXdzBnZPZHS
+                Il0R6LNz7fdvC8vYselujFBln16TaJ1t0aPHpqNRim3QX72lbfJtedwydmx/
+                f/nRtqgL9wZbz+fn20EuHCoK7HJV6QPVUrYUS0HPuvJlN65JAnuI7EE4/fcY
+                4cLivsaoGKgtWXD98ObltPTmpeQKuPgw4Gr48RX2EN0gLldGtiZ62Rh6H4sh
+                V0yI9wdcoe6gJ+iKefjTG3P/669tLrHtni80VmUSA76QZ0CIiSeyTzWzi4H2
+                FgRvnyQoBqQWVMxXVfQFOkL+oCgKQS4U8189VZiNgc5QKtDVAoh3A5dCA9Au
+                BUWuR4gFgm22Lfc93w+vhDcvucLLobsnKGxZTTA006SmZeHu0LItfFxYWlbV
+                Gbpuq2pZpCfUF+jlIn4Ev8rch4D/weVBJQ6yUsYeROxD1u6G+vtF7KxHu0Dy
+                mQ93XJCrtpf2LRV3cEOvgnh0wJWGT9kgEg742UVYsvv/+h6O30j9NlS792eM
+                UjGrLqqbpZ2adXY/LCu5kioIZubBndU6NdJ9rkOKZMJValkaC8vhOpbT/70C
+                iXg4bW9SJ4pUmSATSUnykHxNlidvypI8MRS/KctTUjzGiWkpPsOJG9LEJCdm
+                qjyzNk88ZvPEZZsnHj/0g0fgfM4A2GRsSOZwJmc5MS1NVYgb0vQNTsxIM1Oc
+                mJVmJxkBOHKsQsEdOc4pAFqS8KW3YGzhjn8phUyvlJj/j1jZsCwbWSW3phQ1
+                9m5Ptme0HV2xSkXQ76Qr8JJ6WTM1LN85vD3ianl0tfafnQY26fB/Drj82zJr
+                DvqkSvUsawxGj1MpkUwu3PzZXwcNkpfa8PYcb3OYR+NRVyTY8YouR/5O/QKt
+                f8NOGtrHU8LYRiJ1Ujsd4N1f4cb8AMYXnM9Hn2Fsw4qI8XP8fDiuIEAwNUgh
+                LDFTt8jNhbsrpr6noSfBD76l0e9o/J81e0yLRO/X2erG+/WarXfot9yL3+EX
+                xPq7mPuJSVfoPZXCkDsRSdR2Og8kLBi9YxyJ+5Z7/nuaeDL2HU3V+97H4zQC
+                XSYwjMCeST00yrFFoEDCyjTdgDIRUZ0B5WJKaZZucoy9HLlgI481YetkoZf5
+                4V5FmLAR7kOpl0G4VkE47572MIjXHCHKUGkBgoxgWTAb5xAnsdaBlVsA5sHo
+                5xDdXKoKsY/mQQmcYmBdNti5JrDd7hrYRsi3bchfQnUbxv7xCuRpj3vayzCP
+                O2JOQEEJmBIIaQlzcxzzPFb8sHkHSL3gqGBmueqvYe6vYe6nu6BcnGLo3Tb6
+                j5rQD3qOoG/0YcH24Y922IciwWX4UCmPSOT1K0o9qOXAqUz8UNFLZTTFAsqw
+                TFdpsZaDAaB4wL3oreWgGy20Qg859iFaAkWcqi+YReeCaa9DvurUXJlqcz1y
+                bq6VhuZK1zXXz8/fXKtOzZWpNdc6i9ovmqvgMXQ9B6bHsPcclbDW0FxPTmyu
+                dF2sHp/eXKtOzZU5bK511lwOEJ9C5T4gPEWw9mH2k4bm2mixudJ1zfXLVptr
+                1am5MnXNtc6aywHzM75b+zH2YOwjraG5fn3m5krXNdevztZcq8c3V6apuZzL
+                xE8FxP0FGqmAMnyB5vq0obk+abG56gvm6WnN5aEvOKNJLzH1/++vi//+4ttQ
+                Aen4DfKsbJA7SZtJyiZpi9QkbdNOEvWtbSA5tEt7G9Rv0qBJOZzVJuVN0k0y
+                TCqYNMsn5026a9ISpxdMWjUpbdKnJt02KWFS1KSQSV6TirwuumDVwq/EtZf/
+                C7XJpgW2HAAA
+                """,
+                """
+                androidx/compose/runtime/LaunchedEffectImpl.class:
+                H4sIAAAAAAAA/5VSW08TURD+znbZbleUsgKWi4iCUKiwhfhWQqJETJOKBpSY
+                8HS6Xcppt2fJXhoeG179F/4DH4zEB9Pgmz/KONuLIKiEZPfM5Xwz852Z+fHz
+                6zcAT7HKkOOy4nuicmzZXuPICxzLj2QoGo5V4pG0D53Ki4MDxw6LjSM3CcaQ
+                rvEmt1wuq9brco1ukkgwaOtCinCDYTZbqnuhK6RVazasA0oRCk8G1lZPWy0s
+                7jGI61Dry32A7fleFArpBNamR8xkxGPEOeAdFS5sFHKly8TIGdeaLXl+1ao5
+                YdnngmpwKb2Qd+tte+F25LoFBjXkQV1HimH6AjMhQ8eX3LWKMvQpWNhBErcY
+                Rqkxdr0X/Yb7vOEQkGEhe5XFBc9unKRKrAZxG3cMDGKIIZGN7QEMG1BhMsxc
+                18BBpDCSgoLRmPahCBiWSzcYI722cl37b9r9vzWfYbiPeuWEvMJDTj6l0UzQ
+                8rH40OMDDKxO/mMRW3nSKrSXH9qtCUPJKIaSbrcM+jp6x6ZfV/R2K9NurSl5
+                9nzenEorExmdmYapm6qp5AfyqqmZaoblWT7x/ZS1W2cfNSWt7Sz+D/j+7ETt
+                g1Wqkzw7UUjq4zGlNRYTNfsPOp/GhYH9o1kEMbrdD1bqIUNqV1QlDyPfYZjc
+                6Y6pKJsiEGXXeXa+nTTdTa9CoKES5dyOGmXHf8sJE/PwbO7ucV/Eds85dznX
+                78X8I6mx60W+7WyJOGa8F7N3pTpWacPUznjMeOHIWiRLwTiWSGrk17vDo93V
+                kECOrBLdKyTTOdM4RXrpC+4u5T5j7FMn8gmddwipYQsGXmKI5DL5xroxuIdM
+                vA+kxfVYr14SKySTrFdQgdU5s8iT3CTvBBGY3EeiiKki7hcxjQekYqaIh3i0
+                DxZgFnP70ANkAjwOkAowH2Cho2sBRn4Bxaa1HQ8FAAA=
                 """
     )
 
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
index 48cb5ef..71b1990 100644
--- a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -45,6 +45,11 @@
         val MutableStateMapOf = Name(PackageName, "mutableStateMapOf")
         val ProduceState = Name(PackageName, "produceState")
         val Remember = Name(PackageName, "remember")
+        val DisposableEffect = Name(PackageName, "DisposableEffect")
+        val RememberSaveable = Name(PackageName, "rememberSaveable")
+        val LaunchedEffect = Name(PackageName, "LaunchedEffect")
+        val ReusableContent = Name(PackageName, "ReusableContent")
+        val Key = Name(PackageName, "key")
     }
     object Ui {
         val PackageName = Package("androidx.compose.ui")
diff --git a/compose/material/material-icons-core/build.gradle b/compose/material/material-icons-core/build.gradle
index 0276b88..615b415 100644
--- a/compose/material/material-icons-core/build.gradle
+++ b/compose/material/material-icons-core/build.gradle
@@ -15,65 +15,100 @@
  */
 
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
 import androidx.compose.material.icons.generator.tasks.IconGenerationTask
 
+
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    dependencies {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api("androidx.compose.ui:ui:1.2.1")
-        implementation("androidx.compose.runtime:runtime:1.2.1")
-        implementation(libs.kotlinStdlib)
-
-        samples(project(":compose:material:material-icons-core:material-icons-core-samples"))
-    }
-}
-
-if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:ui:ui"))
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
                 implementation(libs.kotlinStdlib)
             }
         }
-    }
-    dependencies {
-        samples(project(":compose:material:material-icons-core:material-icons-core-samples"))
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                    api(project(":compose:ui:ui"))
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
+            }
+        }
+
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                }
+            }
+        }
     }
 }
 
 IconGenerationTask.registerCoreIconProject(
         project,
         android,
-        AndroidXComposePlugin.isMultiplatformEnabled(project)
+        true
 )
 
 androidx {
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index 0903147..b7e2fea 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
 import androidx.build.RunApiTasks
 import androidx.compose.material.icons.generator.tasks.IconGenerationTask
-import androidx.compose.material.icons.generator.tasks.ExtendedIconGenerationTask
 
 plugins {
     id("AndroidXPlugin")
@@ -26,7 +25,7 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
 IconGenerationTask.registerExtendedIconMainProject(
         project,
@@ -35,38 +34,62 @@
 
 apply from: "shared-dependencies.gradle"
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make an analogous update in the
-         * corresponding block below
-         */
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.kotlinReflect)
-        androidTestImplementation(libs.truth)
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(project(":compose:material:material-icons-core"))
+                implementation(libs.kotlinStdlibCommon)
+                implementation(project(":compose:runtime:runtime"))
+            }
+        }
 
-        androidTestImplementation(project(":compose:foundation:foundation"))
-        androidTestImplementation(project(":compose:foundation:foundation-layout"))
-        androidTestImplementation(project(":compose:ui:ui"))
-        androidTestImplementation(project(":test:screenshot:screenshot"))
-        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation("androidx.activity:activity-compose:1.3.1")
-        androidTestImplementation("androidx.appcompat:appcompat:1.3.0")
-    }
-}
+        commonTest {
+            dependencies {
+            }
+        }
 
-if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            androidAndroidTest.dependencies {
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:foundation:foundation"))
                 implementation(project(":compose:foundation:foundation-layout"))
                 implementation(project(":compose:ui:ui"))
@@ -83,6 +106,21 @@
                 implementation(libs.truth)
             }
         }
+
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                }
+            }
+        }
     }
 }
 
@@ -102,43 +140,7 @@
 
 }
 
-if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    def allThemeProjects = [
-            project(":compose:material:material-icons-extended-filled"),
-            project(":compose:material:material-icons-extended-outlined"),
-            project(":compose:material:material-icons-extended-rounded"),
-            project(":compose:material:material-icons-extended-sharp"),
-            project(":compose:material:material-icons-extended-twotone")
-    ]
-
-    for (themeProject in allThemeProjects) {
-        project.dependencies.add("embedThemesDebug", themeProject)
-        project.dependencies.add("embedThemesRelease", themeProject)
-    }
-    // Compiling all of the icons in this project takes a while,
-    // so when possible, we compile each theme in its own project and merge them here.
-    // Hopefully we can revert this when parallel compilation is supported:
-    // https://youtrack.jetbrains.com/issue/KT-46085
-    android {
-        libraryVariants.all { v ->
-            if (v.name.toLowerCase().contains("debug")) {
-                v.registerPostJavacGeneratedBytecode(configurations.embedThemesDebug)
-            } else {
-                v.registerPostJavacGeneratedBytecode(configurations.embedThemesRelease)
-            }
-            // Manually set up source jar generation
-            ExtendedIconGenerationTask.registerSourceJarOnly(project, v)
-        }
-    }
-} else {
-    // We're not sure how to compile these icons in parallel when multiplatform is enabled
-    IconGenerationTask.registerExtendedIconThemeProject(
-            project,
-            android,
-            AndroidXComposePlugin.isMultiplatformEnabled(project)
-    )
-}
-
+IconGenerationTask.registerExtendedIconThemeProject(project, android)
 
 androidx {
     name = "Compose Material Icons Extended"
diff --git a/compose/material/material-icons-extended/generate.gradle b/compose/material/material-icons-extended/generate.gradle
index aed94d9..eb45afa 100644
--- a/compose/material/material-icons-extended/generate.gradle
+++ b/compose/material/material-icons-extended/generate.gradle
@@ -25,17 +25,9 @@
 apply plugin: "com.android.library"
 apply plugin: "AndroidXComposePlugin"
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
 apply from: "${buildscript.sourceFile.parentFile}/shared-dependencies.gradle"
 
-if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    // We're not sure how to merge icons jars when multiplatform is enabled
-    IconGenerationTask.registerExtendedIconThemeProject(
-            project,
-            android,
-            AndroidXComposePlugin.isMultiplatformEnabled(project)
-    )
-}
+IconGenerationTask.registerExtendedIconThemeProject(project, android)
 
 dependencies.attributesSchema {
     attribute(iconExportAttr)
diff --git a/compose/material/material-icons-extended/shared-dependencies.gradle b/compose/material/material-icons-extended/shared-dependencies.gradle
index f2cbca1..2de0ef0 100644
--- a/compose/material/material-icons-extended/shared-dependencies.gradle
+++ b/compose/material/material-icons-extended/shared-dependencies.gradle
@@ -18,36 +18,25 @@
 // by its specific theme projects (each of which compile a specific theme)
 
 import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 
-dependencies {
-    if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make an analogous update in the
-         * corresponding block below
-         */
-       api(project(":compose:material:material-icons-core"))
-       implementation(libs.kotlinStdlibCommon)
-       implementation(project(":compose:runtime:runtime"))
-    }
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
+
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 }
 
-if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
-                api(project(":compose:material:material-icons-core"))
-                implementation(libs.kotlinStdlibCommon)
-                implementation(project(":compose:runtime:runtime"))
-            }
+kotlin {
+    /*
+     * When updating dependencies, make sure to make an analogous update in the
+     * corresponding block above
+     */
+    sourceSets {
+        commonMain.dependencies {
+            api(project(":compose:material:material-icons-core"))
+            implementation(libs.kotlinStdlibCommon)
+            implementation(project(":compose:runtime:runtime"))
         }
     }
 }
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index 31a99bb..999f59f 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,48 +23,15 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-        api("androidx.compose.foundation:foundation:1.2.1")
-        api("androidx.compose.runtime:runtime:1.2.1")
-
-        implementation(libs.kotlinStdlibCommon)
-        implementation("androidx.compose.animation:animation:1.2.1")
-        implementation("androidx.compose.ui:ui-util:1.2.1")
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:foundation:foundation"))
                 api(project(":compose:runtime:runtime"))
@@ -73,19 +39,55 @@
                 implementation(project(":compose:animation:animation"))
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.truth)
+        commonTest {
+            dependencies {
             }
+        }
 
-            androidAndroidTest.dependencies {
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                    api(project(":compose:foundation:foundation"))
+                    api(project(":compose:runtime:runtime"))
+                    implementation(project(":compose:animation:animation"))
+                    implementation(project(":compose:ui:ui-util"))
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:test-utils"))
 
                 implementation(libs.testRules)
@@ -94,6 +96,29 @@
                 implementation(libs.truth)
             }
         }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                }
+            }
+        }
     }
 }
 
@@ -105,7 +130,7 @@
     // Disable strict API mode for MPP builds as it will fail to compile androidAndroidTest
     // sources, as it doesn't understand that they are tests and thinks they should have explicit
     // visibility
-    legacyDisableKotlinStrictApiMode = AndroidXComposePlugin.isMultiplatformEnabled(project)
+    legacyDisableKotlinStrictApiMode = true
 }
 
 android {
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 4eea647..c9c5c53 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -7,7 +7,8 @@
   }
 
   public final class AndroidMenu_androidKt {
-    method @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index d9fe905..a45a971 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -7,7 +7,8 @@
   }
 
   public final class AndroidMenu_androidKt {
-    method @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
@@ -426,7 +427,7 @@
   }
 
   @androidx.compose.material.ExperimentalMaterialApi @kotlin.jvm.JvmDefaultWithCompatibility public interface ExposedDropdownMenuBoxScope {
-    method @androidx.compose.runtime.Composable public default void ExposedDropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public default void ExposedDropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.ScrollState scrollState, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method public androidx.compose.ui.Modifier exposedDropdownSize(androidx.compose.ui.Modifier, optional boolean matchTextFieldWidth);
   }
 
@@ -531,7 +532,7 @@
   }
 
   public final class ModalBottomSheetKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void ModalBottomSheetLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ModalBottomSheetState sheetState, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void ModalBottomSheetLayout(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ModalBottomSheetState sheetState, optional boolean sheetGesturesEnabled, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
     method @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.material.ModalBottomSheetState ModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, androidx.compose.ui.unit.Density density, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmValueChange, optional boolean isSkipHalfExpanded);
     method @Deprecated @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.material.ModalBottomSheetState ModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmValueChange, optional boolean isSkipHalfExpanded);
     method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.ModalBottomSheetState rememberModalBottomSheetState(androidx.compose.material.ModalBottomSheetValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.ModalBottomSheetValue,java.lang.Boolean> confirmValueChange, optional boolean skipHalfExpanded);
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 4eea647..c9c5c53 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -7,7 +7,8 @@
   }
 
   public final class AndroidMenu_androidKt {
-    method @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void DropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional long offset, optional androidx.compose.ui.window.PopupProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void DropdownMenuItem(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index 82e2838..3019ae0 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
 
 plugins {
@@ -24,70 +24,15 @@
     id("AndroidXPaparazziPlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-        api("androidx.compose.animation:animation-core:1.2.1")
-        api(project(":compose:foundation:foundation"))
-        api(project(":compose:material:material-icons-core"))
-        api(project(":compose:material:material-ripple"))
-        api("androidx.compose.runtime:runtime:1.2.1")
-        api("androidx.compose.ui:ui:1.2.1")
-        api(project(":compose:ui:ui-text"))
-
-        implementation(libs.kotlinStdlibCommon)
-        implementation("androidx.compose.animation:animation:1.2.1")
-        implementation("androidx.compose.foundation:foundation-layout:1.2.1")
-        implementation("androidx.compose.ui:ui-util:1.2.1")
-
-        // TODO: remove next 3 dependencies when b/202810604 is fixed
-        implementation("androidx.savedstate:savedstate:1.2.1")
-        implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
-        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-
-        androidTestImplementation(project(":compose:material:material:material-samples"))
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(project(":test:screenshot:screenshot"))
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.dexmakerMockito)
-        androidTestImplementation(libs.mockitoCore)
-        androidTestImplementation(libs.mockitoKotlin)
-        androidTestImplementation(libs.testUiautomator)
-
-        lintPublish project(":compose:material:material-lint")
-
-        samples(project(":compose:material:material:material-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:animation:animation-core"))
                 api(project(":compose:foundation:foundation"))
@@ -101,8 +46,37 @@
                 implementation(project(":compose:foundation:foundation-layout"))
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                    api(project(":compose:animation:animation-core"))
+                    api(project(":compose:runtime:runtime"))
+                    api(project(":compose:ui:ui"))
+                    api(project(":compose:ui:ui-text"))
+                    implementation(project(":compose:animation:animation"))
+                    implementation(project(":compose:foundation:foundation-layout"))
+                    implementation(project(":compose:ui:ui-util"))
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
 
                 // TODO: remove next 3 dependencies when b/202810604 is fixed
@@ -110,23 +84,27 @@
                 implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
                 implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
             }
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.kotlinStdlib)
+                }
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.truth)
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
             }
+        }
 
-            androidAndroidTest.dependencies {
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:material:material:material-samples"))
                 implementation(project(":compose:test-utils"))
                 implementation(project(":test:screenshot:screenshot"))
@@ -140,18 +118,39 @@
                 implementation(libs.mockitoKotlin)
                 implementation(libs.testUiautomator)
             }
+        }
 
-            desktopTest.dependencies {
-                implementation(project(":compose:ui:ui-test-junit4"))
-                implementation(libs.truth)
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
                 implementation(libs.junit)
-                implementation(libs.skikoCurrentOs)
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                    implementation(project(":compose:ui:ui-test-junit4"))
+                    implementation(libs.truth)
+                    implementation(libs.junit)
+                    implementation(libs.skikoCurrentOs)
+                }
             }
         }
     }
-    dependencies {
-        samples(project(":compose:material:material:material-samples"))
-    }
+}
+
+dependencies {
+    lintPublish project(":compose:material:material-lint")
 }
 
 androidx {
diff --git a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt
index 2fe5d63..d705315 100644
--- a/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt
+++ b/compose/material/material/icons/generator/src/main/kotlin/androidx/compose/material/icons/generator/tasks/IconGenerationTask.kt
@@ -157,15 +157,10 @@
         @JvmStatic
         fun registerExtendedIconThemeProject(
             project: Project,
-            libraryExtension: LibraryExtension,
-            isMpp: Boolean
+            libraryExtension: LibraryExtension
         ) {
-            if (isMpp) {
-                ExtendedIconGenerationTask.register(project, null)
-            } else {
-                libraryExtension.libraryVariants.all { variant ->
-                    ExtendedIconGenerationTask.register(project, variant)
-                }
+            libraryExtension.libraryVariants.all { variant ->
+                ExtendedIconGenerationTask.register(project, variant)
             }
 
             // b/175401659 - disable lint as it takes a long time, and most errors should
diff --git a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt
index ccbd6ff..30fa679 100644
--- a/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt
+++ b/compose/material/material/integration-tests/material-catalog/src/main/java/androidx/compose/material/catalog/library/model/Examples.kt
@@ -50,6 +50,7 @@
 import androidx.compose.material.samples.LeadingIconTabs
 import androidx.compose.material.samples.LinearProgressIndicatorSample
 import androidx.compose.material.samples.MenuSample
+import androidx.compose.material.samples.MenuWithScrollStateSample
 import androidx.compose.material.samples.ModalBottomSheetSample
 import androidx.compose.material.samples.ModalDrawerSample
 import androidx.compose.material.samples.NavigationRailBottomAlignSample
@@ -398,6 +399,13 @@
         MenuSample()
     },
     Example(
+        name = ::MenuWithScrollStateSample.name,
+        description = MenusExampleDescription,
+        sourceUrl = MenusExampleSourceUrl
+    ) {
+        MenuWithScrollStateSample()
+    },
+    Example(
         name = ::ExposedDropdownMenuSample.name,
         description = MenusExampleDescription,
         sourceUrl = MenusExampleSourceUrl
@@ -703,14 +711,18 @@
         description = TextFieldsExampleDescription,
         sourceUrl = TextFieldsExampleSourceUrl
     ) {
-       TextArea()
+        TextArea()
     }
 ).map {
     // By default text field samples are minimal and don't have a `width` modifier to restrict the
     // width. As a result, they grow horizontally if enough text is typed. To prevent this behavior
     // in Catalog app the code below restricts the width of every text field sample
     it.copy(content = {
-        Box(Modifier.wrapContentWidth().width(280.dp)) { it.content() }
+        Box(
+            Modifier
+                .wrapContentWidth()
+                .width(280.dp)
+        ) { it.content() }
     })
 }
 
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt
index bf6f420..f9c6a77 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/MenuSamples.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.material.Divider
 import androidx.compose.material.DropdownMenu
 import androidx.compose.material.DropdownMenuItem
@@ -29,6 +30,7 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.MoreVert
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -41,7 +43,9 @@
 fun MenuSample() {
     var expanded by remember { mutableStateOf(false) }
 
-    Box(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.TopStart)) {
+    Box(modifier = Modifier
+        .fillMaxSize()
+        .wrapContentSize(Alignment.TopStart)) {
         IconButton(onClick = { expanded = true }) {
             Icon(Icons.Default.MoreVert, contentDescription = "Localized description")
         }
@@ -61,4 +65,37 @@
             }
         }
     }
-}
\ No newline at end of file
+}
+
+@Sampled
+@Composable
+fun MenuWithScrollStateSample() {
+    var expanded by remember { mutableStateOf(false) }
+    val scrollState = rememberScrollState()
+    Box(
+        modifier = Modifier
+            .fillMaxSize()
+            .wrapContentSize(Alignment.TopStart)
+    ) {
+        IconButton(onClick = { expanded = true }) {
+            Icon(Icons.Default.MoreVert, contentDescription = "Localized description")
+        }
+        DropdownMenu(
+            expanded = expanded,
+            onDismissRequest = { expanded = false },
+            scrollState = scrollState
+        ) {
+            repeat(30) {
+                DropdownMenuItem(onClick = { /* Handle item! */ }) {
+                    Text("Item ${it + 1}")
+                }
+            }
+        }
+        LaunchedEffect(expanded) {
+            if (expanded) {
+                // Scroll to show the bottom menu items.
+                scrollState.scrollTo(scrollState.maxValue)
+            }
+        }
+    }
+}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt
index 17c6cc8..23300d9 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ExposedDropdownMenuTest.kt
@@ -22,7 +22,9 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -35,9 +37,11 @@
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertTextContains
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -336,6 +340,47 @@
         // Should not have crashed.
     }
 
+    @Test
+    fun withScrolledContent() {
+        rule.setMaterialContent {
+            Box(Modifier.fillMaxSize()) {
+                ExposedDropdownMenuBox(
+                    modifier = Modifier.align(Alignment.Center),
+                    expanded = true,
+                    onExpandedChange = { }
+                ) {
+                    val scrollState = rememberScrollState()
+                    TextField(
+                        value = "",
+                        onValueChange = { },
+                        label = { Text("Label") },
+                    )
+                    ExposedDropdownMenu(
+                        expanded = true,
+                        onDismissRequest = { },
+                        scrollState = scrollState
+                    ) {
+                        repeat(100) {
+                            Box(
+                                Modifier
+                                    .testTag("MenuContent ${it + 1}")
+                                    .size(with(LocalDensity.current) { 70.toDp() })
+                            )
+                        }
+                    }
+                    LaunchedEffect(Unit) {
+                        scrollState.scrollTo(scrollState.maxValue)
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("MenuContent 1").assertIsNotDisplayed()
+        rule.onNodeWithTag("MenuContent 100").assertIsDisplayed()
+    }
+
     @Composable
     fun ExposedDropdownMenuForTest(
         expanded: Boolean,
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt
index ace0f2a..8ad3242 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/MenuTest.kt
@@ -21,6 +21,8 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -29,6 +31,8 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.hasAnyDescendant
 import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.isPopup
@@ -128,6 +132,42 @@
     }
 
     @Test
+    fun menu_scrolledContent() {
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(
+                    Modifier
+                        .requiredSize(20.toDp())
+                        .background(color = Color.Blue)
+                ) {
+                    val scrollState = rememberScrollState()
+                    DropdownMenu(
+                        expanded = true,
+                        onDismissRequest = {},
+                        scrollState = scrollState
+                    ) {
+                        repeat(100) {
+                            Box(
+                                Modifier
+                                    .testTag("MenuContent ${it + 1}")
+                                    .size(70.toDp())
+                            )
+                        }
+                    }
+                    LaunchedEffect(Unit) {
+                        scrollState.scrollTo(scrollState.maxValue)
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("MenuContent 1").assertIsNotDisplayed()
+        rule.onNodeWithTag("MenuContent 100").assertIsDisplayed()
+    }
+
+    @Test
     fun menu_positioning_bottomEnd() {
         val screenWidth = 500
         val screenHeight = 1000
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
index 9043b15..cb2f6d2 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
@@ -36,6 +36,10 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.testTag
@@ -56,11 +60,13 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.coerceAtMost
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
@@ -875,6 +881,7 @@
                         }
                     }
                 },
+                sheetGesturesEnabled = true,
                 content = { Box(Modifier.fillMaxSize()) }
             )
         }
@@ -926,6 +933,53 @@
     }
 
     @Test
+    fun modalBottomSheet_gesturesDisabled_doesNotParticipateInNestedScroll() =
+        runBlocking(AutoTestFrameClock()) {
+            lateinit var sheetState: ModalBottomSheetState
+            val sheetContentTag = "sheetContent"
+            val scrollConnection = object : NestedScrollConnection {}
+            val scrollDispatcher = NestedScrollDispatcher()
+            val sheetHeight = 300.dp
+            val sheetHeightPx = with(rule.density) { sheetHeight.toPx() }
+
+            rule.setContent {
+                sheetState = rememberModalBottomSheetState(
+                    initialValue = ModalBottomSheetValue.Expanded,
+                )
+                ModalBottomSheetLayout(
+                    sheetState = sheetState,
+                    sheetContent = {
+                        Box(
+                            Modifier
+                                .fillMaxWidth()
+                                .requiredHeight(sheetHeight)
+                                .nestedScroll(scrollConnection, scrollDispatcher)
+                                .testTag(sheetContentTag),
+                        )
+                    },
+                    sheetGesturesEnabled = false,
+                    content = { Box(Modifier.fillMaxSize()) },
+                )
+            }
+
+            assertThat(sheetState.currentValue).isEqualTo(ModalBottomSheetValue.Expanded)
+
+            val offsetBeforeScroll = sheetState.requireOffset()
+            scrollDispatcher.dispatchPreScroll(
+                Offset(x = 0f, y = -sheetHeightPx),
+                NestedScrollSource.Drag,
+            )
+            rule.waitForIdle()
+            assertWithMessage("Offset after scroll is equal to offset before scroll")
+                .that(sheetState.requireOffset()).isEqualTo(offsetBeforeScroll)
+
+            val highFlingVelocity = Velocity(x = 0f, y = with(rule.density) { 500.dp.toPx() })
+            scrollDispatcher.dispatchPreFling(highFlingVelocity)
+            rule.waitForIdle()
+            assertThat(sheetState.currentValue).isEqualTo(ModalBottomSheetValue.Expanded)
+        }
+
+    @Test
     fun modalBottomSheet_anchorsChange_retainsCurrentValue() {
         lateinit var state: ModalBottomSheetState
         var amountOfItems by mutableStateOf(0)
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/AndroidMenu.android.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/AndroidMenu.android.kt
index 94c0373..e1bd14d 100644
--- a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/AndroidMenu.android.kt
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/AndroidMenu.android.kt
@@ -17,11 +17,13 @@
 package androidx.compose.material
 
 import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -70,6 +72,15 @@
  * tapping outside the menu's bounds
  * @param offset [DpOffset] to be added to the position of the menu
  */
+@Deprecated(
+    level = DeprecationLevel.HIDDEN,
+    replaceWith = ReplaceWith(
+        expression = "DropdownMenu(expanded,onDismissRequest, modifier, offset, " +
+            "rememberScrollState(), properties, content)",
+        "androidx.compose.foundation.rememberScrollState"
+    ),
+    message = "Replaced by a DropdownMenu function with a ScrollState parameter"
+)
 @Composable
 fun DropdownMenu(
     expanded: Boolean,
@@ -78,6 +89,69 @@
     offset: DpOffset = DpOffset(0.dp, 0.dp),
     properties: PopupProperties = PopupProperties(focusable = true),
     content: @Composable ColumnScope.() -> Unit
+) = DropdownMenu(
+    expanded = expanded,
+    onDismissRequest = onDismissRequest,
+    modifier = modifier,
+    offset = offset,
+    scrollState = rememberScrollState(),
+    properties = properties,
+    content = content
+)
+
+/**
+ * <a href="https://material.io/components/menus#dropdown-menu" class="external" target="_blank">Material Design dropdown menu</a>.
+ *
+ * A dropdown menu is a compact way of displaying multiple choices. It appears upon interaction with
+ * an element (such as an icon or button) or when users perform a specific action.
+ *
+ * ![Menus image](https://developer.android.com/images/reference/androidx/compose/material/menus.png)
+ *
+ * A [DropdownMenu] behaves similarly to a [Popup], and will use the position of the parent layout
+ * to position itself on screen. Commonly a [DropdownMenu] will be placed in a [Box] with a sibling
+ * that will be used as the 'anchor'. Note that a [DropdownMenu] by itself will not take up any
+ * space in a layout, as the menu is displayed in a separate window, on top of other content.
+ *
+ * The [content] of a [DropdownMenu] will typically be [DropdownMenuItem]s, as well as custom
+ * content. Using [DropdownMenuItem]s will result in a menu that matches the Material
+ * specification for menus. Also note that the [content] is placed inside a scrollable [Column],
+ * so using a [LazyColumn] as the root layout inside [content] is unsupported.
+ *
+ * [onDismissRequest] will be called when the menu should close - for example when there is a
+ * tap outside the menu, or when the back key is pressed.
+ *
+ * [DropdownMenu] changes its positioning depending on the available space, always trying to be
+ * fully visible. It will try to expand horizontally, depending on layout direction, to the end of
+ * its parent, then to the start of its parent, and then screen end-aligned. Vertically, it will
+ * try to expand to the bottom of its parent, then from the top of its parent, and then screen
+ * top-aligned. An [offset] can be provided to adjust the positioning of the menu for cases when
+ * the layout bounds of its parent do not coincide with its visual bounds. Note the offset will
+ * be applied in the direction in which the menu will decide to expand.
+ *
+ * Example usage:
+ * @sample androidx.compose.material.samples.MenuSample
+ *
+ * Example usage with a [ScrollState] to control the menu items scroll position:
+ * @sample androidx.compose.material.samples.MenuWithScrollStateSample
+ *
+ * @param expanded whether the menu is expanded or not
+ * @param onDismissRequest called when the user requests to dismiss the menu, such as by tapping
+ * outside the menu's bounds
+ * @param modifier [Modifier] to be applied to the menu's content
+ * @param offset [DpOffset] to be added to the position of the menu
+ * @param scrollState a [ScrollState] to used by the menu's content for items vertical scrolling
+ * @param properties [PopupProperties] for further customization of this popup's behavior
+ * @param content the content of this dropdown menu, typically a [DropdownMenuItem]
+ */
+@Composable
+fun DropdownMenu(
+    expanded: Boolean,
+    onDismissRequest: () -> Unit,
+    modifier: Modifier = Modifier,
+    offset: DpOffset = DpOffset(0.dp, 0.dp),
+    scrollState: ScrollState = rememberScrollState(),
+    properties: PopupProperties = PopupProperties(focusable = true),
+    content: @Composable ColumnScope.() -> Unit
 ) {
     val expandedStates = remember { MutableTransitionState(false) }
     expandedStates.targetState = expanded
@@ -100,6 +174,7 @@
             DropdownMenuContent(
                 expandedStates = expandedStates,
                 transformOriginState = transformOriginState,
+                scrollState = scrollState,
                 modifier = modifier,
                 content = content
             )
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
index 4a46e56..660f1c2 100644
--- a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.MutableTransitionState
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
@@ -30,6 +31,7 @@
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowDropDown
 import androidx.compose.material.internal.ExposedDropdownMenuPopup
@@ -223,6 +225,7 @@
      * @param onDismissRequest Called when the user requests to dismiss the menu, such as by
      * tapping outside the menu's bounds
      * @param modifier The modifier to apply to this layout
+     * @param scrollState a [ScrollState] to used by the menu's content for items vertical scrolling
      * @param content The content of the [ExposedDropdownMenu]
      */
     @Composable
@@ -230,6 +233,7 @@
         expanded: Boolean,
         onDismissRequest: () -> Unit,
         modifier: Modifier = Modifier,
+        scrollState: ScrollState = rememberScrollState(),
         content: @Composable ColumnScope.() -> Unit
     ) {
         // TODO(b/202810604): use DropdownMenu when PopupProperties constructor is stable
@@ -261,6 +265,7 @@
                 DropdownMenuContent(
                     expandedStates = expandedStates,
                     transformOriginState = transformOriginState,
+                    scrollState = scrollState,
                     modifier = modifier.exposedDropdownSize(),
                     content = content
                 )
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
index 3e80837..9d3dadbd 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Menu.kt
@@ -21,8 +21,9 @@
 import androidx.compose.animation.core.animateFloat
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
-import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.IntrinsicSize
@@ -33,14 +34,13 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -57,11 +57,11 @@
 import kotlin.math.max
 import kotlin.math.min
 
-@Suppress("ModifierParameter")
 @Composable
 internal fun DropdownMenuContent(
     expandedStates: MutableTransitionState<Boolean>,
     transformOriginState: MutableState<TransformOrigin>,
+    scrollState: ScrollState,
     modifier: Modifier = Modifier,
     content: @Composable ColumnScope.() -> Unit
 ) {
@@ -126,7 +126,7 @@
             modifier = modifier
                 .padding(vertical = DropdownMenuVerticalPadding)
                 .width(IntrinsicSize.Max)
-                .verticalScroll(rememberScrollState()),
+                .verticalScroll(scrollState),
             content = content
         )
     }
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 9d1da3f..8b9eef6 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -528,6 +528,7 @@
  * @param sheetContent The content of the bottom sheet.
  * @param modifier Optional [Modifier] for the entire component.
  * @param sheetState The state of the bottom sheet.
+ * @param sheetGesturesEnabled Whether the bottom sheet can be interacted with by gestures.
  * @param sheetShape The shape of the bottom sheet.
  * @param sheetElevation The elevation of the bottom sheet.
  * @param sheetBackgroundColor The background color of the bottom sheet.
@@ -547,6 +548,7 @@
     modifier: Modifier = Modifier,
     sheetState: ModalBottomSheetState =
         rememberModalBottomSheetState(Hidden),
+    sheetGesturesEnabled: Boolean = true,
     sheetShape: Shape = MaterialTheme.shapes.large,
     sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
     sheetBackgroundColor: Color = MaterialTheme.colors.surface,
@@ -585,13 +587,17 @@
                 .align(Alignment.TopCenter) // We offset from the top so we'll center from there
                 .widthIn(max = MaxModalBottomSheetWidth)
                 .fillMaxWidth()
-                .nestedScroll(
-                    remember(sheetState.anchoredDraggableState, orientation) {
-                        ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
-                            state = sheetState.anchoredDraggableState,
-                            orientation = orientation
+                .then(
+                    if (sheetGesturesEnabled) {
+                        Modifier.nestedScroll(
+                            remember(sheetState.anchoredDraggableState, orientation) {
+                                ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
+                                    state = sheetState.anchoredDraggableState,
+                                    orientation = orientation
+                                )
+                            }
                         )
-                    }
+                    } else Modifier
                 )
                 .offset {
                     IntOffset(
@@ -604,7 +610,8 @@
                 .anchoredDraggable(
                     state = sheetState.anchoredDraggableState,
                     orientation = orientation,
-                    enabled = sheetState.anchoredDraggableState.currentValue != Hidden,
+                    enabled = sheetGesturesEnabled &&
+                        sheetState.anchoredDraggableState.currentValue != Hidden,
                 )
                 .onSizeChanged { sheetSize ->
                     val anchors = buildMap {
@@ -619,37 +626,45 @@
                     }
                     sheetState.anchoredDraggableState.updateAnchors(anchors, anchorChangeCallback)
                 }
-                .semantics {
-                    if (sheetState.isVisible) {
-                        dismiss {
-                            if (sheetState.anchoredDraggableState.confirmValueChange(Hidden)) {
-                                scope.launch { sheetState.hide() }
-                            }
-                            true
-                        }
-                        if (sheetState.anchoredDraggableState.currentValue == HalfExpanded) {
-                            expand {
-                                if (sheetState.anchoredDraggableState.confirmValueChange(
-                                        Expanded
-                                    )
-                                ) {
-                                    scope.launch { sheetState.expand() }
+                .then(
+                    if (sheetGesturesEnabled) {
+                        Modifier.semantics {
+                            if (sheetState.isVisible) {
+                                dismiss {
+                                    if (
+                                        sheetState.anchoredDraggableState.confirmValueChange(Hidden)
+                                    ) {
+                                        scope.launch { sheetState.hide() }
+                                    }
+                                    true
                                 }
-                                true
-                            }
-                        } else if (sheetState.hasHalfExpandedState) {
-                            collapse {
-                                if (sheetState.anchoredDraggableState.confirmValueChange(
-                                        HalfExpanded
-                                    )
+                                if (sheetState.anchoredDraggableState.currentValue
+                                    == HalfExpanded
                                 ) {
-                                    scope.launch { sheetState.halfExpand() }
+                                    expand {
+                                        if (sheetState.anchoredDraggableState.confirmValueChange(
+                                                Expanded
+                                            )
+                                        ) {
+                                            scope.launch { sheetState.expand() }
+                                        }
+                                        true
+                                    }
+                                } else if (sheetState.hasHalfExpandedState) {
+                                    collapse {
+                                        if (sheetState.anchoredDraggableState.confirmValueChange(
+                                                HalfExpanded
+                                            )
+                                        ) {
+                                            scope.launch { sheetState.halfExpand() }
+                                        }
+                                        true
+                                    }
                                 }
-                                true
                             }
                         }
-                    }
-                },
+                    } else Modifier
+                ),
             shape = sheetShape,
             elevation = sheetElevation,
             color = sheetBackgroundColor,
diff --git a/compose/material/material/src/desktopMain/kotlin/androidx/compose/material/DesktopMenu.desktop.kt b/compose/material/material/src/desktopMain/kotlin/androidx/compose/material/DesktopMenu.desktop.kt
index 4da69f2..ea91b6c 100644
--- a/compose/material/material/src/desktopMain/kotlin/androidx/compose/material/DesktopMenu.desktop.kt
+++ b/compose/material/material/src/desktopMain/kotlin/androidx/compose/material/DesktopMenu.desktop.kt
@@ -21,6 +21,8 @@
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.mutableStateOf
@@ -74,7 +76,15 @@
  * @param offset [DpOffset] to be added to the position of the menu
  * @param content content lambda
  */
-@Suppress("ModifierParameter")
+@Deprecated(
+    level = DeprecationLevel.HIDDEN,
+    replaceWith = ReplaceWith(
+        expression = "DropdownMenu(expanded,onDismissRequest, focusable, modifier, offset, " +
+            "rememberScrollState(), content)",
+        "androidx.compose.foundation.rememberScrollState"
+    ),
+    message = "Replaced by a DropdownMenu function with a ScrollState parameter"
+)
 @Composable
 fun DropdownMenu(
     expanded: Boolean,
@@ -83,6 +93,64 @@
     modifier: Modifier = Modifier,
     offset: DpOffset = DpOffset(0.dp, 0.dp),
     content: @Composable ColumnScope.() -> Unit
+) = DropdownMenu(
+    expanded = expanded,
+    onDismissRequest = onDismissRequest,
+    focusable = focusable,
+    modifier = modifier,
+    offset = offset,
+    scrollState = rememberScrollState(),
+    content = content
+)
+
+/**
+ * A Material Design [dropdown menu](https://material.io/components/menus#dropdown-menu).
+ *
+ * A [DropdownMenu] behaves similarly to a [Popup], and will use the position of the parent layout
+ * to position itself on screen. Commonly a [DropdownMenu] will be placed in a [Box] with a sibling
+ * that will be used as the 'anchor'. Note that a [DropdownMenu] by itself will not take up any
+ * space in a layout, as the menu is displayed in a separate window, on top of other content.
+ *
+ * The [content] of a [DropdownMenu] will typically be [DropdownMenuItem]s, as well as custom
+ * content. Using [DropdownMenuItem]s will result in a menu that matches the Material
+ * specification for menus. Also note that the [content] is placed inside a scrollable [Column],
+ * so using a [LazyColumn] as the root layout inside [content] is unsupported.
+ *
+ * [onDismissRequest] will be called when the menu should close - for example when there is a
+ * tap outside the menu, or when the back key is pressed.
+ *
+ * [DropdownMenu] changes its positioning depending on the available space, always trying to be
+ * fully visible. It will try to expand horizontally, depending on layout direction, to the end of
+ * its parent, then to the start of its parent, and then screen end-aligned. Vertically, it will
+ * try to expand to the bottom of its parent, then from the top of its parent, and then screen
+ * top-aligned. An [offset] can be provided to adjust the positioning of the menu for cases when
+ * the layout bounds of its parent do not coincide with its visual bounds. Note the offset will
+ * be applied in the direction in which the menu will decide to expand.
+ *
+ * Example usage:
+ * @sample androidx.compose.material.samples.MenuSample
+ *
+ * Example usage with a [ScrollState] to control the menu items scroll position:
+ * @sample androidx.compose.material.samples.MenuWithScrollStateSample
+ *
+ * @param expanded Whether the menu is currently open and visible to the user
+ * @param onDismissRequest Called when the user requests to dismiss the menu, such as by
+ * tapping outside the menu's bounds
+ * @param focusable Whether the dropdown can capture focus
+ * @param modifier [Modifier] to be applied to the menu's content
+ * @param offset [DpOffset] to be added to the position of the menu
+ * @param scrollState a [ScrollState] to used by the menu's content for items vertical scrolling
+ * @param content the content of this dropdown menu, typically a [DropdownMenuItem]
+ */
+@Composable
+fun DropdownMenu(
+    expanded: Boolean,
+    onDismissRequest: () -> Unit,
+    focusable: Boolean = true,
+    modifier: Modifier = Modifier,
+    offset: DpOffset = DpOffset(0.dp, 0.dp),
+    scrollState: ScrollState = rememberScrollState(),
+    content: @Composable ColumnScope.() -> Unit
 ) {
     val expandedStates = remember { MutableTransitionState(false) }
     expandedStates.targetState = expanded
@@ -110,6 +178,7 @@
                 expandedStates = expandedStates,
                 transformOriginState = transformOriginState,
                 modifier = modifier,
+                scrollState = scrollState,
                 content = content
             )
         }
@@ -163,8 +232,19 @@
  * @param expanded Whether the menu is currently open and visible to the user
  * @param onDismissRequest Called when the user requests to dismiss the menu, such as by
  * tapping outside the menu's bounds
+ * @param focusable Sets the ability for the menu to capture focus
+ * @param modifier The modifier for this layout.
+ * @param content The content lambda.
  */
-@Suppress("ModifierParameter")
+@Deprecated(
+    level = DeprecationLevel.HIDDEN,
+    replaceWith = ReplaceWith(
+        expression = "CursorDropdownMenu(expanded,onDismissRequest, focusable, modifier, " +
+            "rememberScrollState(), content)",
+        "androidx.compose.foundation.rememberScrollState"
+    ),
+    message = "Replaced by a CursorDropdownMenu function with a ScrollState parameter"
+)
 @Composable
 fun CursorDropdownMenu(
     expanded: Boolean,
@@ -172,6 +252,40 @@
     focusable: Boolean = true,
     modifier: Modifier = Modifier,
     content: @Composable ColumnScope.() -> Unit
+) = CursorDropdownMenu(
+    expanded = expanded,
+    onDismissRequest = onDismissRequest,
+    focusable = focusable,
+    modifier = modifier,
+    scrollState = rememberScrollState(),
+    content = content
+)
+
+/**
+ *
+ * A [CursorDropdownMenu] behaves similarly to [Popup] and will use the current position of the mouse
+ * cursor to position itself on screen.
+ *
+ * The [content] of a [CursorDropdownMenu] will typically be [DropdownMenuItem]s, as well as custom
+ * content. Using [DropdownMenuItem]s will result in a menu that matches the Material
+ * specification for menus.
+ *
+ * @param expanded Whether the menu is currently open and visible to the user
+ * @param onDismissRequest Called when the user requests to dismiss the menu, such as by
+ * tapping outside the menu's bounds
+ * @param focusable Whether the dropdown can capture focus
+ * @param modifier [Modifier] to be applied to the menu's content
+ * @param scrollState a [ScrollState] to used by the menu's content for items vertical scrolling
+ * @param content the content of this dropdown menu, typically a [DropdownMenuItem]
+ */
+@Composable
+fun CursorDropdownMenu(
+    expanded: Boolean,
+    onDismissRequest: () -> Unit,
+    focusable: Boolean = true,
+    modifier: Modifier = Modifier,
+    scrollState: ScrollState = rememberScrollState(),
+    content: @Composable ColumnScope.() -> Unit
 ) {
     val expandedStates = remember { MutableTransitionState(false) }
     expandedStates.targetState = expanded
@@ -188,6 +302,7 @@
                 expandedStates = expandedStates,
                 transformOriginState = transformOriginState,
                 modifier = modifier,
+                scrollState = scrollState,
                 content = content
             )
         }
diff --git a/compose/material3/material3-adaptive/build.gradle b/compose/material3/material3-adaptive/build.gradle
index c263c79..b1aaced 100644
--- a/compose/material3/material3-adaptive/build.gradle
+++ b/compose/material3/material3-adaptive/build.gradle
@@ -17,7 +17,7 @@
 import androidx.build.AndroidXComposePlugin
 import androidx.build.LibraryType
 import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.KmpPlatformsKt
 
 plugins {
     id("AndroidXPlugin")
@@ -25,49 +25,61 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the analogous update in the
-         * corresponding block below
-         */
-        implementation(libs.kotlinStdlibCommon)
-
-        api("androidx.annotation:annotation:1.1.0")
-
-        api(project(":compose:foundation:foundation"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:foundation:foundation"))
             }
+        }
 
-            androidMain.dependencies {
-                api("androidx.annotation:annotation:1.1.0")
+        commonTest {
+            dependencies {
             }
+        }
 
-            desktopMain.dependencies {
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
                 implementation(libs.kotlinStdlib)
             }
         }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                }
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
+
+        androidTest {
+            dependsOn(jvmTest)
+        }
     }
 }
 
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index 9eb3383..495dd73 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
+import androidx.build.KmpPlatformsKt
 import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,69 +25,73 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-        implementation(libs.kotlinStdlibCommon)
-        api("androidx.compose.runtime:runtime:1.2.1")
-        api("androidx.compose.ui:ui:1.2.1")
-        api("androidx.compose.ui:ui-unit:1.2.1")
-        implementation("androidx.window:window:1.0.0")
-
-        testImplementation(libs.kotlinTest)
-        testImplementation(libs.truth)
-
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(project(":compose:foundation:foundation"))
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-
-        samples(project(":compose:material3:material3-window-size-class:material3-window-size-class-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    kotlin {
-        android()
-        jvm("desktop")
-
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:runtime:runtime"))
                 api(project(":compose:ui:ui"))
                 api(project(":compose:ui:ui-unit"))
             }
+        }
 
-            jvmMain.dependencies {
+        commonTest {
+            dependencies {
+
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
                 implementation(libs.kotlinStdlib)
             }
+        }
 
-            androidMain.dependencies {
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                    // Because dependencies are pinned in the android/common code.
+                    api(project(":compose:runtime:runtime"))
+                    api(project(":compose:ui:ui"))
+                    api(project(":compose:ui:ui-unit"))
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 implementation("androidx.window:window:1.0.0")
             }
+        }
 
-            androidMain.dependsOn(jvmMain)
-            desktopMain.dependsOn(jvmMain)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
 
-            androidTest.dependencies {
-                implementation(libs.kotlinTest)
-                implementation(libs.truth)
+                }
             }
+        }
 
-            androidAndroidTest.dependencies {
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:test-utils"))
                 implementation(project(":compose:foundation:foundation"))
                 implementation(libs.testRules)
@@ -96,9 +100,24 @@
                 implementation(libs.truth)
             }
         }
-    }
-    dependencies {
-        samples(project(":compose:material3:material3-window-size-class:material3-window-size-class-samples"))
+
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.kotlinTest)
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+
+                }
+            }
+        }
     }
 }
 
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerScreenshotTest.kt
index 7374b08..1f62b814 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DividerScreenshotTest.kt
@@ -31,6 +31,7 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Assume.assumeFalse
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,6 +65,8 @@
 
     @Test
     fun darkTheme() {
+        assumeFalse("See b/272301182", Build.VERSION.SDK_INT == 33)
+
         composeTestRule.setMaterialContent(darkColorScheme()) {
             Column(Modifier.testTag(Tag)) {
                 Spacer(Modifier.size(10.dp))
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/MenuTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/MenuTest.kt
index 0df58c9..26f91f1 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/MenuTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/MenuTest.kt
@@ -146,7 +146,6 @@
         }
     }
 
-    @OptIn(ExperimentalMaterial3Api::class)
     @Test
     fun menu_scrolledContent() {
         rule.setContent {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidMenu.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidMenu.android.kt
index 0acd46a..5948252 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidMenu.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/AndroidMenu.android.kt
@@ -75,7 +75,6 @@
  * @param content the content of this dropdown menu, typically a [DropdownMenuItem]
  */
 @OptIn(ExperimentalMaterial3Api::class)
-@Suppress("ModifierParameter")
 @Deprecated(
     level = DeprecationLevel.HIDDEN,
     replaceWith = ReplaceWith(
@@ -147,7 +146,6 @@
  * @param properties [PopupProperties] for further customization of this popup's behavior
  * @param content the content of this dropdown menu, typically a [DropdownMenuItem]
  */
-@Suppress("ModifierParameter")
 @Composable
 fun DropdownMenu(
     expanded: Boolean,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
index 318985d..4b558c6 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Menu.kt
@@ -61,7 +61,6 @@
 import kotlin.math.max
 import kotlin.math.min
 
-@Suppress("ModifierParameter")
 @Composable
 internal fun DropdownMenuContent(
     expandedStates: MutableTransitionState<Boolean>,
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
new file mode 100644
index 0000000..70fd1cd
--- /dev/null
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetector.kt
@@ -0,0 +1,253 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.runtime.lint
+
+import androidx.compose.lint.Names
+import androidx.compose.lint.isInPackageName
+import androidx.compose.lint.isVoidOrUnit
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.android.tools.lint.detector.api.UastLintUtils.Companion.tryResolveUDeclaration
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiParameter
+import java.util.EnumSet
+import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UDeclarationsExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UExpressionList
+import org.jetbrains.uast.UParameter
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.UVariable
+import org.jetbrains.uast.skipParenthesizedExprDown
+import org.jetbrains.uast.toUElement
+
+/**
+ * Detector to warn when [Unit] is being passed "opaquely" as an argument to any of the methods in
+ * [getApplicableMethodNames]. An argument is defined as an opaque unit key if all the following
+ * are true:
+ *  - The argument is an expression of type `Unit`
+ *  - The argument is being passed to a parameter of type `Any?`
+ *  - The argument is not the `Unit` literal
+ *  - The argument is not a trivial variable or property read expression
+ */
+class OpaqueUnitKeyDetector : Detector(), SourceCodeScanner {
+
+    override fun getApplicableMethodNames(): List<String> = listOf(
+        Names.Runtime.Remember.shortName,
+        Names.Runtime.RememberSaveable.shortName,
+        Names.Runtime.DisposableEffect.shortName,
+        Names.Runtime.LaunchedEffect.shortName,
+        Names.Runtime.ProduceState.shortName,
+        Names.Runtime.ReusableContent.shortName,
+        Names.Runtime.Key.shortName,
+    )
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (!method.isInPackageName(Names.Runtime.PackageName)) return
+
+        method.parameterList.parameters.forEach { parameter ->
+            val arg = node.getArgumentForParameter(parameter.parameterIndex())
+            if (parameter.isNullableAny()) {
+                if (arg?.isOpaqueUnitExpression() == true) {
+                    reportOpaqueUnitArgKey(
+                        context = context,
+                        method = method,
+                        methodInvocation = node,
+                        parameter = parameter,
+                        argument = arg
+                    )
+                }
+            } else if (parameter.isPotentiallyVarArgs() && arg is UExpressionList) {
+                arg.expressions.forEach { varArg ->
+                    if (varArg.isOpaqueUnitExpression()) {
+                        reportOpaqueUnitArgKey(
+                            context = context,
+                            method = method,
+                            methodInvocation = node,
+                            parameter = parameter,
+                            argument = varArg
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    private fun reportOpaqueUnitArgKey(
+        context: JavaContext,
+        method: PsiMethod,
+        methodInvocation: UCallExpression,
+        parameter: PsiParameter,
+        argument: UExpression
+    ) {
+        val rootExpression = methodInvocation.resolveRootExpression()
+        val rootExpressionLocation = context.getLocation(rootExpression)
+
+        context.report(
+            OpaqueUnitKey,
+            argument,
+            context.getLocation(argument),
+            "Implicitly passing `Unit` as argument to ${parameter.name}",
+            fix()
+                .name(
+                    "Move expression outside of `${method.name}`'s arguments " +
+                        "and pass `Unit` explicitly"
+                )
+                .composite(
+                    if (rootExpression.isInPhysicalBlock()) {
+                        // If we're in a block where we can add an expression without breaking any
+                        // syntax rules, promote the argument's expression to a sibling.
+                        fix()
+                            .replace()
+                            .range(rootExpressionLocation)
+                            .beginning()
+                            .with("${argument.asSourceString()}\n")
+                            .reformat(true)
+                            .build()
+                    } else {
+                        // If we're not in a block, then introduce one for cheap by wrapping the
+                        // call with Kotlin's `run` function to a format that appears as follows:
+                        //
+                        // ```
+                        // run {
+                        //    theArgument()
+                        //    theMethod(...)
+                        // }
+                        // ```
+                        fix()
+                            .composite(
+                                fix()
+                                    .replace()
+                                    .range(rootExpressionLocation)
+                                    .beginning()
+                                    .with("kotlin.run {\n${argument.asSourceString()}\n")
+                                    .reformat(true)
+                                    .shortenNames()
+                                    .build(),
+                                fix()
+                                    .replace()
+                                    .range(rootExpressionLocation)
+                                    .end()
+                                    .with("\n}")
+                                    .reformat(true)
+                                    .build()
+                            )
+                    },
+
+                    // Replace the old parameter with the Unit literal
+                    fix()
+                        .replace()
+                        .range(context.getLocation(argument))
+                        .with(FqUnitName)
+                        .shortenNames()
+                        .build(),
+                )
+        )
+    }
+
+    private fun UCallExpression.resolveRootExpression(): UExpression {
+        var root: UExpression = this
+        var parent: UExpression? = root.getParentExpression()
+        while (parent != null && parent !is UBlockExpression) {
+            if (!parent.isVirtual) { root = parent }
+            parent = parent.getParentExpression()
+        }
+        return root
+    }
+
+    private fun UExpression.isInPhysicalBlock(): Boolean {
+        var parent: UElement? = this
+        while (parent != null) {
+            if (parent is UBlockExpression) {
+                return !parent.isVirtual
+            }
+            parent = parent.uastParent
+        }
+        return false
+    }
+
+    private val UElement.isVirtual get() = sourcePsi == null
+
+    private fun UExpression.getParentExpression(): UExpression? {
+        return when (val parent = uastParent) {
+            is UVariable -> parent.uastParent as UDeclarationsExpression
+            is UExpression -> parent
+            else -> null
+        }
+    }
+
+    private fun PsiParameter.isNullableAny(): Boolean {
+        val element = toUElement() as UParameter
+        return element.type.canonicalText == FqJavaObjectName &&
+            element.getAnnotations().any { it.qualifiedName == FqKotlinNullableAnnotation }
+    }
+
+    private fun PsiParameter.isPotentiallyVarArgs(): Boolean {
+        return type.canonicalText == "$FqJavaObjectName[]"
+    }
+
+    private fun UExpression.isOpaqueUnitExpression(): Boolean {
+        return getExpressionType().isVoidOrUnit && !isUnitLiteral()
+    }
+
+    private fun UExpression.isUnitLiteral(): Boolean {
+        val expr = skipParenthesizedExprDown() ?: this
+        if (expr !is USimpleNameReferenceExpression) return false
+
+        return (expr.tryResolveUDeclaration() as? UClass)?.qualifiedName == FqUnitName
+    }
+
+    companion object {
+        private const val FqJavaObjectName = "java.lang.Object"
+        private const val FqUnitName = "kotlin.Unit"
+        private const val FqKotlinNullableAnnotation = "org.jetbrains.annotations.Nullable"
+
+        val OpaqueUnitKey = Issue.create(
+            "OpaqueUnitKey",
+            "Passing an expression which always returns `Unit` as a key argument",
+            "Certain Compose functions including `remember`, `LaunchedEffect`, and " +
+                "`DisposableEffect` declare (and sometimes require) one or more key parameters. " +
+                "When a key parameter changes, it is a signal that the previous invocation is " +
+                "now invalid. In certain cases, it may be required to pass `Unit` as a key to " +
+                "one of these functions, indicating that the invocation never becomes invalid. " +
+                "Using `Unit` as a key should be done infrequently, and should always be done " +
+                "explicitly by passing the `Unit` literal. This inspection checks for " +
+                "invocations where `Unit` is being passed as a key argument in any form other " +
+                "than the `Unit` literal. This is usually done by mistake, and can harm " +
+                "readability. If a Unit expression is being passed as a key, it is always " +
+                "equivalent to move the expression before the function invocation and pass the " +
+                "`Unit` literal instead.",
+            Category.CORRECTNESS, 3, Severity.WARNING,
+            Implementation(
+                OpaqueUnitKeyDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
index bc749d0..8a2444e 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
@@ -41,6 +41,7 @@
         MutableCollectionMutableStateDetector.MutableCollectionMutableState,
         ProduceStateDetector.ProduceStateDoesNotAssignValue,
         RememberDetector.RememberReturnType,
+        OpaqueUnitKeyDetector.OpaqueUnitKey,
         UnrememberedStateDetector.UnrememberedState
     )
     override val vendor = Vendor(
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetectorTest.kt
new file mode 100644
index 0000000..597189a
--- /dev/null
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/OpaqueUnitKeyDetectorTest.kt
@@ -0,0 +1,1744 @@
+/*
+ * 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.compose.runtime.lint
+
+import androidx.compose.lint.test.Stubs
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+class OpaqueUnitKeyDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = OpaqueUnitKeyDetector()
+
+    override fun getIssues() = listOf(OpaqueUnitKeyDetector.OpaqueUnitKey)
+
+    // region remember test cases
+
+    @Test
+    fun remember_withUnitLiteralKey_doesNotError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x = remember(Unit) { listOf(1, 2, 3) }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun remember_withUnitPropertyRead_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    val unitProperty = Unit
+
+                    @Composable
+                    fun Test() {
+                        val x = remember(unitProperty) { listOf(1, 2, 3) }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:10: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        val x = remember(unitProperty) { listOf(1, 2, 3) }
+                                         ~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 10: Move expression outside of `remember`'s arguments and pass `Unit` explicitly:
+@@ -10 +10
+-                         val x = remember(unitProperty) { listOf(1, 2, 3) }
++                         unitProperty
++ val x = remember(kotlin.Unit) { listOf(1, 2, 3) }
+                """
+            )
+    }
+
+    @Test
+    fun remember_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x = remember(produceUnit()) { listOf(1, 2, 3) }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        val x = remember(produceUnit()) { listOf(1, 2, 3) }
+                                         ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `remember`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x = remember(produceUnit()) { listOf(1, 2, 3) }
++                         produceUnit()
++ val x = remember(kotlin.Unit) { listOf(1, 2, 3) }
+                """
+            )
+    }
+
+    @Test
+    fun remember_withUnitComposableInvocation_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x = remember(AnotherComposable()) { listOf(1, 2, 3) }
+                    }
+
+                    @Composable
+                    fun AnotherComposable() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        val x = remember(AnotherComposable()) { listOf(1, 2, 3) }
+                                         ~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `remember`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x = remember(AnotherComposable()) { listOf(1, 2, 3) }
++                         AnotherComposable()
++ val x = remember(kotlin.Unit) { listOf(1, 2, 3) }
+                """
+            )
+    }
+
+    @Test
+    fun remember_withUnitComposableInvocation_reportsError_withFixInSingleExpressionFun() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun test() = remember(produceUnit()) { listOf(1, 2, 3) }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                    fun test() = remember(produceUnit()) { listOf(1, 2, 3) }
+                                          ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 7: Move expression outside of `remember`'s arguments and pass `Unit` explicitly:
+@@ -7 +7
+-                     fun test() = remember(produceUnit()) { listOf(1, 2, 3) }
++                     fun test() = kotlin.run {
++ produceUnit()
++ remember(kotlin.Unit) { listOf(1, 2, 3) }
++ }
+                """
+            )
+    }
+
+    @Test
+    fun remember_withIfStatementThatReturnsUnit_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        val x = remember(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                doSomethingElse()
+                            }
+                        ) { listOf(1, 2, 3) }
+                    }
+
+                    fun doSomething() {}
+                    fun doSomethingElse() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:9: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                            if (condition) {
+                            ^
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `remember`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x = remember(
+-                             if (condition) {
+-                                 doSomething()
+-                             } else {
+-                                 doSomethingElse()
+-                             }
++                         if (condition) {
++     doSomething()
++ }else {
++     doSomethingElse()
++ }
++ val x = remember(
++                             kotlin.Unit
+                """
+            )
+    }
+
+    @Test
+    fun remember_withIfStatementCoercedToAny_doesNotReportError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        val x = remember(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                42
+                            }
+                        ) { listOf(1, 2, 3) }
+                    }
+
+                    fun doSomething() {}
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun remember_twoKeys_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x = remember(42, produceUnit()) { listOf(1, 2, 3) }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key2 [OpaqueUnitKey]
+                        val x = remember(42, produceUnit()) { listOf(1, 2, 3) }
+                                             ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `remember`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x = remember(42, produceUnit()) { listOf(1, 2, 3) }
++                         produceUnit()
++ val x = remember(42, kotlin.Unit) { listOf(1, 2, 3) }
+                """
+            )
+    }
+
+    // endregion remember test cases
+
+    // region produceState test cases
+
+    @Test
+    fun produceState_withUnitLiteralKey_doesNotError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x by produceState("123", Unit) { /* Do nothing. */ }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun produceState_withUnitPropertyRead_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    val unitProperty = Unit
+
+                    @Composable
+                    fun Test() {
+                        val x by produceState("123", unitProperty) { /* Do nothing. */ }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:10: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        val x by produceState("123", unitProperty) { /* Do nothing. */ }
+                                                     ~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 10: Move expression outside of `produceState`'s arguments and pass `Unit` explicitly:
+@@ -10 +10
+-                         val x by produceState("123", unitProperty) { /* Do nothing. */ }
++                         unitProperty
++ val x by produceState("123", kotlin.Unit) { /* Do nothing. */ }
+                """
+            )
+    }
+
+    @Test
+    fun produceState_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x by produceState("123", produceUnit()) { /* Do nothing. */ }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        val x by produceState("123", produceUnit()) { /* Do nothing. */ }
+                                                     ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `produceState`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x by produceState("123", produceUnit()) { /* Do nothing. */ }
++                         produceUnit()
++ val x by produceState("123", kotlin.Unit) { /* Do nothing. */ }
+                """
+            )
+    }
+
+    @Test
+    fun produceState_withUnitComposableInvocation_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x by produceState("123", AnotherComposable()) { /* Do nothing. */ }
+                    }
+
+                    @Composable
+                    fun AnotherComposable() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        val x by produceState("123", AnotherComposable()) { /* Do nothing. */ }
+                                                     ~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `produceState`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x by produceState("123", AnotherComposable()) { /* Do nothing. */ }
++                         AnotherComposable()
++ val x by produceState("123", kotlin.Unit) { /* Do nothing. */ }
+                """
+            )
+    }
+
+    @Test
+    fun produceState_withUnitComposableInvocation_reportsError_withFixInSingleExpressionFun() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun test() = produceState("123", produceUnit()) { /* Do nothing. */ }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                    fun test() = produceState("123", produceUnit()) { /* Do nothing. */ }
+                                                     ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 7: Move expression outside of `produceState`'s arguments and pass `Unit` explicitly:
+@@ -7 +7
+-                     fun test() = produceState("123", produceUnit()) { /* Do nothing. */ }
++                     fun test() = kotlin.run {
++ produceUnit()
++ produceState("123", kotlin.Unit) { /* Do nothing. */ }
++ }
+                """
+            )
+    }
+
+    @Test
+    fun produceState_withIfStatementThatReturnsUnit_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        val x by produceState(
+                            initialValue = "123",
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                doSomethingElse()
+                            }
+                        ) { /* Do nothing. */ }
+                    }
+
+                    fun doSomething() {}
+                    fun doSomethingElse() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:10: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                            if (condition) {
+                            ^
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 10: Move expression outside of `produceState`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x by produceState(
++                         if (condition) {
++     doSomething()
++ }else {
++     doSomethingElse()
++ }
++ val x by produceState(
+-                             if (condition) {
+-                                 doSomething()
+-                             } else {
+-                                 doSomethingElse()
+-                             }
++                             kotlin.Unit
+                """
+            )
+    }
+
+    @Test
+    fun produceState_withIfStatementCoercedToAny_doesNotReportError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        val x by produceState(
+                            initialValue = "123",
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                42
+                            }
+                        ) { /* Do nothing */ }
+                    }
+
+                    fun doSomething() {}
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun produceState_twoKeys_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.SnapshotState,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        val x by produceState("123", produceUnit()) { /* Do nothing */ }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        val x by produceState("123", produceUnit()) { /* Do nothing */ }
+                                                     ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `produceState`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         val x by produceState("123", produceUnit()) { /* Do nothing */ }
++                         produceUnit()
++ val x by produceState("123", kotlin.Unit) { /* Do nothing */ }
+                """
+            )
+    }
+
+    // endregion produceState test cases
+
+    // region DisposableEffect test cases
+
+    @Test
+    fun disposableEffect_withUnitLiteralKey_doesNotError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        DisposableEffect(Unit) {
+                            onDispose {
+                                // Do nothing.
+                            }
+                        }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun disposableEffect_withUnitPropertyRead_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    val unitProperty = Unit
+
+                    @Composable
+                    fun Test() {
+                        DisposableEffect(unitProperty) {
+                            onDispose {
+                                // Do nothing.
+                            }
+                        }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:10: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        DisposableEffect(unitProperty) {
+                                         ~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 10: Move expression outside of `DisposableEffect`'s arguments and pass `Unit` explicitly:
+@@ -10 +10
+-                         DisposableEffect(unitProperty) {
++                         unitProperty
++ DisposableEffect(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun disposableEffect_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        DisposableEffect(produceUnit()) {
+                            onDispose {
+                                // Do nothing.
+                            }
+                        }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        DisposableEffect(produceUnit()) {
+                                         ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `DisposableEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         DisposableEffect(produceUnit()) {
++                         produceUnit()
++ DisposableEffect(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun disposableEffect_withUnitComposableInvocation_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        DisposableEffect(AnotherComposable()) {
+                            onDispose {
+                                // Do nothing.
+                            }
+                        }
+                    }
+
+                    @Composable
+                    fun AnotherComposable() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        DisposableEffect(AnotherComposable()) {
+                                         ~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `DisposableEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         DisposableEffect(AnotherComposable()) {
++                         AnotherComposable()
++ DisposableEffect(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun disposableEffect_withUnitComposableInvocation_reportsError_withFixInSingleExpressionFun() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun test() = DisposableEffect(produceUnit()) {
+                        onDispose {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                    fun test() = DisposableEffect(produceUnit()) {
+                                                  ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 7: Move expression outside of `DisposableEffect`'s arguments and pass `Unit` explicitly:
+@@ -7 +7
+-                     fun test() = DisposableEffect(produceUnit()) {
++                     fun test() = kotlin.run {
++ produceUnit()
++ DisposableEffect(kotlin.Unit) {
+@@ -12 +14
++ }
+                """
+            )
+    }
+
+    @Test
+    fun disposableEffect_withIfStatementThatReturnsUnit_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        DisposableEffect(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                doSomethingElse()
+                            }
+                        ) {
+                            onDispose {
+                                // Do nothing.
+                            }
+                        }
+                    }
+
+                    fun doSomething() {}
+                    fun doSomethingElse() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:9: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                            if (condition) {
+                            ^
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 9: Move expression outside of `DisposableEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         DisposableEffect(
+-                             if (condition) {
+-                                 doSomething()
+-                             } else {
+-                                 doSomethingElse()
+-                             }
++                         if (condition) {
++     doSomething()
++ }else {
++     doSomethingElse()
++ }
++ DisposableEffect(
++                             kotlin.Unit
+                """
+            )
+    }
+
+    @Test
+    fun disposableEffect_withIfStatementCoercedToAny_doesNotReportError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        DisposableEffect(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                42
+                            }
+                        ) {
+                            onDispose {
+                                // Do nothing.
+                            }
+                        }
+                    }
+
+                    fun doSomething() {}
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun disposableEffect_twoKeys_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        DisposableEffect(42, produceUnit()) {
+                            onDispose {
+                                // Do nothing.
+                            }
+                        }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key2 [OpaqueUnitKey]
+                        DisposableEffect(42, produceUnit()) {
+                                             ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `DisposableEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         DisposableEffect(42, produceUnit()) {
++                         produceUnit()
++ DisposableEffect(42, kotlin.Unit) {
+                """
+            )
+    }
+
+    // endregion DisposableEffect test cases
+
+    // region LaunchedEffect test cases
+
+    @Test
+    fun launchedEffect_withUnitLiteralKey_doesNotError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        LaunchedEffect(Unit) {
+                            // Do nothing.
+                        }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun launchedEffect_withUnitPropertyRead_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    val unitProperty = Unit
+
+                    @Composable
+                    fun Test() {
+                        LaunchedEffect(unitProperty) {
+                            // Do nothing.
+                        }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:10: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        LaunchedEffect(unitProperty) {
+                                       ~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 10: Move expression outside of `LaunchedEffect`'s arguments and pass `Unit` explicitly:
+@@ -10 +10
+-                         LaunchedEffect(unitProperty) {
++                         unitProperty
++ LaunchedEffect(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun launchedEffect_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        LaunchedEffect(produceUnit()) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        LaunchedEffect(produceUnit()) {
+                                       ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `LaunchedEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         LaunchedEffect(produceUnit()) {
++                         produceUnit()
++ LaunchedEffect(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun launchedEffect_withUnitComposableInvocation_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        LaunchedEffect(AnotherComposable()) {
+                            // Do nothing.
+                        }
+                    }
+
+                    @Composable
+                    fun AnotherComposable() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                        LaunchedEffect(AnotherComposable()) {
+                                       ~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `LaunchedEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         LaunchedEffect(AnotherComposable()) {
++                         AnotherComposable()
++ LaunchedEffect(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun launchedEffect_withUnitComposableInvocation_reportsError_withFixInSingleExpressionFun() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun test() = LaunchedEffect(produceUnit()) {
+                        // Do nothing.
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                    fun test() = LaunchedEffect(produceUnit()) {
+                                                ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 7: Move expression outside of `LaunchedEffect`'s arguments and pass `Unit` explicitly:
+@@ -7 +7
+-                     fun test() = LaunchedEffect(produceUnit()) {
++                     fun test() = kotlin.run {
++ produceUnit()
++ LaunchedEffect(kotlin.Unit) {
+@@ -10 +12
++ }
+                """
+            )
+    }
+
+    @Test
+    fun launchedEffect_withIfStatementThatReturnsUnit_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        LaunchedEffect(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                doSomethingElse()
+                            }
+                        ) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun doSomething() {}
+                    fun doSomethingElse() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:9: Warning: Implicitly passing Unit as argument to key1 [OpaqueUnitKey]
+                            if (condition) {
+                            ^
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 9: Move expression outside of `LaunchedEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         LaunchedEffect(
+-                             if (condition) {
+-                                 doSomething()
+-                             } else {
+-                                 doSomethingElse()
+-                             }
++                         if (condition) {
++     doSomething()
++ }else {
++     doSomethingElse()
++ }
++ LaunchedEffect(
++                             kotlin.Unit
+                """
+            )
+    }
+
+    @Test
+    fun launchedEffect_withIfStatementCoercedToAny_doesNotReportError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        LaunchedEffect(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                42
+                            }
+                        ) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun doSomething() {}
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun launchedEffect_twoKeys_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Effects,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        LaunchedEffect(42, produceUnit()) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to key2 [OpaqueUnitKey]
+                        LaunchedEffect(42, produceUnit()) {
+                                           ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `LaunchedEffect`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         LaunchedEffect(42, produceUnit()) {
++                         produceUnit()
++ LaunchedEffect(42, kotlin.Unit) {
+                """
+            )
+    }
+
+    // endregion LaunchedEffect test cases
+
+    // region key() test cases
+
+    @Test
+    fun key_withUnitLiteralKey_doesNotError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        key(Unit) {
+                            // Do nothing.
+                        }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun key_withUnitPropertyRead_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    val unitProperty = Unit
+
+                    @Composable
+                    fun Test() {
+                        key(unitProperty) {
+                            // Do nothing.
+                        }
+                    }
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:10: Warning: Implicitly passing Unit as argument to keys [OpaqueUnitKey]
+                        key(unitProperty) {
+                            ~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 10: Move expression outside of `key`'s arguments and pass `Unit` explicitly:
+@@ -10 +10
+-                         key(unitProperty) {
++                         unitProperty
++ key(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun key_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        key(produceUnit()) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to keys [OpaqueUnitKey]
+                        key(produceUnit()) {
+                            ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `key`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         key(produceUnit()) {
++                         produceUnit()
++ key(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun key_withUnitComposableInvocation_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        key(AnotherComposable()) {
+                            // Do nothing.
+                        }
+                    }
+
+                    @Composable
+                    fun AnotherComposable() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to keys [OpaqueUnitKey]
+                        key(AnotherComposable()) {
+                            ~~~~~~~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `key`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         key(AnotherComposable()) {
++                         AnotherComposable()
++ key(kotlin.Unit) {
+                """
+            )
+    }
+
+    @Test
+    fun key_withUnitComposableInvocation_reportsError_withFixInSingleExpressionFun() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun test() = key(produceUnit()) {
+                        // Do nothing.
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Warning: Implicitly passing Unit as argument to keys [OpaqueUnitKey]
+                    fun test() = key(produceUnit()) {
+                                     ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 7: Move expression outside of `key`'s arguments and pass `Unit` explicitly:
+@@ -7 +7
+-                     fun test() = key(produceUnit()) {
++                     fun test() = kotlin.run {
++ produceUnit()
++ key(kotlin.Unit) {
+@@ -10 +12
++ }
+                """
+            )
+    }
+
+    @Test
+    fun key_withIfStatementThatReturnsUnit_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        key(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                doSomethingElse()
+                            }
+                        ) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun doSomething() {}
+                    fun doSomethingElse() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:9: Warning: Implicitly passing Unit as argument to keys [OpaqueUnitKey]
+                            if (condition) {
+                            ^
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 9: Move expression outside of `key`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         key(
+-                             if (condition) {
+-                                 doSomething()
+-                             } else {
+-                                 doSomethingElse()
+-                             }
++                         if (condition) {
++     doSomething()
++ }else {
++     doSomethingElse()
++ }
++ key(
++                             kotlin.Unit
+                """
+            )
+    }
+
+    @Test
+    fun key_withIfStatementCoercedToAny_doesNotReportError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test(condition: Boolean) {
+                        key(
+                            if (condition) {
+                                doSomething()
+                            } else {
+                                42
+                            }
+                        ) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun doSomething() {}
+                    """
+                )
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun key_twoKeys_withUnitFunctionCall_reportsError() {
+        lint()
+            .files(
+                Stubs.Remember,
+                Stubs.Composable,
+                Stubs.Composables,
+                kotlin(
+                    """
+                    package test
+
+                    import androidx.compose.runtime.*
+
+                    @Composable
+                    fun Test() {
+                        key(42, produceUnit()) {
+                            // Do nothing.
+                        }
+                    }
+
+                    fun produceUnit() {}
+                    """
+                )
+            )
+            .run()
+            .expect(
+                """
+src/test/test.kt:8: Warning: Implicitly passing Unit as argument to keys [OpaqueUnitKey]
+                        key(42, produceUnit()) {
+                                ~~~~~~~~~~~~~
+0 errors, 1 warnings
+                """
+            )
+            .expectFixDiffs(
+                """
+Fix for src/test/test.kt line 8: Move expression outside of `key`'s arguments and pass `Unit` explicitly:
+@@ -8 +8
+-                         key(42, produceUnit()) {
++                         produceUnit()
++ key(42, kotlin.Unit) {
+                """
+            )
+    }
+
+    // endregion key() test cases
+}
+/* ktlint-enable max-line-length */
\ No newline at end of file
diff --git a/compose/runtime/runtime-saveable/build.gradle b/compose/runtime/runtime-saveable/build.gradle
index b4d8073..d23376d 100644
--- a/compose/runtime/runtime-saveable/build.gradle
+++ b/compose/runtime/runtime-saveable/build.gradle
@@ -15,9 +15,8 @@
  */
 
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,77 +24,54 @@
     id("com.android.library")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /* When updating dependencies, make sure to make the an an analogous update in the
-            corresponding block below */
-        api project(":compose:runtime:runtime")
-        api "androidx.annotation:annotation:1.1.0"
-
-        implementation(libs.kotlinStdlib)
-
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-        testImplementation(libs.testCore)
-        testImplementation(libs.testRules)
-
-        androidTestImplementation projectOrArtifact(':compose:ui:ui')
-        androidTestImplementation projectOrArtifact(":compose:ui:ui-test-junit4")
-        androidTestImplementation projectOrArtifact(":compose:test-utils")
-        androidTestImplementation "androidx.fragment:fragment:1.3.0"
-        androidTestImplementation projectOrArtifact(":activity:activity-compose")
-        androidTestImplementation(libs.testUiautomator)
-        androidTestImplementation(libs.testCore)
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.espressoCore)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.dexmakerMockito)
-        androidTestImplementation(libs.mockitoCore)
-
-        lintPublish(project(":compose:runtime:runtime-saveable-lint"))
-
-        samples(projectOrArtifact(":compose:runtime:runtime-saveable:runtime-saveable-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /* When updating dependencies, make sure to make the an an analogous update in the
-            corresponding block above */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
                 api project(":compose:runtime:runtime")
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependencies {
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 implementation(libs.kotlinStdlib)
                 api "androidx.annotation:annotation:1.1.0"
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.truth)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
             }
+        }
 
-            androidAndroidTest.dependencies {
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation project(':compose:ui:ui')
                 implementation project(":compose:ui:ui-test-junit4")
                 implementation project(":compose:test-utils")
@@ -112,10 +88,32 @@
                 implementation(libs.mockitoCore)
             }
         }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
     }
-    dependencies {
-        samples(projectOrArtifact(":compose:runtime:runtime-saveable:runtime-saveable-samples"))
-    }
+}
+
+dependencies {
+    samples(projectOrArtifact(":compose:runtime:runtime-saveable:runtime-saveable-samples"))
+    lintPublish(project(":compose:runtime:runtime-saveable-lint"))
 }
 
 androidx {
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 0fee604..35456b2 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -765,6 +765,7 @@
   public static final class Snapshot.Companion {
     method public androidx.compose.runtime.snapshots.Snapshot getCurrent();
     method public inline <T> T global(kotlin.jvm.functions.Function0<? extends T> block);
+    method public boolean isApplyObserverNotificationPending();
     method public void notifyObjectsInitialized();
     method public <T> T observe(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver, kotlin.jvm.functions.Function0<? extends T> block);
     method public androidx.compose.runtime.snapshots.ObserverHandle registerApplyObserver(kotlin.jvm.functions.Function2<? super java.util.Set<?>,? super androidx.compose.runtime.snapshots.Snapshot,kotlin.Unit> observer);
@@ -775,6 +776,7 @@
     method public inline <R> R withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
     method public inline <T> T withoutReadObservation(kotlin.jvm.functions.Function0<? extends T> block);
     property public final androidx.compose.runtime.snapshots.Snapshot current;
+    property public final boolean isApplyObserverNotificationPending;
   }
 
   public final class SnapshotApplyConflictException extends java.lang.Exception {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 1fa82d6..5c926bd 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -833,6 +833,7 @@
   public static final class Snapshot.Companion {
     method public androidx.compose.runtime.snapshots.Snapshot getCurrent();
     method public inline <T> T global(kotlin.jvm.functions.Function0<? extends T> block);
+    method public boolean isApplyObserverNotificationPending();
     method public void notifyObjectsInitialized();
     method public <T> T observe(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver, kotlin.jvm.functions.Function0<? extends T> block);
     method @androidx.compose.runtime.InternalComposeApi public int openSnapshotCount();
@@ -844,6 +845,7 @@
     method public inline <R> R withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
     method public inline <T> T withoutReadObservation(kotlin.jvm.functions.Function0<? extends T> block);
     property public final androidx.compose.runtime.snapshots.Snapshot current;
+    property public final boolean isApplyObserverNotificationPending;
   }
 
   public final class SnapshotApplyConflictException extends java.lang.Exception {
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 5ce4dbc..b855aea 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -806,6 +806,7 @@
     method @kotlin.PublishedApi internal androidx.compose.runtime.snapshots.Snapshot createNonObservableSnapshot();
     method public androidx.compose.runtime.snapshots.Snapshot getCurrent();
     method public inline <T> T global(kotlin.jvm.functions.Function0<? extends T> block);
+    method public boolean isApplyObserverNotificationPending();
     method public void notifyObjectsInitialized();
     method public <T> T observe(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver, kotlin.jvm.functions.Function0<? extends T> block);
     method public androidx.compose.runtime.snapshots.ObserverHandle registerApplyObserver(kotlin.jvm.functions.Function2<? super java.util.Set<?>,? super androidx.compose.runtime.snapshots.Snapshot,kotlin.Unit> observer);
@@ -818,6 +819,7 @@
     method public inline <R> R withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
     method public inline <T> T withoutReadObservation(kotlin.jvm.functions.Function0<? extends T> block);
     property public final androidx.compose.runtime.snapshots.Snapshot current;
+    property public final boolean isApplyObserverNotificationPending;
   }
 
   public final class SnapshotApplyConflictException extends java.lang.Exception {
diff --git a/compose/runtime/runtime/integration-tests/build.gradle b/compose/runtime/runtime/integration-tests/build.gradle
index b9b65e5..f62e6ba 100644
--- a/compose/runtime/runtime/integration-tests/build.gradle
+++ b/compose/runtime/runtime/integration-tests/build.gradle
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
@@ -24,69 +23,63 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        androidTestImplementation(projectOrArtifact(":compose:ui:ui"))
-        androidTestImplementation(projectOrArtifact(":compose:material:material"))
-        androidTestImplementation(projectOrArtifact(":compose:ui:ui-test-junit4"))
-        androidTestImplementation(project(":compose:runtime:runtime"))
-        androidTestImplementation(projectOrArtifact(":compose:test-utils"))
-        androidTestImplementation(projectOrArtifact(":activity:activity-compose"))
-
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.kotlinTestJunit)
-        androidTestImplementation(libs.testExtJunit)
-        androidTestImplementation(libs.testCore)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.kotlinStdlib)
-        androidTestImplementation(libs.kotlinReflect)
-        androidTestImplementation(libs.truth)
-    }
-}
-
-android {
-    namespace "androidx.compose.runtime.integrationtests"
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 implementation(libs.kotlinCoroutinesCore)
                 implementation(projectOrArtifact(":compose:ui:ui"))
             }
-            jvmMain.dependencies {
+        }
+
+        commonTest {
+            dependencies {
+                implementation(kotlin("test-junit"))
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
                 implementation(libs.kotlinStdlib)
                 api(libs.kotlinCoroutinesCore)
             }
-            androidMain.dependencies {
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api(libs.kotlinCoroutinesAndroid)
                 api("androidx.annotation:annotation:1.1.0")
 
                 implementation("androidx.core:core-ktx:1.1.0")
             }
-            desktopMain.dependencies {
-                api(libs.kotlinCoroutinesSwing)
-            }
+        }
 
-            commonTest.dependencies {
-                implementation(kotlin("test-junit"))
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    api(libs.kotlinCoroutinesSwing)
+                }
             }
-            androidAndroidTest.dependencies {
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(projectOrArtifact(":compose:ui:ui"))
                 implementation(projectOrArtifact(":compose:material:material"))
                 implementation(projectOrArtifact(":compose:ui:ui-test-junit4"))
@@ -98,9 +91,23 @@
                 implementation(libs.truth)
             }
         }
+
+        androidTest {
+            dependsOn(jvmTest)
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
     }
 }
 
+android {
+    namespace "androidx.compose.runtime.integrationtests"
+}
+
 tasks.withType(KotlinCompile).configureEach {
     kotlinOptions {
         incremental = false
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 06d1f47..d5d312b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.runtime.snapshots
 
+import androidx.compose.runtime.AtomicInt
 import androidx.compose.runtime.AtomicReference
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisallowComposableCalls
@@ -280,6 +281,13 @@
         val current get() = currentSnapshot()
 
         /**
+         * Returns whether any threads are currently in the process of notifying observers about
+         * changes to the global snapshot.
+         */
+        val isApplyObserverNotificationPending: Boolean
+            get() = pendingApplyObserverCount.get() > 0
+
+        /**
          * Take a snapshot of the current value of all state objects. The values are preserved until
          * [Snapshot.dispose] is called on the result.
          *
@@ -1767,20 +1775,36 @@
     return result
 }
 
+/**
+ * Counts the number of threads currently inside `advanceGlobalSnapshot`, notifying observers of
+ * changes to the global snapshot.
+ */
+private var pendingApplyObserverCount = AtomicInt(0)
+
 private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
     var previousGlobalSnapshot = snapshotInitializer as GlobalSnapshot
+
+    var modified: IdentityArraySet<StateObject>? = null // Effectively val; can be with contracts
     val result = sync {
         previousGlobalSnapshot = currentGlobalSnapshot.get()
+        modified = previousGlobalSnapshot.modified
+        if (modified != null) {
+            pendingApplyObserverCount.add(1)
+        }
         takeNewGlobalSnapshot(previousGlobalSnapshot, block)
     }
 
     // If the previous global snapshot had any modified states then notify the registered apply
     // observers.
-    val modified = previousGlobalSnapshot.modified
-    if (modified != null) {
-        val observers: List<(Set<Any>, Snapshot) -> Unit> = sync { applyObservers.toMutableList() }
-        observers.fastForEach { observer ->
-            observer(modified, previousGlobalSnapshot)
+    modified?.let {
+        try {
+            val observers: List<(Set<Any>, Snapshot) -> Unit> =
+                sync { applyObservers.toMutableList() }
+            observers.fastForEach { observer ->
+                observer(it, previousGlobalSnapshot)
+            }
+        } finally {
+            pendingApplyObserverCount.add(-1)
         }
     }
 
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
index 8efc203..3ec3fb7 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTests.kt
@@ -269,6 +269,33 @@
     }
 
     @Test
+    fun applyObserverNotificationIsPendingWhileSendingApplyNotifications() {
+        val state = mutableStateOf(0)
+
+        var notificationsPendingWhileObserving = false
+        val unregister = Snapshot.registerApplyObserver { _, _ ->
+            notificationsPendingWhileObserving = Snapshot.isApplyObserverNotificationPending
+        }
+
+        try {
+            // Normally not pending
+            assertFalse(Snapshot.isApplyObserverNotificationPending)
+
+            state.value = 1
+
+            Snapshot.sendApplyNotifications()
+
+            // Was pending while sending apply notifications
+            assertTrue(notificationsPendingWhileObserving)
+
+            // Not pending afterwards
+            assertFalse(Snapshot.isApplyObserverNotificationPending)
+        } finally {
+            unregister.dispose()
+        }
+    }
+
+    @Test
     fun aNestedSnapshotCanBeTaken() {
         val state = mutableStateOf<Int>(0)
 
diff --git a/compose/test-utils/build.gradle b/compose/test-utils/build.gradle
index 5b5d26c..46eecc3 100644
--- a/compose/test-utils/build.gradle
+++ b/compose/test-utils/build.gradle
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
 import androidx.build.LibraryType
 import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,89 +23,93 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = false // b/276387374 TODO: KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-
-        api("androidx.activity:activity:1.2.0")
-        api(projectOrArtifact(":compose:ui:ui-test-junit4"))
-        api(project(":test:screenshot:screenshot"))
-
-        implementation(libs.kotlinStdlibCommon)
-        implementation(projectOrArtifact(":compose:runtime:runtime"))
-        implementation(projectOrArtifact(":compose:ui:ui-unit"))
-        implementation(projectOrArtifact(":compose:ui:ui-graphics"))
-        implementation("androidx.activity:activity-compose:1.3.1")
-        // old version of common-java8 conflicts with newer version, because both have
-        // DefaultLifecycleEventObserver.
-        // Outside of androidx this is resolved via constraint added to lifecycle-common,
-        // but it doesn't work in androidx.
-        // See aosp/1804059
-        implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
-        implementation(libs.testCore)
-        implementation(libs.testRules)
-
-        // This has stub APIs for access to legacy Android APIs, so we don't want
-        // any dependency on this module.
-        compileOnly(projectOrArtifact(":compose:ui:ui-android-stubs"))
-
-        testImplementation(libs.truth)
-
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(projectOrArtifact(":compose:material:material"))
-    }
-}
-
-if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 implementation(projectOrArtifact(":compose:runtime:runtime"))
                 implementation(projectOrArtifact(":compose:ui:ui-unit"))
                 implementation(projectOrArtifact(":compose:ui:ui-graphics"))
                 implementation(projectOrArtifact(":compose:ui:ui-test-junit4"))
             }
+        }
+        androidMain.dependencies {
+            api("androidx.activity:activity:1.2.0")
+            implementation "androidx.activity:activity-compose:1.3.1"
+            api(projectOrArtifact(":compose:ui:ui-test-junit4"))
+            api(project(":test:screenshot:screenshot"))
+            // This has stub APIs for access to legacy Android APIs, so we don't want
+            // any dependency on this module.
+            compileOnly(projectOrArtifact(":compose:ui:ui-android-stubs"))
+            implementation(libs.testCore)
+            implementation(libs.testRules)
+        }
 
-            androidMain.dependencies {
-                api("androidx.activity:activity:1.2.0")
-                implementation "androidx.activity:activity-compose:1.3.1"
-                api(projectOrArtifact(":compose:ui:ui-test-junit4"))
-                api(project(":test:screenshot:screenshot"))
-                // This has stub APIs for access to legacy Android APIs, so we don't want
-                // any dependency on this module.
-                compileOnly(projectOrArtifact(":compose:ui:ui-android-stubs"))
-                implementation(libs.testCore)
-                implementation(libs.testRules)
+        commonTest {
+            dependencies {
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.truth)
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
             }
+        }
 
-            androidAndroidTest.dependencies {
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.truth)
                 implementation(projectOrArtifact(":compose:material:material"))
             }
         }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                }
+            }
+        }
     }
 }
 
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index 1a05daf..15e6e16 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,53 +23,69 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    dependencies {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api("androidx.annotation:annotation:1.1.0")
-
-        implementation("androidx.compose.runtime:runtime:1.2.1")
-        implementation(project(":compose:ui:ui-util"))
-        implementation(libs.kotlinStdlib)
-
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-        testImplementation(libs.kotlinTest)
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
-                implementation(project(":compose:runtime:runtime"))
+                implementation("androidx.compose.runtime:runtime:1.2.1")
                 implementation(project(":compose:ui:ui-util"))
             }
-            jvmMain.dependencies {
+        }
+
+        commonTest {
+            dependencies {
+                implementation(kotlin("test-junit"))
+            }
+        }
+
+        jvmMain {
+            dependencies {
                 implementation(libs.kotlinStdlib)
             }
-            androidMain.dependencies {
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
             }
-            commonTest.dependencies {
-                implementation(kotlin("test-junit"))
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(project(":compose:runtime:runtime"))
+                }
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
+            }
+        }
+
+        androidTest {
+            dependsOn(jvmTest)
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
             }
         }
     }
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index ae86521..18cdc50 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -401,7 +401,7 @@
   }
 
   public fun interface ColorProducer {
-    method public long invoke();
+    method public long produce();
   }
 
   @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class FilterQuality {
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index d6fde28..75d0b80 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -401,7 +401,7 @@
   }
 
   public fun interface ColorProducer {
-    method public long invoke();
+    method public long produce();
   }
 
   @kotlin.RequiresOptIn(message="This API is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalGraphicsApi {
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index 17269b9..08b7e05 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -432,7 +432,7 @@
   }
 
   public fun interface ColorProducer {
-    method public long invoke();
+    method public long produce();
   }
 
   public final class DegreesKt {
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index 815c1df..cb9f0da 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,102 +23,72 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    dependencies {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api("androidx.annotation:annotation:1.2.0")
-        api(project(":compose:ui:ui-unit"))
-
-        implementation("androidx.compose.runtime:runtime:1.2.1")
-        implementation(project(":compose:ui:ui-util"))
-        implementation(libs.kotlinStdlibCommon)
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.kotlinTestJunit)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-
-        androidTestImplementation(project(":compose:ui:ui-graphics:ui-graphics-samples"))
-        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.espressoCore)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-
-        lintPublish(project(":compose:ui:ui-graphics-lint"))
-
-        samples(projectOrArtifact(":compose:ui:ui-graphics:ui-graphics-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
 
                 api(project(":compose:ui:ui-unit"))
                 implementation(project(":compose:runtime:runtime"))
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
 
-            androidMain.dependencies {
-                api("androidx.annotation:annotation:1.2.0")
+        commonTest {
+            dependencies {
+                implementation(kotlin("test"))
             }
+        }
 
+        if (desktopEnabled) {
             skikoMain {
                 dependsOn(commonMain)
                 dependencies {
                     api(libs.skikoCommon)
+                    implementation(project(":compose:runtime:runtime"))
                 }
             }
+        }
 
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.2.0")
+            }
+        }
+
+        if (desktopEnabled) {
             desktopMain {
-                dependsOn skikoMain
+                dependsOn(jvmMain)
+                dependsOn(skikoMain)
                 dependencies {
                     implementation(libs.kotlinStdlib)
                     implementation(libs.kotlinStdlibJdk8)
                 }
             }
+        }
 
-            commonTest {
-                dependencies {
-                    implementation(kotlin("test"))
-                }
+        jvmTest {
+            dependencies {
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.truth)
-            }
-
-            androidAndroidTest.dependencies {
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:ui:ui-graphics:ui-graphics-samples"))
                 implementation(project(":compose:ui:ui-test-junit4"))
                 implementation(project(":compose:test-utils"))
@@ -128,9 +97,26 @@
                 implementation(libs.espressoCore)
                 implementation(libs.junit)
             }
+        }
 
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
             desktopTest {
                 resources.srcDirs += "src/desktopTest/res"
+                dependsOn(jvmTest)
                 dependencies {
                     implementation(project(":compose:ui:ui-test-junit4"))
                     implementation(libs.junit)
@@ -140,9 +126,10 @@
             }
         }
     }
-    dependencies {
-        samples(projectOrArtifact(":compose:ui:ui-graphics:ui-graphics-samples"))
-    }
+}
+
+dependencies {
+    lintPublish(project(":compose:ui:ui-graphics-lint"))
 }
 
 androidx {
@@ -161,7 +148,7 @@
     namespace "androidx.compose.ui.graphics"
 }
 
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
+if (desktopEnabled) {
     tasks.findByName("desktopTest").configure {
         systemProperties["GOLDEN_PATH"] = project.rootDir.absolutePath + "/../../golden"
     }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
index ca53498..a17406a 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
@@ -668,5 +668,5 @@
     /**
      * Return the color
      */
-    fun invoke(): Color
+    fun produce(): Color
 }
\ No newline at end of file
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index 5e4501d..5db2d70 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,81 +23,42 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-android {
-    lintOptions {
-        disable("InvalidPackage")
-    }
-    namespace "androidx.compose.ui.test.junit4"
-}
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-dependencies {
-
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        api(project(":compose:ui:ui-test"))
-        api("androidx.activity:activity:1.2.1")
-        api(libs.junit)
-        api(libs.kotlinStdlib)
-        api(libs.kotlinStdlibCommon)
-        api(libs.testExtJunit)
-
-        implementation("androidx.compose.runtime:runtime-saveable:1.2.1")
-        implementation("androidx.activity:activity-compose:1.3.0")
-        implementation("androidx.annotation:annotation:1.1.0")
-        implementation("androidx.lifecycle:lifecycle-common:2.5.1")
-        implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
-        implementation("androidx.test:core:1.5.0")
-        implementation("androidx.test:monitor:1.6.0")
-        implementation("androidx.test.espresso:espresso-core:3.5.0")
-        implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
-        implementation(libs.kotlinCoroutinesCore)
-        implementation(libs.kotlinCoroutinesTest)
-
-        testImplementation(project(":compose:animation:animation-core"))
-        testImplementation(project(":compose:material:material"))
-        testImplementation(project(":compose:test-utils"))
-        testImplementation(libs.truth)
-        testImplementation(libs.robolectric)
-
-        androidTestImplementation(project(":compose:animation:animation"))
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(project(":compose:material:material"))
-        androidTestImplementation("androidx.fragment:fragment-testing:1.4.1")
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.mockitoCore)
-        androidTestImplementation(libs.dexmakerMockito)
-        androidTestImplementation(libs.mockitoKotlin)
-    }
-}
-
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 api(project(":compose:ui:ui-test"))
                 implementation(libs.kotlinStdlib)
                 implementation(libs.kotlinCoroutinesCore)
                 implementation(libs.kotlinCoroutinesTest)
             }
+        }
 
-            jvmMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
                 api(libs.junit)
                 api(libs.kotlinStdlib)
                 api(libs.kotlinStdlibCommon)
 
                 compileOnly("androidx.annotation:annotation:1.1.0")
             }
+        }
 
-            androidMain.dependencies {
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.activity:activity:1.2.1")
                 implementation "androidx.activity:activity-compose:1.3.0"
                 api(libs.testExtJunit)
@@ -107,24 +67,31 @@
                 implementation(project(":compose:runtime:runtime-saveable"))
                 implementation("androidx.lifecycle:lifecycle-common:2.5.1")
                 implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
-                implementation("androidx.test:core:1.4.0")
+                implementation("androidx.test:core:1.5.0")
                 implementation(libs.testMonitor)
                 implementation("androidx.test.espresso:espresso-core:3.3.0")
-                implementation("androidx.test.espresso:espresso-idling-resource:3.3.0")
+                implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(project(":compose:animation:animation-core"))
-                implementation(project(":compose:material:material"))
-                implementation(project(":compose:test-utils"))
-                implementation(libs.truth)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.truth)
+                    implementation(libs.skiko)
+                }
             }
+        }
 
-            androidAndroidTest.dependencies {
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:animation:animation"))
                 implementation(project(":compose:test-utils"))
                 implementation(project(":compose:material:material"))
@@ -136,28 +103,50 @@
                 implementation(libs.dexmakerMockito)
                 implementation(libs.mockitoKotlin)
             }
+        }
 
-            desktopMain.dependencies {
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(project(":compose:animation:animation-core"))
+                implementation(project(":compose:material:material"))
+                implementation(project(":compose:test-utils"))
                 implementation(libs.truth)
-                implementation(libs.skiko)
             }
+        }
 
-            desktopTest.dependencies {
-                implementation(libs.truth)
-                implementation(libs.junit)
-                implementation(libs.kotlinTest)
-                implementation(libs.skikoCurrentOs)
-                implementation(project(":compose:foundation:foundation"))
-                implementation(project(":compose:ui:ui-test-junit4"))
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependencies {
+                    implementation(libs.truth)
+                    implementation(libs.junit)
+                    implementation(libs.kotlinTest)
+                    implementation(libs.skikoCurrentOs)
+                    implementation(project(":compose:foundation:foundation"))
+                    implementation(project(":compose:ui:ui-test-junit4"))
+                }
             }
         }
     }
+}
 
-    dependencies {
-        // Can't declare this in kotlin { sourceSets { androidTest.dependencies { .. } } } as that
-        // leaks into instrumented tests (b/214407011)
-        testImplementation(libs.robolectric)
+
+android {
+    lintOptions {
+        disable("InvalidPackage")
     }
+    namespace "androidx.compose.ui.test.junit4"
+}
+
+dependencies {
+    // Can't declare this in kotlin { sourceSets { androidTest.dependencies { .. } } } as that
+    // leaks into instrumented tests (b/214407011)
+    testImplementation(libs.robolectric)
 }
 
 androidx {
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
index 8af350a..6977ddf 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
+++ b/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
@@ -182,6 +182,10 @@
         }
     }
 
+    /**
+     * @param matcher
+     * @param useUnmergedTree
+     */
     override fun onNode(
         matcher: SemanticsMatcher,
         useUnmergedTree: Boolean
@@ -189,6 +193,10 @@
         return SemanticsNodeInteraction(testContext, useUnmergedTree, matcher)
     }
 
+    /**
+     * @param matcher
+     * @param useUnmergedTree
+     */
     override fun onAllNodes(
         matcher: SemanticsMatcher,
         useUnmergedTree: Boolean
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 097486a..ead9fac 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,14 +23,115 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
+
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(project(":compose:ui:ui"))
+                api(project(":compose:ui:ui-text"))
+                api(project(":compose:ui:ui-unit"))
+                api(project(":compose:runtime:runtime"))
+                api(libs.kotlinStdlib)
+
+                implementation(project(":compose:ui:ui-util"))
+            }
+        }
+
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                api(libs.kotlinCoroutinesCore)
+                api(libs.kotlinCoroutinesTest)
+                api(libs.kotlinStdlibCommon)
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api(project(":compose:ui:ui-graphics"))
+
+                implementation("androidx.annotation:annotation:1.1.0")
+                implementation("androidx.core:core-ktx:1.2.0")
+                implementation("androidx.test.espresso:espresso-core:3.5.0")
+                implementation(libs.testMonitor)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.junit)
+                    implementation(libs.truth)
+                    implementation(libs.skiko)
+                }
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidCommonTest {
+            dependsOn(commonTest)
+            dependencies {
+                implementation(project(":compose:test-utils"))
+                implementation(libs.truth)
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependsOn(androidCommonTest)
+            dependencies {
+                implementation(project(":compose:material:material"))
+                implementation(project(":compose:ui:ui-test-junit4"))
+                implementation("androidx.activity:activity-compose:1.3.1")
+                implementation(libs.mockitoCore)
+                implementation(libs.mockitoKotlin)
+                implementation(libs.dexmakerMockito)
+                implementation(libs.kotlinTest)
+            }
+        }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependsOn(androidCommonTest)
+            dependencies {
+                implementation(libs.mockitoCore4)
+                implementation(libs.mockitoKotlin4)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
+    }
+}
 
 android {
-    if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        sourceSets {
-            test.java.srcDirs += "src/androidCommonTest/kotlin"
-            androidTest.java.srcDirs += "src/androidCommonTest/kotlin"
-        }
+    sourceSets {
+        test.java.srcDirs += "src/androidCommonTest/kotlin"
+        androidTest.java.srcDirs += "src/androidCommonTest/kotlin"
     }
 
     lintOptions {
@@ -41,121 +141,9 @@
 }
 
 dependencies {
-
-    if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        api("androidx.compose.runtime:runtime:1.2.1")
-        api(project(":compose:ui:ui"))
-        api(project(":compose:ui:ui-graphics"))
-        api(project(":compose:ui:ui-text"))
-        api(project(":compose:ui:ui-unit"))
-        api(libs.kotlinCoroutinesCore)
-        api(libs.kotlinCoroutinesTest)
-        api(libs.kotlinStdlib)
-        api(libs.kotlinStdlibCommon)
-
-        implementation(project(":compose:ui:ui-util"))
-        implementation("androidx.annotation:annotation:1.1.0")
-        implementation("androidx.core:core-ktx:1.1.0")
-        implementation("androidx.test.espresso:espresso-core:3.5.0")
-        implementation("androidx.test:monitor:1.6.0")
-
-        testImplementation(project(":compose:test-utils"))
-        testImplementation(libs.truth)
-        testImplementation(libs.robolectric)
-        testImplementation(libs.mockitoCore4)
-        testImplementation(libs.mockitoKotlin4)
-
-        androidTestImplementation("androidx.activity:activity-compose:1.3.1")
-        androidTestImplementation(project(":compose:material:material"))
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.mockitoCore)
-        androidTestImplementation(libs.dexmakerMockito)
-        androidTestImplementation(libs.mockitoKotlin)
-        androidTestImplementation(libs.kotlinTest)
-
-        samples(project(":compose:ui:ui-test:ui-test-samples"))
-    }
-}
-
-
-if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        sourceSets {
-            commonMain.dependencies {
-                api(project(":compose:ui:ui"))
-                api(project(":compose:ui:ui-text"))
-                api(project(":compose:ui:ui-unit"))
-                api(libs.kotlinStdlib)
-
-                implementation(project(":compose:ui:ui-util"))
-            }
-
-            jvmMain.dependencies {
-                api(project(":compose:runtime:runtime"))
-                api(libs.kotlinCoroutinesCore)
-                api(libs.kotlinCoroutinesTest)
-                api(libs.kotlinStdlibCommon)
-            }
-
-            androidMain.dependencies {
-                api(project(":compose:ui:ui-graphics"))
-
-                implementation("androidx.annotation:annotation:1.1.0")
-                implementation("androidx.core:core-ktx:1.2.0")
-                implementation("androidx.test.espresso:espresso-core:3.3.0")
-                implementation(libs.testMonitor)
-            }
-
-            androidCommonTest.dependencies {
-                implementation(project(":compose:test-utils"))
-                implementation(libs.truth)
-            }
-
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.mockitoCore4)
-                implementation(libs.mockitoKotlin4)
-            }
-
-            androidAndroidTest.dependencies {
-                implementation(project(":compose:material:material"))
-                implementation(project(":compose:ui:ui-test-junit4"))
-                implementation("androidx.activity:activity-compose:1.3.1")
-                implementation(libs.mockitoCore)
-                implementation(libs.mockitoKotlin)
-                implementation(libs.dexmakerMockito)
-                implementation(libs.kotlinTest)
-            }
-
-            desktopMain.dependencies {
-                implementation(libs.junit)
-                implementation(libs.truth)
-                implementation(libs.skiko)
-            }
-
-            androidCommonTest.dependsOn(commonTest)
-            androidTest.dependsOn(androidCommonTest)
-            androidAndroidTest.dependsOn(androidCommonTest)
-        }
-    }
-
-    dependencies {
-        // Can't declare this in kotlin { sourceSets { androidTest.dependencies { .. } } } as that
-        // leaks into instrumented tests (b/214407011)
-        testImplementation(libs.robolectric)
-
-        samples(project(":compose:ui:ui-test:ui-test-samples"))
-    }
+    // Can't declare this in kotlin { sourceSets { androidTest.dependencies { .. } } } as that
+    // leaks into instrumented tests (b/214407011)
+    testImplementation(libs.robolectric)
 }
 
 androidx {
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index c20c781..2903ae2 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,77 +23,15 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    dependencies {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-        implementation(libs.kotlinStdlibCommon)
-        implementation(libs.kotlinCoroutinesCore)
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api(project(":compose:ui:ui-graphics"))
-        api(project(":compose:ui:ui-unit"))
-        api("androidx.annotation:annotation:1.1.0")
-
-        // when updating the runtime version please also update the runtime-saveable version
-        implementation("androidx.compose.runtime:runtime:1.2.1")
-        implementation("androidx.compose.runtime:runtime-saveable:1.2.1")
-
-        implementation(project(":compose:ui:ui-util"))
-        implementation(libs.kotlinStdlib)
-        implementation("androidx.core:core:1.7.0")
-        implementation('androidx.collection:collection:1.0.0')
-        implementation("androidx.emoji2:emoji2:1.2.0")
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-        testImplementation(libs.mockitoCore4)
-        testImplementation(libs.truth)
-        testImplementation(libs.kotlinReflect)
-        testImplementation(libs.kotlinTest)
-        testImplementation(libs.mockitoKotlin4)
-
-        androidTestImplementation(project(":internal-testutils-fonts"))
-        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-        androidTestImplementation(libs.testCore)
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.espressoCore)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.dexmakerMockito)
-        androidTestImplementation(libs.mockitoCore)
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.mockitoKotlin)
-
-        samples(projectOrArtifact(":compose:ui:ui-text:ui-text-samples"))
-    }
-
-    android {
-        sourceSets {
-            main {
-                java.srcDirs += "${supportRootFolder}/text/text/src/main/java"
-            }
-        }
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 implementation(libs.kotlinCoroutinesCore)
 
@@ -107,53 +44,58 @@
 
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
 
-            jvmMain.dependencies {
-                implementation(libs.kotlinStdlib)
+        commonTest {
+            dependencies {
             }
+        }
 
+        if (desktopEnabled) {
             skikoMain {
                 dependsOn(commonMain)
                 dependencies {
                     api(libs.skikoCommon)
+                    implementation(project(":compose:runtime:runtime"))
+                    implementation(project(":compose:runtime:runtime-saveable"))
                 }
             }
+        }
 
-            desktopMain {
-                dependsOn(skikoMain)
-                dependsOn(jvmMain)
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                implementation(libs.kotlinStdlib)
             }
+        }
 
-            androidMain {
-                dependsOn(commonMain)
-            }
 
-            androidMain.dependencies {
+        androidMain {
+            dependsOn(commonMain)
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.core:core:1.7.0")
                 implementation("androidx.emoji2:emoji2:1.2.0")
                 implementation('androidx.collection:collection:1.0.0')
             }
+        }
 
-            androidMain.kotlin.srcDirs("${supportRootFolder}/text/text/src/main/java")
-
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(project(":internal-testutils-fonts"))
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.mockitoCore4)
-                implementation(libs.truth)
-                implementation(libs.kotlinReflect)
-                implementation(libs.kotlinTest)
-                implementation(libs.mockitoKotlin4)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
             }
+        }
 
-            androidAndroidTest.dependencies {
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:ui:ui-test-junit4"))
                 implementation(project(":internal-testutils-fonts"))
                 implementation(libs.testRules)
@@ -165,20 +107,50 @@
                 implementation(libs.truth)
                 implementation(libs.mockitoKotlin)
             }
+        }
 
-            desktopTest.dependencies {
-                implementation(libs.truth)
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(project(":internal-testutils-fonts"))
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
                 implementation(libs.junit)
+                implementation(libs.truth)
+                implementation(libs.kotlinReflect)
                 implementation(libs.kotlinTest)
-                implementation(libs.skikoCurrentOs)
-                implementation(project(":compose:foundation:foundation"))
-                implementation(project(":compose:ui:ui-test-junit4"))
             }
         }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependencies {
+                    implementation(libs.truth)
+                    implementation(libs.junit)
+                    implementation(libs.kotlinTest)
+                    implementation(libs.skikoCurrentOs)
+                    implementation(project(":compose:foundation:foundation"))
+                    implementation(project(":compose:ui:ui-test-junit4"))
+                    implementation(project(":internal-testutils-fonts"))
+                }
+            }
+        }
+
+        androidMain.kotlin.srcDirs("${supportRootFolder}/text/text/src/main/java")
     }
-    dependencies {
-        samples(projectOrArtifact(":compose:ui:ui-text:ui-text-samples"))
-    }
+}
+
+dependencies {
+    // Can't declare this in kotlin { sourceSets { androidTest.dependencies { .. } } } as that
+    // leaks into instrumented tests (b/214407011)
+    testImplementation(libs.mockitoCore4)
+    testImplementation(libs.mockitoKotlin4)
 }
 
 androidx {
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index 13f1be5..b69203b 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -15,9 +15,8 @@
  */
 
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,79 +24,70 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    dependencies {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-
-        implementation(libs.kotlinStdlib)
-
-        api "androidx.annotation:annotation:1.1.0"
-
-        api("androidx.compose.runtime:runtime:1.2.1")
-        api(project(":compose:ui:ui"))
-
-        androidTestImplementation project(":compose:ui:ui-test-junit4")
-
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.testCore)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.testRules)
-
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(project(":compose:foundation:foundation-layout"))
-        androidTestImplementation(project(":compose:foundation:foundation"))
-        androidTestImplementation(project(":compose:material:material"))
-        androidTestImplementation("androidx.activity:activity-compose:1.3.1")
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
-
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlib)
 
-                api "androidx.annotation:annotation:1.1.0"
-
-                api("androidx.compose.runtime:runtime:1.2.1")
+                api(project(":compose:runtime:runtime"))
                 api(project(":compose:ui:ui"))
             }
-            jvmMain.dependencies {
-                implementation(libs.kotlinStdlib)
-            }
-            androidMain.dependencies {
-                api("androidx.annotation:annotation:1.1.0")
-            }
+        }
 
-            commonTest.dependencies {
+        commonTest {
+            dependencies {
                 implementation(kotlin("test-junit"))
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.truth)
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                implementation(libs.kotlinStdlib)
             }
-            androidAndroidTest.dependencies {
+        }
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
+
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:ui:ui-test-junit4"))
 
                 implementation(libs.junit)
@@ -112,9 +102,23 @@
                 implementation("androidx.activity:activity-compose:1.3.1")
             }
         }
-    }
-    dependencies {
-        samples(projectOrArtifact(":compose:ui:ui-unit:ui-unit-samples"))
+
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+
+                }
+            }
+        }
     }
 }
 
diff --git a/compose/ui/ui-tooling-preview/build.gradle b/compose/ui/ui-tooling-preview/build.gradle
index a9f9236..d6d3a42 100644
--- a/compose/ui/ui-tooling-preview/build.gradle
+++ b/compose/ui/ui-tooling-preview/build.gradle
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -25,47 +23,91 @@
     id("com.android.library")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
 
-dependencies {
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        implementation(libs.kotlinStdlib)
-        api("androidx.annotation:annotation:1.2.0")
-        api("androidx.compose.runtime:runtime:1.2.1")
-        testImplementation(libs.junit)
-    }
-}
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
 
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:runtime:runtime"))
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                    api(project(":compose:runtime:runtime"))
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.2.0")
             }
+        }
 
-            androidTest.dependencies {
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
+
+                }
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
+            }
+        }
+
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.junit)
             }
         }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+
+                }
+            }
+        }
     }
 }
 
-
 androidx {
     name = "Compose Tooling API"
     type = LibraryType.PUBLISHED_LIBRARY
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 507d5fcf..ea90aee 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,69 +23,51 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        implementation(libs.kotlinStdlib)
-
-        api("androidx.annotation:annotation:1.1.0")
-        implementation(project(":compose:animation:animation"))
-
-        api("androidx.compose.runtime:runtime:1.2.1")
-        api(project(":compose:ui:ui"))
-        api(project(":compose:ui:ui-tooling-preview"))
-        api(project(":compose:ui:ui-tooling-data"))
-        implementation("androidx.savedstate:savedstate-ktx:1.2.1")
-        implementation("androidx.compose.material:material:1.0.0")
-        implementation("androidx.activity:activity-compose:1.7.0")
-        implementation("androidx.lifecycle:lifecycle-common:2.6.1")
-
-        // kotlin-reflect and animation-tooling-internal are provided by Studio at runtime
-        compileOnly(project(":compose:animation:animation-tooling-internal"))
-        compileOnly(libs.kotlinReflect)
-
-        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(project(":compose:foundation:foundation-layout"))
-        androidTestImplementation(project(":compose:foundation:foundation"))
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.kotlinReflect)
-        androidTestImplementation(project(":compose:animation:animation-tooling-internal"))
-        androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
-        androidTestImplementation(project(":compose:runtime:runtime-livedata"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:ui:ui-tooling-preview"))
                 api(project(":compose:runtime:runtime"))
                 api(project(":compose:ui:ui"))
                 api(project(":compose:ui:ui-tooling-data"))
             }
-            androidMain.dependencies {
+        }
+
+        commonTest {
+            dependencies {
+
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                    api(project(":compose:runtime:runtime"))
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation(project(":compose:animation:animation"))
                 implementation("androidx.savedstate:savedstate-ktx:1.2.1")
-                implementation(project(":compose:material:material"))
+                implementation("androidx.compose.material:material:1.0.0")
                 implementation("androidx.activity:activity-compose:1.7.0")
                 implementation("androidx.lifecycle:lifecycle-common:2.6.1")
 
@@ -94,14 +75,29 @@
                 compileOnly(project(":compose:animation:animation-tooling-internal"))
                 compileOnly(libs.kotlinReflect)
             }
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
-                implementation(project(":compose:runtime:runtime"))
-                implementation(project(":compose:ui:ui"))
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(skikoMain)
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.kotlinStdlib)
+                    implementation(project(":compose:runtime:runtime"))
+                    implementation(project(":compose:ui:ui"))
+                }
             }
+        }
 
-            androidAndroidTest.dependencies {
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(project(":compose:ui:ui-test-junit4"))
 
                 implementation(libs.junit)
@@ -117,10 +113,25 @@
                 implementation(project(":compose:runtime:runtime-livedata"))
             }
         }
+
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+
+                }
+            }
+        }
     }
 }
 
-
 androidx {
     name = "Compose Tooling"
     type = LibraryType.PUBLISHED_LIBRARY
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index bfbfed7..627cd87 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,85 +23,88 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    dependencies {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        api(project(":compose:ui:ui-geometry"))
-        api("androidx.annotation:annotation:1.1.0")
-
-        implementation(libs.kotlinStdlib)
-        implementation("androidx.compose.runtime:runtime:1.2.1")
-        implementation(project(":compose:ui:ui-util"))
-
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.testExtJunit)
-        androidTestImplementation(libs.espressoCore)
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.kotlinTest)
-
-        samples(projectOrArtifact(":compose:ui:ui-unit:ui-unit-samples"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 api(project(":compose:ui:ui-geometry"))
 
                 implementation(project(":compose:runtime:runtime"))
                 implementation(project(":compose:ui:ui-util"))
             }
-            jvmMain.dependencies {
-                implementation(libs.kotlinStdlib)
-            }
-            androidMain.dependencies {
-                api("androidx.annotation:annotation:1.1.0")
-            }
+        }
 
-            commonTest.dependencies {
+        commonTest {
+            dependencies {
                 implementation(kotlin("test-junit"))
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.truth)
+        jvmMain {
+            dependencies {
+                implementation(libs.kotlinStdlib)
             }
-            androidAndroidTest.dependencies {
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.annotation:annotation:1.1.0")
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(project(":compose:runtime:runtime"))
+                }
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.testRules)
                 implementation(libs.testRunner)
                 implementation(libs.testExtJunit)
                 implementation(libs.espressoCore)
             }
         }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.truth)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
     }
-    dependencies {
-        samples(projectOrArtifact(":compose:ui:ui-unit:ui-unit-samples"))
-    }
+}
+
+dependencies {
+    samples(projectOrArtifact(":compose:ui:ui-unit:ui-unit-samples"))
 }
 
 androidx {
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index 8eeb75e..4c19723 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -24,59 +23,72 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    dependencies {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-        implementation(libs.kotlinStdlib)
-
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-        testImplementation(libs.kotlinTest)
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
             }
+        }
 
-            jvmMain.dependencies {
-                implementation(libs.kotlinStdlib)
-            }
-
-            androidMain.dependencies {
-                implementation(libs.kotlinStdlib)
-            }
-
-            commonTest.dependencies {
+        commonTest {
+            dependencies {
                 implementation(kotlin("test-junit"))
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
+        jvmMain {
+            dependencies {
+                implementation(libs.kotlinStdlib)
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                implementation(libs.kotlinStdlib)
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+            }
+        }
+
+        jvmTest {
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
+            }
+        }
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.truth)
             }
         }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+            }
+        }
     }
 }
 
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 2a98886..6b29580 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2064,6 +2064,8 @@
 
   public interface IntrinsicMeasureScope extends androidx.compose.ui.unit.Density {
     method public androidx.compose.ui.unit.LayoutDirection getLayoutDirection();
+    method @androidx.compose.ui.ExperimentalComposeUiApi public default boolean isLookingAhead();
+    property @androidx.compose.ui.ExperimentalComposeUiApi public default boolean isLookingAhead;
     property public abstract androidx.compose.ui.unit.LayoutDirection layoutDirection;
   }
 
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 2b2f069..3beea81 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.inspection.gradle.InspectionPluginKt.packageInspector
 
@@ -26,130 +25,16 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-    constraints {
-        // In 1.4.0-alpha02 there was a change made in :compose:ui:ui which fixed an issue where
-        // we were over-invalidating layout. This change caused a corresponding regression in
-        // foundation's CoreText, where it was expecting a layout to happen but with this change
-        // it would not. A corresponding fix for this was added in 1.4.0-alpha02 of
-        // :compose:foundation:foundation. By adding this constraint, we are ensuring that the
-        // if an app has this ui module _and_ the foundation module as a dependency, then the
-        // version of foundation will be at least this version. This will prevent the bug in
-        // foundation from occurring. This does _NOT_ require that the app have foundation as
-        // a dependency.
-        implementation(project(":compose:foundation:foundation")) {
-            because 'prevents a critical bug in Text'
-        }
-    }
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-        implementation(libs.kotlinStdlibCommon)
-        implementation(libs.kotlinCoroutinesCore)
 
-        // when updating the runtime version please also update the runtime-saveable version
-        implementation(project(":compose:runtime:runtime"))
-        api(project(":compose:runtime:runtime-saveable"))
-
-        api(project(":compose:ui:ui-geometry"))
-        api(project(":compose:ui:ui-graphics"))
-        api(project(":compose:ui:ui-text"))
-        api(project(":compose:ui:ui-unit"))
-        api("androidx.annotation:annotation:1.5.0")
-
-        // This has stub APIs for access to legacy Android APIs, so we don't want
-        // any dependency on this module.
-        compileOnly(project(":compose:ui:ui-android-stubs"))
-
-        implementation(project(":compose:ui:ui-util"))
-        implementation(libs.kotlinStdlib)
-        implementation("androidx.autofill:autofill:1.0.0")
-        implementation(libs.kotlinCoroutinesAndroid)
-
-        // Used to generate debug information in the layout inspector. If not present,
-        // we may fall back to more limited data.
-        compileOnly(libs.kotlinReflect)
-        testImplementation(libs.kotlinReflect)
-
-        implementation("androidx.activity:activity-ktx:1.7.0")
-        implementation(project(":core:core"))
-        implementation('androidx.collection:collection:1.0.0')
-        implementation("androidx.customview:customview-poolingcontainer:1.0.0")
-        implementation("androidx.savedstate:savedstate-ktx:1.2.1")
-        implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
-        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
-        implementation("androidx.profileinstaller:profileinstaller:1.3.0")
-        implementation("androidx.emoji2:emoji2:1.2.0")
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.kotlinCoroutinesTest)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-        testImplementation(libs.mockitoCore4)
-        testImplementation(libs.mockitoKotlin4)
-        testImplementation(libs.robolectric)
-        testImplementation(project(":compose:ui:ui-test-junit4"))
-        testImplementation(project(":compose:test-utils"))
-
-        androidTestImplementation(libs.testCore)
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.testExtJunitKtx)
-        androidTestImplementation(libs.testUiautomator)
-        androidTestImplementation(libs.kotlinCoroutinesTest)
-        androidTestImplementation(libs.kotlinTest)
-        androidTestImplementation(libs.espressoCore)
-        androidTestImplementation(libs.bundles.espressoContrib)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.dexmakerMockito)
-        androidTestImplementation(libs.mockitoCore)
-        androidTestImplementation(libs.truth)
-        androidTestImplementation(libs.mockitoKotlin)
-        androidTestImplementation(libs.material)
-        androidTestImplementation(project(":compose:animation:animation-core"))
-        androidTestImplementation(project(":compose:foundation:foundation"))
-        androidTestImplementation(project(":compose:foundation:foundation-layout"))
-        androidTestImplementation(project(":compose:material:material"))
-        androidTestImplementation(project(":compose:test-utils"))
-        androidTestImplementation(project(":internal-testutils-fonts"))
-        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-        androidTestImplementation(project(":internal-testutils-runtime"))
-        androidTestImplementation(project(":test:screenshot:screenshot"))
-        androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.1")
-        androidTestImplementation("androidx.recyclerview:recyclerview:1.3.0-alpha02")
-        androidTestImplementation("androidx.core:core-ktx:1.9.0")
-        androidTestImplementation("androidx.activity:activity-compose:1.7.0")
-        androidTestImplementation("androidx.appcompat:appcompat:1.3.0")
-        androidTestImplementation("androidx.fragment:fragment:1.3.0")
-
-        lintChecks(project(":compose:ui:ui-lint"))
-        lintPublish(project(":compose:ui:ui-lint"))
-
-        samples(project(":compose:ui:ui:ui-samples"))
-    }
-}
-
-packageInspector(project, ":compose:ui:ui-inspection")
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
                 implementation(libs.kotlinCoroutinesCore)
 
@@ -163,8 +48,33 @@
                 api project(":compose:ui:ui-unit")
                 implementation(project(":compose:ui:ui-util"))
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+                implementation(libs.kotlinReflect)
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+        if (desktopEnabled) {
+            skikoMain {
+                dependsOn(commonMain)
+                dependencies {
+                    api(project(":compose:ui:ui-graphics"))
+                    api(libs.skikoCommon)
+                }
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 implementation(libs.kotlinStdlib)
                 // This has stub APIs for access to legacy Android APIs, so we don't want
                 // any dependency on this module.
@@ -181,48 +91,32 @@
                 implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
                 implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
                 implementation("androidx.emoji2:emoji2:1.2.0")
-            }
 
-            jvmMain.dependencies {
-                implementation(libs.kotlinStdlib)
+                implementation("androidx.profileinstaller:profileinstaller:1.3.0")
             }
-            skikoMain {
-                dependsOn(commonMain)
-                dependencies {
-                    api(project(":compose:ui:ui-graphics"))
-                    api(libs.skikoCommon)
-                }
-            }
+        }
+
+        if (desktopEnabled) {
             desktopMain {
                 dependsOn(skikoMain)
+                dependsOn(jvmMain)
                 dependencies {
+                    implementation(libs.kotlinStdlib)
                     implementation(libs.kotlinStdlibJdk8)
                     api(libs.kotlinCoroutinesSwing)
                 }
             }
+        }
 
-            commonTest.dependencies {
-                implementation(libs.kotlinReflect)
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.kotlinCoroutinesTest)
-                implementation(libs.junit)
-                implementation(libs.truth)
-                implementation(libs.mockitoCore4)
-                implementation(libs.mockitoKotlin4)
-                implementation(project(":compose:ui:ui-test-junit4"))
-                implementation(project(":internal-testutils-fonts"))
-                implementation(project(":compose:test-utils"))
-            }
-
-            androidAndroidTest.dependencies {
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation("androidx.fragment:fragment:1.3.0")
                 implementation("androidx.appcompat:appcompat:1.3.0")
                 implementation(libs.testUiautomator)
@@ -253,27 +147,75 @@
                 implementation("androidx.core:core-ktx:1.2.0")
                 implementation("androidx.activity:activity-compose:1.7.0")
             }
+        }
 
-            desktopTest.dependencies {
-                implementation(libs.truth)
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.kotlinCoroutinesTest)
                 implementation(libs.junit)
-                implementation(libs.mockitoCore)
-                implementation(libs.mockitoKotlin)
-                implementation(libs.skikoCurrentOs)
-                implementation(project(":compose:material:material"))
+                implementation(libs.truth)
                 implementation(project(":compose:ui:ui-test-junit4"))
+                implementation(project(":internal-testutils-fonts"))
+                implementation(project(":compose:test-utils"))
+            }
+        }
+
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                    implementation(libs.truth)
+                    implementation(libs.junit)
+                    implementation(libs.mockitoCore)
+                    implementation(libs.mockitoKotlin)
+                    implementation(libs.skikoCurrentOs)
+                    implementation(project(":compose:material:material"))
+                    implementation(project(":compose:ui:ui-test-junit4"))
+                }
             }
         }
     }
-    dependencies {
-        samples(project(":compose:ui:ui:ui-samples"))
+}
 
-        // Can't declare this in kotlin { sourceSets { androidTest.dependencies { .. } } } as that
-        // leaks into instrumented tests (b/214407011)
-        testImplementation(libs.robolectric)
+dependencies {
+
+    constraints {
+        // In 1.4.0-alpha02 there was a change made in :compose:ui:ui which fixed an issue where
+        // we were over-invalidating layout. This change caused a corresponding regression in
+        // foundation's CoreText, where it was expecting a layout to happen but with this change
+        // it would not. A corresponding fix for this was added in 1.4.0-alpha02 of
+        // :compose:foundation:foundation. By adding this constraint, we are ensuring that the
+        // if an app has this ui module _and_ the foundation module as a dependency, then the
+        // version of foundation will be at least this version. This will prevent the bug in
+        // foundation from occurring. This does _NOT_ require that the app have foundation as
+        // a dependency.
+        implementation(project(":compose:foundation:foundation")) {
+            because 'prevents a critical bug in Text'
+        }
     }
 }
 
+packageInspector(project, ":compose:ui:ui-inspection")
+
+dependencies {
+    lintChecks(project(":compose:ui:ui-lint"))
+    lintPublish(project(":compose:ui:ui-lint"))
+
+    // Can't declare this in kotlin { sourceSets { androidTest.dependencies { .. } } } as that
+    // leaks into instrumented tests (b/214407011)
+    testImplementation(libs.robolectric)
+    testImplementation(libs.mockitoCore4)
+    testImplementation(libs.mockitoKotlin4)
+}
+
 androidx {
     name = "Compose UI primitives"
     type = LibraryType.PUBLISHED_LIBRARY
@@ -282,7 +224,7 @@
     legacyDisableKotlinStrictApiMode = true
 }
 
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
+if (desktopEnabled) {
     tasks.findByName("desktopTest").configure {
         systemProperties["GOLDEN_PATH"] = project.rootDir.absolutePath + "/../../golden"
     }
@@ -304,24 +246,3 @@
     // the androidx.compose.ui:ui-test library
     testNamespace "androidx.compose.ui.tests"
 }
-
-// Diagnostics for b/188565660
-def verifyKotlinModule(String variant) {
-    project.afterEvaluate {
-        def capitalVariant = variant.capitalize()
-        def moduleFile = new File("${buildDir}/tmp/kotlin-classes/${variant}/META-INF/ui_${variant}.kotlin_module")
-        tasks.named("compile${capitalVariant}Kotlin").configure { t ->
-            t.doLast {
-                // This file should be large, about 3.2K. If this file is short then many symbols will fail to resolve
-                if (moduleFile.length() < 250) {
-                    throw new GradleException("kotlin_module file ($moduleFile) too short! See b/188565660 for more information. File text: ${moduleFile.text}")
-                }
-            }
-        }
-    }
-}
-if (!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    for (variant in ["debug", "release"]) {
-        verifyKotlinModule(variant)
-    }
-}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt
index 343c89d..6d4e513 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LookaheadLayoutSample.kt
@@ -40,14 +40,17 @@
 import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
+import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.LookaheadScope
 import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -209,3 +212,53 @@
         }
     }
 }
+
+@Sampled
+@Composable
+fun animateContentSizeAfterLookaheadPass() {
+    var sizeAnim by remember {
+        mutableStateOf<Animatable<IntSize, AnimationVector2D>?>(null)
+    }
+    var lookaheadSize by remember {
+        mutableStateOf<IntSize?>(null)
+    }
+    val coroutineScope = rememberCoroutineScope()
+    LookaheadScope {
+        // The Box is in a LookaheadScope. This means there will be a lookahead measure pass
+        // before the main measure pass.
+        // Here we are creating something similar to the `animateContentSize` modifier.
+        Box(
+            Modifier
+                .clipToBounds()
+                .layout { measurable, constraints ->
+                    val placeable = measurable.measure(constraints)
+
+                    val measuredSize = IntSize(placeable.width, placeable.height)
+                    val (width, height) = if (isLookingAhead) {
+                        // Record lookahead size if we are in lookahead pass. This lookahead size
+                        // will be used for size animation, such that the main measure pass will
+                        // gradually change size until it reaches the lookahead size.
+                        lookaheadSize = measuredSize
+                        measuredSize
+                    } else {
+                        // Since we are in an explicit lookaheadScope, we know the lookahead pass
+                        // is guaranteed to happen, therefore the lookahead size that we recorded is
+                        // not null.
+                        val target = requireNotNull(lookaheadSize)
+                        val anim = sizeAnim?.also {
+                            coroutineScope.launch { it.animateTo(target) }
+                        } ?: Animatable(target, IntSize.VectorConverter)
+                        sizeAnim = anim
+                        // By returning the animated size only during main pass, we are allowing
+                        // lookahead pass to see the future layout past the animation.
+                        anim.value
+                    }
+
+                    layout(width, height) {
+                        placeable.place(0, 0)
+                    }
+                }) {
+            // Some content that changes size
+        }
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
index 32fd60c..f761c28 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilterTest.kt
@@ -4405,38 +4405,3 @@
 )
 
 internal typealias PointerEventHandler = (PointerEvent, PointerEventPass, IntSize) -> Unit
-
-private fun PointerEventHandler.invokeOverAllPasses(
-    pointerEvent: PointerEvent,
-    size: IntSize = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
-) {
-    invokeOverPasses(
-        pointerEvent,
-        listOf(
-            PointerEventPass.Initial,
-            PointerEventPass.Main,
-            PointerEventPass.Final
-        ),
-        size = size
-    )
-}
-
-private fun PointerEventHandler.invokeOverPasses(
-    pointerEvent: PointerEvent,
-    vararg pointerEventPasses: PointerEventPass,
-    size: IntSize = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
-) {
-    invokeOverPasses(pointerEvent, pointerEventPasses.toList(), size)
-}
-
-private fun PointerEventHandler.invokeOverPasses(
-    pointerEvent: PointerEvent,
-    pointerEventPasses: List<PointerEventPass>,
-    size: IntSize = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
-) {
-    require(pointerEvent.changes.isNotEmpty())
-    require(pointerEventPasses.isNotEmpty())
-    pointerEventPasses.forEach {
-        this.invoke(pointerEvent, it, size)
-    }
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 302768a..378d5b7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -65,8 +65,11 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.node.LayoutModifierNode
 import androidx.compose.ui.node.LayoutNode
+import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.AndroidOwnerExtraAssertionsRule
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.unit.Constraints
@@ -83,6 +86,7 @@
 import androidx.test.filters.MediumTest
 import java.lang.Integer.max
 import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
 import junit.framework.TestCase.assertTrue
 import kotlin.math.roundToInt
 import kotlin.random.Random
@@ -1928,6 +1932,218 @@
         }
     }
 
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun testIsLookingAhead() {
+        var iterations by mutableStateOf(0)
+        val size = mutableMapOf<Boolean, IntSize>()
+        rule.setContent {
+            Box(Modifier.fillMaxSize()) {
+                LookaheadScope {
+                    // Fill max size will cause the remeasure requests to go down the
+                    // forceMeasureSubtree code path.
+                    CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                        Column(Modifier.fillMaxSize()) {
+                            // This box will get a remeasure request when `iterations` changes.
+                            // Subsequently this Box's size change will trigger a measurement pass
+                            // from Column.
+                            Box(
+                                Modifier
+                                    .layout { measurable, constraints ->
+                                        measurable
+                                            .measure(constraints)
+                                            .run {
+                                                size[isLookingAhead] = IntSize(width, height)
+                                                layout(width, height) {
+                                                    place(0, 0)
+                                                }
+                                            }
+                                    }
+                                    .intermediateLayout { measurable, _ ->
+                                        // Force a state-read (similar to animation but more
+                                        // reliable)
+                                        measurable
+                                            .measure(Constraints.fixed(200 + 100 * iterations, 200))
+                                            .run {
+                                                layout(width, height) {
+                                                    place(0, 0)
+                                                }
+                                            }
+                                    }) {
+                                Box(Modifier.size(100.dp))
+                            }
+                        }
+                        SubcomposeLayout(
+                            Modifier
+                                .fillMaxSize()
+                                .requiredSize(200.dp),
+                            intermediateMeasurePolicy = { constraints ->
+                                assertFalse(isLookingAhead)
+                                measurablesForSlot(Unit)[0].measure(constraints)
+                                layout(0, 0) {}
+                            }
+                        ) { constraints ->
+                            assertTrue(isLookingAhead)
+                            val placeable = subcompose(Unit) {
+                                Box(Modifier.requiredSize(400.dp, 600.dp))
+                            }[0].measure(constraints)
+                            layout(500, 300) {
+                                placeable.place(0, 0)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        repeat(4) {
+            rule.runOnIdle {
+                assertEquals(IntSize(100, 100), size[true])
+                assertEquals(IntSize(200 + 100 * it, 200), size[false])
+                iterations++
+            }
+        }
+    }
+
+    class TestLayoutModifierNode(
+        var lookaheadIntrinsicResult: MutableMap<String, Int>,
+        var intrinsicResult: MutableMap<String, Int>
+    ) : LayoutModifierNode, Modifier.Node() {
+        override fun MeasureScope.measure(
+            measurable: Measurable,
+            constraints: Constraints
+        ): MeasureResult {
+            return measurable.measure(constraints).run {
+                layout(width, height) {
+                    place(0, 0)
+                }
+            }
+        }
+
+        override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+            measurable: IntrinsicMeasurable,
+            width: Int
+        ): Int = measurable.maxIntrinsicHeight(width).also {
+            if (isLookingAhead) {
+                lookaheadIntrinsicResult["maxHeight"] = it
+            } else {
+                intrinsicResult["maxHeight"] = it
+            }
+        }
+
+        override fun IntrinsicMeasureScope.minIntrinsicHeight(
+            measurable: IntrinsicMeasurable,
+            width: Int
+        ): Int = measurable.minIntrinsicHeight(width).also {
+            if (isLookingAhead) {
+                lookaheadIntrinsicResult["minHeight"] = it
+            } else {
+                intrinsicResult["minHeight"] = it
+            }
+        }
+
+        override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+            measurable: IntrinsicMeasurable,
+            height: Int
+        ): Int = measurable.maxIntrinsicWidth(height).also {
+            if (isLookingAhead) {
+                lookaheadIntrinsicResult["maxWidth"] = it
+            } else {
+                intrinsicResult["maxWidth"] = it
+            }
+        }
+
+        override fun IntrinsicMeasureScope.minIntrinsicWidth(
+            measurable: IntrinsicMeasurable,
+            height: Int
+        ): Int = measurable.minIntrinsicWidth(height).also {
+            if (isLookingAhead) {
+                lookaheadIntrinsicResult["minWidth"] = it
+            } else {
+                intrinsicResult["minWidth"] = it
+            }
+        }
+    }
+
+    data class TestElement(
+        val lookaheadIntrinsicResult: MutableMap<String, Int>,
+        val intrinsicResult: MutableMap<String, Int>
+    ) : ModifierNodeElement<TestLayoutModifierNode>() {
+        override fun create(): TestLayoutModifierNode =
+            TestLayoutModifierNode(lookaheadIntrinsicResult, intrinsicResult)
+
+        override fun update(node: TestLayoutModifierNode) {
+            node.lookaheadIntrinsicResult = lookaheadIntrinsicResult
+            node.intrinsicResult = intrinsicResult
+        }
+
+        override fun InspectorInfo.inspectableProperties() {
+            name = "TestElement"
+            properties["lookaheadIntrinsicResult"] = lookaheadIntrinsicResult
+            properties["intrinsicResult"] = intrinsicResult
+        }
+    }
+
+    @Test
+    fun testIsLookingAheadWithIntrinsics() {
+        val lookaheadIntrinsicsResult = mutableMapOf<String, Int>()
+        val intrinsicsResult = mutableMapOf<String, Int>()
+        val modifierList = listOf(
+            Modifier.width(IntrinsicSize.Max),
+            Modifier.width(IntrinsicSize.Min),
+            Modifier.height(IntrinsicSize.Max),
+            Modifier.height(IntrinsicSize.Min),
+        )
+        var iteration by mutableStateOf(0)
+        rule.setContent {
+            LookaheadScope {
+                CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                    Row(Modifier.width(IntrinsicSize.Max)) {
+                        Box(
+                            Modifier
+                                .fillMaxSize()
+                                .then(modifierList[iteration])
+                                .then(
+                                    TestElement(
+                                        lookaheadIntrinsicsResult, intrinsicsResult
+                                    )
+                                )
+                                .layout { measurable, constraints ->
+                                    measurable
+                                        .measure(constraints)
+                                        .run {
+                                            if (isLookingAhead) {
+                                                layout(200, 250) {
+                                                    place(0, 0)
+                                                }
+                                            } else {
+                                                layout(100, 150) {
+                                                    place(0, 0)
+                                                }
+                                            }
+                                        }
+                                }) {
+                            Box(Modifier.size(10.dp))
+                        }
+                    }
+                }
+            }
+        }
+        repeat(3) {
+            rule.waitForIdle()
+            iteration++
+        }
+        rule.runOnIdle {
+            assertEquals(250, lookaheadIntrinsicsResult["maxHeight"])
+            assertEquals(250, lookaheadIntrinsicsResult["minHeight"])
+            assertEquals(200, lookaheadIntrinsicsResult["maxWidth"])
+            assertEquals(200, lookaheadIntrinsicsResult["minWidth"])
+            assertEquals(150, intrinsicsResult["maxHeight"])
+            assertEquals(150, intrinsicsResult["minHeight"])
+            assertEquals(100, intrinsicsResult["maxWidth"])
+            assertEquals(100, intrinsicsResult["minWidth"])
+        }
+    }
+
     @Test
     fun forceMeasureSubtreeWhileLookaheadMeasureRequestedFromSubtree() {
         var iterations by mutableStateOf(0)
@@ -1960,13 +2176,17 @@
                                     if (iterations % 2 == 0)
                                         Modifier.size(100.dp)
                                     else
-                                        Modifier.intermediateLayout { measurable, constraints ->
-                                            measurable.measure(constraints).run {
-                                                layout(width, height) {
-                                                    place(5, 5)
-                                                }
+                                        Modifier
+                                            .intermediateLayout { measurable, constraints ->
+                                                measurable
+                                                    .measure(constraints)
+                                                    .run {
+                                                        layout(width, height) {
+                                                            place(5, 5)
+                                                        }
+                                                    }
                                             }
-                                        }.padding(5.dp)
+                                            .padding(5.dp)
                                 )
                             }
                         }
@@ -2099,6 +2319,7 @@
         with(scope) {
             this@composed
                 .intermediateLayout { measurable, constraints ->
+                    assertFalse(isLookingAhead)
                     lookaheadSize = this.lookaheadSize
                     measureWithLambdas(
                         prePlacement = {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidUiDispatcherTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidUiDispatcherTest.kt
index 5575793..e550f57 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidUiDispatcherTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/AndroidUiDispatcherTest.kt
@@ -32,10 +32,18 @@
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
+import kotlin.test.fail
 import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Runnable
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeoutOrNull
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
@@ -174,4 +182,60 @@
             }
         )
     }
+
+    /**
+     * Test that an AndroidUiDispatcher can be wrapped by another ContinuationInterceptor
+     * without breaking the MonotonicFrameClock's ability to coordinate with its
+     * original dispatcher.
+     *
+     * Construct a situation where the Choreographer contains three frame callbacks:
+     * 1) checkpoint 1
+     * 2) the AndroidUiDispatcher awaiting-frame callback
+     * 3) checkpoint 2
+     * Confirm that a call to withFrameNanos made *after* these three frame callbacks
+     * are enqueued runs *before* checkpoint 2, indicating that it ran with the original
+     * dispatcher's awaiting-frame callback, even though we wrapped the dispatcher.
+     */
+    @Test
+    fun wrappedDispatcherPostsToDispatcherFrameClock() = runBlocking(Dispatchers.Main) {
+        val uiDispatcherContext = AndroidUiDispatcher.Main
+        val uiDispatcher = uiDispatcherContext[ContinuationInterceptor] as CoroutineDispatcher
+        val wrapperDispatcher = object : CoroutineDispatcher() {
+            override fun dispatch(context: CoroutineContext, block: Runnable) {
+                uiDispatcher.dispatch(context, block)
+            }
+        }
+
+        val choreographer = Choreographer.getInstance()
+
+        val expectCount = AtomicInteger(1)
+        fun expect(value: Int) {
+            while (true) {
+                val old = expectCount.get()
+                if (old != value) fail("expected sequence $old but encountered $value")
+                if (expectCount.compareAndSet(value, value + 1)) break
+            }
+        }
+
+        choreographer.postFrameCallback {
+            expect(1)
+        }
+
+        launch(uiDispatcherContext, start = CoroutineStart.UNDISPATCHED) {
+            withFrameNanos {
+                expect(2)
+            }
+        }
+
+        choreographer.postFrameCallback {
+            expect(4)
+        }
+
+        withContext(uiDispatcherContext + wrapperDispatcher) {
+            withFrameNanos {
+                expect(3)
+            }
+            expect(5)
+        }
+    }
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiDispatcher.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiDispatcher.android.kt
index 5be4139..7dce6f5 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiDispatcher.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiDispatcher.android.kt
@@ -131,7 +131,7 @@
      * A [MonotonicFrameClock] associated with this [AndroidUiDispatcher]'s [choreographer]
      * that may be used to await [Choreographer] frame dispatch.
      */
-    val frameClock: MonotonicFrameClock = AndroidUiFrameClock(choreographer)
+    val frameClock: MonotonicFrameClock = AndroidUiFrameClock(choreographer, this)
 
     override fun dispatch(context: CoroutineContext, block: Runnable) {
         synchronized(lock) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiFrameClock.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiFrameClock.android.kt
index fbc7b21..a9fd7ed 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiFrameClock.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidUiFrameClock.android.kt
@@ -21,13 +21,20 @@
 import kotlin.coroutines.ContinuationInterceptor
 import kotlin.coroutines.coroutineContext
 
-class AndroidUiFrameClock(
-    val choreographer: Choreographer
+class AndroidUiFrameClock internal constructor(
+    val choreographer: Choreographer,
+    private val dispatcher: AndroidUiDispatcher?
 ) : androidx.compose.runtime.MonotonicFrameClock {
+
+    constructor(
+        choreographer: Choreographer
+    ) : this(choreographer, null)
+
     override suspend fun <R> withFrameNanos(
         onFrame: (Long) -> R
     ): R {
-        val uiDispatcher = coroutineContext[ContinuationInterceptor] as? AndroidUiDispatcher
+        val uiDispatcher = dispatcher
+            ?: coroutineContext[ContinuationInterceptor] as? AndroidUiDispatcher
         return suspendCancellableCoroutine { co ->
             // Important: this callback won't throw, and AndroidUiDispatcher counts on it.
             val callback = Choreographer.FrameCallback { frameTimeNanos ->
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
index 96c06cc..eab488c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
@@ -497,7 +497,7 @@
     override fun resetPointerInputHandler() {
         val localJob = pointerInputJob
         if (localJob != null) {
-            localJob.cancel(CancellationException())
+            localJob.cancel()
             pointerInputJob = null
         }
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
index 9b4c8f9..e0ad52d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntermediateLayoutModifierNode.kt
@@ -317,6 +317,10 @@
             }
         }
 
+        // Intermediate layout pass is post-lookahead. Therefore return false here.
+        override val isLookingAhead: Boolean
+            get() = false
+
         override val layoutDirection: LayoutDirection
             get() = coordinator!!.layoutDirection
         override val density: Float
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntrinsicMeasureScope.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntrinsicMeasureScope.kt
index 5c29e6c..657e31d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntrinsicMeasureScope.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/IntrinsicMeasureScope.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.layout
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 
@@ -28,4 +29,17 @@
      * to measure their children.
      */
     val layoutDirection: LayoutDirection
+
+    /**
+     * This indicates whether the ongoing measurement is for lookahead pass.
+     * [IntrinsicMeasureScope] implementations, especially [MeasureScope] implementations should
+     * override this flag to reflect whether the measurement is intended for lookahead pass.
+     *
+     * @sample androidx.compose.ui.samples.animateContentSizeAfterLookaheadPass
+     */
+    @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+    @get:ExperimentalComposeUiApi
+    @ExperimentalComposeUiApi
+    val isLookingAhead: Boolean
+        get() = false
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index 2b45ead..813a516 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -32,7 +32,6 @@
 import androidx.compose.ui.node.ComposeUiNode
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
@@ -64,7 +63,8 @@
  */
 @Suppress("ComposableLambdaParameterPosition")
 @UiComposable
-@Composable inline fun Layout(
+@Composable
+inline fun Layout(
     content: @Composable @UiComposable () -> Unit,
     modifier: Modifier = Modifier,
     measurePolicy: MeasurePolicy
@@ -322,6 +322,6 @@
  * call.
  */
 internal class IntrinsicsMeasureScope(
-    density: Density,
-    override val layoutDirection: LayoutDirection
-) : MeasureScope, Density by density
+    intrinsicMeasureScope: IntrinsicMeasureScope,
+    override val layoutDirection: LayoutDirection,
+) : MeasureScope, IntrinsicMeasureScope by intrinsicMeasureScope
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index 6ebfd55..d95d81b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -1000,6 +1000,9 @@
         override var layoutDirection: LayoutDirection = LayoutDirection.Rtl
         override var density: Float = 0f
         override var fontScale: Float = 0f
+        override val isLookingAhead: Boolean
+            get() = root.layoutState == LayoutState.LookaheadLayingOut ||
+                root.layoutState == LayoutState.LookaheadMeasuring
 
         override fun subcompose(slotId: Any?, content: @Composable () -> Unit) =
             this@LayoutNodeSubcompositionsState.subcompose(slotId, content)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
index b34b523..ba59c04 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LookaheadDelegate.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.node
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.layout.AlignmentLine
 import androidx.compose.ui.layout.LayoutCoordinates
@@ -76,6 +77,10 @@
             alignmentLinesOwner.parentAlignmentLinesOwner?.alignmentLines?.onAlignmentsChanged()
         }
     }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    override val isLookingAhead: Boolean
+        get() = false
 }
 
 internal abstract class LookaheadDelegate(
@@ -91,6 +96,8 @@
         get() = _measureResult ?: error(
             "LookaheadDelegate has not been measured yet when measureResult is requested."
         )
+    override val isLookingAhead: Boolean
+        get() = true
     override val layoutDirection: LayoutDirection
         get() = coordinator.layoutDirection
     override val density: Float
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
index 2b2847f..9043a07 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
@@ -743,7 +743,8 @@
 private fun Modifier.fillVector(
     result: MutableVector<Modifier.Element>
 ): MutableVector<Modifier.Element> {
-    val stack = MutableVector<Modifier>(result.size).also { it.add(this) }
+    val capacity = result.size.coerceAtLeast(16)
+    val stack = MutableVector<Modifier>(capacity).also { it.add(this) }
     while (stack.isNotEmpty()) {
         when (val next = stack.removeAt(stack.size - 1)) {
             is CombinedModifier -> {
diff --git a/constraintlayout/constraintlayout-compose/build.gradle b/constraintlayout/constraintlayout-compose/build.gradle
index 639aa60..51a3271 100644
--- a/constraintlayout/constraintlayout-compose/build.gradle
+++ b/constraintlayout/constraintlayout-compose/build.gradle
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
 import androidx.build.LibraryType
-import androidx.build.Publish
 
 plugins {
     id("AndroidXPlugin")
@@ -24,77 +22,49 @@
     id("AndroidXComposePlugin")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+androidXMultiplatform {
+    android()
 
-dependencies {
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        implementation(project(":compose:ui:ui"))
-        implementation(project(":compose:ui:ui-unit"))
-        implementation(project(":compose:ui:ui-util"))
-        implementation(project(":compose:foundation:foundation"))
-        implementation(project(":compose:foundation:foundation-layout"))
-
-        implementation(project(":constraintlayout:constraintlayout-core"))
-
-        androidTestImplementation(project(":compose:material:material"))
-        androidTestImplementation(project(":compose:ui:ui-test"))
-        androidTestImplementation(project(":compose:ui:ui-test-junit4"))
-        androidTestImplementation(project(":compose:ui:ui-test-manifest"))
-        androidTestImplementation(project(":activity:activity"))
-
-        androidTestImplementation(libs.kotlinTest)
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-
-        lintPublish(project(":constraintlayout:constraintlayout-compose-lint"))
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
-//                implementation(libs.kotlinStdlibCommon)
-
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(project(":compose:ui:ui"))
-                implementation("androidx.compose.ui:ui-unit:1.4.0-beta02")
-                implementation("androidx.compose.ui:ui-util:1.4.0-beta02")
-                implementation("androidx.compose.foundation:foundation:1.4.0-beta02")
-                implementation("androidx.compose.foundation:foundation-layout:1.4.0-beta02")
+                implementation(project(":compose:ui:ui-unit"))
+                implementation(project(":compose:ui:ui-util"))
+                implementation(project(":compose:foundation:foundation"))
+                implementation(project(":compose:foundation:foundation-layout"))
                 implementation(project(":constraintlayout:constraintlayout-core"))
-
             }
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependencies {
+            }
+        }
+
+
+        androidMain {
+            dependsOn(commonMain)
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.core:core-ktx:1.5.0")
             }
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
+        jvmTest {
+            dependencies {
             }
+        }
 
-            // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
-            //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
-            //  level dependencies block instead:
-            //  `dependencies { testImplementation(libs.robolectric) }`
-            androidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-            }
-
-            androidAndroidTest.dependencies {
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.kotlinTest)
                 implementation(libs.testRules)
                 implementation(libs.testRunner)
@@ -107,9 +77,27 @@
                 implementation(project(":compose:test-utils"))
             }
         }
+
+
+        // TODO(b/214407011): These dependencies leak into instrumented tests as well. If you
+        //  need to add Robolectric (which must be kept out of androidAndroidTest), use a top
+        //  level dependencies block instead:
+        //  `dependencies { testImplementation(libs.robolectric) }`
+        androidTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+            }
+        }
     }
 }
 
+dependencies {
+    lintPublish(project(":constraintlayout:constraintlayout-compose-lint"))
+}
+
 androidx {
     name = "Android ConstraintLayout Compose Library"
     type = LibraryType.PUBLISHED_LIBRARY
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 7ce3b2f..c5f7d8c 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1078,6 +1078,7 @@
     method @ColorInt public static int getColor(android.content.Context, @ColorRes int);
     method public static android.content.res.ColorStateList? getColorStateList(android.content.Context, @ColorRes int);
     method public static java.io.File? getDataDir(android.content.Context);
+    method public static android.view.Display getDisplay(@DisplayContext android.content.Context);
     method public static android.graphics.drawable.Drawable? getDrawable(android.content.Context, @DrawableRes int);
     method public static java.io.File![] getExternalCacheDirs(android.content.Context);
     method public static java.io.File![] getExternalFilesDirs(android.content.Context, String?);
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 77a0578..a9d425f 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -1078,6 +1078,7 @@
     method @ColorInt public static int getColor(android.content.Context, @ColorRes int);
     method public static android.content.res.ColorStateList? getColorStateList(android.content.Context, @ColorRes int);
     method public static java.io.File? getDataDir(android.content.Context);
+    method public static android.view.Display getDisplay(@DisplayContext android.content.Context);
     method public static android.graphics.drawable.Drawable? getDrawable(android.content.Context, @DrawableRes int);
     method public static java.io.File![] getExternalCacheDirs(android.content.Context);
     method public static java.io.File![] getExternalFilesDirs(android.content.Context, String?);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 9d411f1..6a02dd0 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1195,6 +1195,7 @@
     method @ColorInt public static int getColor(android.content.Context, @ColorRes int);
     method public static android.content.res.ColorStateList? getColorStateList(android.content.Context, @ColorRes int);
     method public static java.io.File? getDataDir(android.content.Context);
+    method public static android.view.Display getDisplay(@DisplayContext android.content.Context);
     method public static android.graphics.drawable.Drawable? getDrawable(android.content.Context, @DrawableRes int);
     method public static java.io.File![] getExternalCacheDirs(android.content.Context);
     method public static java.io.File![] getExternalFilesDirs(android.content.Context, String?);
diff --git a/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java
index 85db291..42fce77 100644
--- a/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java
@@ -66,6 +66,7 @@
 import static android.content.Context.WIFI_P2P_SERVICE;
 import static android.content.Context.WIFI_SERVICE;
 import static android.content.Context.WINDOW_SERVICE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -137,6 +138,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -146,8 +148,10 @@
 
 import androidx.annotation.OptIn;
 import androidx.core.app.NotificationManagerCompat;
+import androidx.core.hardware.display.DisplayManagerCompat;
 import androidx.core.os.BuildCompat;
 import androidx.core.test.R;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -601,4 +605,58 @@
                             Manifest.permission.POST_NOTIFICATIONS));
         }
     }
+
+    @Test
+    public void testGetDisplayFromActivity() {
+        final Display actualDisplay = ContextCompat.getDisplay(mContext);
+        if (Build.VERSION.SDK_INT >= 30) {
+            assertEquals(mContext.getDisplay(), actualDisplay);
+        } else {
+            final WindowManager windowManager =
+                    (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
+            assertEquals(actualDisplay, windowManager.getDefaultDisplay());
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 17)
+    public void testGetDisplayFromDisplayContext() {
+        final DisplayManagerCompat displayManagerCompat = DisplayManagerCompat
+                .getInstance(mContext);
+        final Display defaultDisplay =  displayManagerCompat.getDisplay(Display.DEFAULT_DISPLAY);
+        final Context displayContext = mContext.createDisplayContext(defaultDisplay);
+
+        assertEquals(ContextCompat.getDisplay(displayContext), defaultDisplay);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 30)
+    public void testGetDisplayFromWindowContext() {
+        final Context windowContext = mContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+
+        assertEquals(ContextCompat.getDisplay(windowContext), windowContext.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromApplication() {
+        final Context applicationContext = ApplicationProvider.getApplicationContext();
+        final Context spyContext = spy(applicationContext);
+        final Display actualDisplay = ContextCompat.getDisplay(spyContext);
+
+        if (Build.VERSION.SDK_INT >= 30) {
+            verify(spyContext).getSystemService(eq(DisplayManager.class));
+
+            final Display defaultDisplay = DisplayManagerCompat.getInstance(spyContext)
+                    .getDisplay(Display.DEFAULT_DISPLAY);
+            assertEquals(defaultDisplay, actualDisplay);
+        } else {
+            final WindowManager windowManager =
+                    (WindowManager) spyContext.getSystemService(WINDOW_SERVICE);
+            // Don't verify if the returned display is the same instance because Application is
+            // not a DisplayContext and the framework always create a fallback Display for
+            // the Context that not associated with a Display.
+            assertEquals(windowManager.getDefaultDisplay().getDisplayId(),
+                    actualDisplay.getDisplayId());
+        }
+    }
 }
diff --git a/core/core/src/main/java/androidx/core/content/ContextCompat.java b/core/core/src/main/java/androidx/core/content/ContextCompat.java
index cd4e613..172b452 100644
--- a/core/core/src/main/java/androidx/core/content/ContextCompat.java
+++ b/core/core/src/main/java/androidx/core/content/ContextCompat.java
@@ -70,6 +70,7 @@
 
 import android.accounts.AccountManager;
 import android.annotation.SuppressLint;
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
@@ -131,6 +132,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.TypedValue;
+import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -140,6 +142,7 @@
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.ColorRes;
+import androidx.annotation.DisplayContext;
 import androidx.annotation.DoNotInline;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.IntDef;
@@ -753,6 +756,28 @@
     }
 
     /**
+     * Get the display this context is associated with.
+     * <p>
+     * Applications must use this method with {@link Activity} or a context associated with a
+     * {@link Display} via {@link Context#createDisplayContext(Display)} or
+     * {@link Context#createWindowContext(Display, int, Bundle)}, or the reported {@link Display}
+     * instance is not reliable. </p>
+     *
+     * @param context Context to obtain the associated display
+     * @return The display associated with the Context.
+     */
+    @NonNull
+    public static Display getDisplay(@NonNull @DisplayContext Context context) {
+        if (Build.VERSION.SDK_INT >= 30) {
+            return Api30Impl.getDisplayNoCrash(context);
+        } else {
+            final WindowManager windowManager =
+                    (WindowManager) context.getSystemService(WINDOW_SERVICE);
+            return windowManager.getDefaultDisplay();
+        }
+    }
+
+    /**
      * Return the handle to a system-level service by class.
      *
      * @param context      Context to retrieve service from.
@@ -1113,6 +1138,19 @@
         static String getAttributionTag(Context obj) {
             return obj.getAttributionTag();
         }
+
+        @DoNotInline
+        static Display getDisplayNoCrash(Context obj) {
+            try {
+                return obj.getDisplay();
+            } catch (UnsupportedOperationException e) {
+                // Provide a fallback display if the context is not associated with any display.
+                Log.w(TAG, "The context:" + obj + " is not associated with any display. Return a "
+                        + "fallback display instead.");
+                return obj.getSystemService(DisplayManager.class)
+                        .getDisplay(Display.DEFAULT_DISPLAY);
+            }
+        }
     }
 
     @RequiresApi(33)
diff --git a/development/project-creator/compose-template/groupId/artifactId/build.gradle b/development/project-creator/compose-template/groupId/artifactId/build.gradle
index a47774d..e09ec31 100644
--- a/development/project-creator/compose-template/groupId/artifactId/build.gradle
+++ b/development/project-creator/compose-template/groupId/artifactId/build.gradle
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-import androidx.build.AndroidXComposePlugin
 import androidx.build.LibraryType
+import androidx.build.KmpPlatformsKt
 
 plugins {
     id("AndroidXPlugin")
@@ -24,68 +24,75 @@
     id("org.jetbrains.kotlin.android")
 }
 
-AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project)
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
 
-dependencies {
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 
-
-    if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block below
-         */
-        implementation(libs.kotlinStdlibCommon)
-
-        api("androidx.annotation:annotation:1.1.0")
-
-        testImplementation(libs.testRules)
-        testImplementation(libs.testRunner)
-        testImplementation(libs.junit)
-        testImplementation(libs.truth)
-
-        androidTestImplementation(libs.testRules)
-        androidTestImplementation(libs.testRunner)
-        androidTestImplementation(libs.junit)
-        androidTestImplementation(libs.truth)
-    }
-}
-
-if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
-    androidXComposeMultiplatform {
-        android()
-        desktop()
-    }
-
-    kotlin {
-        /*
-         * When updating dependencies, make sure to make the an an analogous update in the
-         * corresponding block above
-         */
-        sourceSets {
-            commonMain.dependencies {
+    sourceSets {
+        commonMain {
+            dependencies {
                 implementation(libs.kotlinStdlibCommon)
             }
+        }
+        androidMain.dependencies {
+        }
 
-            androidMain.dependencies {
+        commonTest {
+            dependencies {
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+                implementation(libs.truth)
+            }
+        }
+
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
                 api("androidx.annotation:annotation:1.1.0")
             }
+        }
 
-            desktopMain.dependencies {
-                implementation(libs.kotlinStdlib)
+        if (desktopEnabled) {
+            desktopMain {
+                dependsOn(jvmMain)
+                dependencies {
+                    implementation(libs.kotlinStdlib)
+                }
             }
+        }
 
-            test.dependencies {
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidAndroidTest {
+            dependsOn(jvmTest)
+            dependencies {
                 implementation(libs.testRules)
                 implementation(libs.testRunner)
                 implementation(libs.junit)
                 implementation(libs.truth)
             }
+        }
 
-            androidAndroidTest.dependencies {
-                implementation(libs.testRules)
-                implementation(libs.testRunner)
-                implementation(libs.junit)
-                implementation(libs.truth)
+        if (desktopEnabled) {
+            desktopTest {
+                dependsOn(jvmTest)
+                dependsOn(desktopMain)
+                dependencies {
+                }
             }
         }
     }
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java
index e75969a..5130a65 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiInputFilterTest.java
@@ -30,13 +30,19 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.widget.EditText;
 import android.widget.TextView;
 
 import androidx.emoji.text.EmojiCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -126,4 +132,31 @@
         verify(mEmojiCompat, times(0)).process(any(Spannable.class), anyInt(), anyInt());
         verify(mEmojiCompat, times(1)).registerInitCallback(any(EmojiCompat.InitCallback.class));
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void initCallback_doesntCrashWhenNotAttached() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        EditText editText = new EditText(context);
+        EmojiInputFilter subject = new EmojiInputFilter(editText);
+        subject.getInitCallback().onInitialized();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    public void initCallback_sendsToNonMainHandler_beforeSetText() {
+        // this is just testing that onInitialized dispatches to editText.getHandler before setText
+        EditText mockEditText = mock(EditText.class);
+        HandlerThread thread = new HandlerThread("random thread");
+        thread.start();
+        Handler handler = new Handler(thread.getLooper());
+        thread.quitSafely();
+        when(mockEditText.getHandler()).thenReturn(handler);
+        EmojiInputFilter subject = new EmojiInputFilter(mockEditText);
+        EmojiInputFilter.InitCallbackImpl initCallback =
+                (EmojiInputFilter.InitCallbackImpl) subject.getInitCallback();
+        initCallback.onInitialized();
+
+        handler.hasCallbacks(initCallback);
+    }
 }
diff --git a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java
index fe3da64..50b0169 100644
--- a/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java
+++ b/emoji/emoji/src/androidTest/java/androidx/emoji/widget/EmojiTextWatcherTest.java
@@ -27,13 +27,18 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.widget.EditText;
 
 import androidx.emoji.text.EmojiCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -41,6 +46,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 19)
 public class EmojiTextWatcherTest {
 
     private EmojiTextWatcher mTextWatcher;
@@ -120,4 +126,31 @@
         verify(mEmojiCompat, times(0)).process(any(Spannable.class), anyInt(), anyInt());
         verify(mEmojiCompat, times(1)).registerInitCallback(any(EmojiCompat.InitCallback.class));
     }
+
+    @Test
+    public void initCallback_doesntCrashWhenNotAttached() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        EditText editText = new EditText(context);
+        EmojiTextWatcher subject = new EmojiTextWatcher(editText);
+        subject.getInitCallback().onInitialized();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    public void initCallback_sendsToNonMainHandler_beforeSetText() {
+        // this is just testing that onInitialized dispatches to editText.getHandler before setText
+        EditText mockEditText = mock(EditText.class);
+        HandlerThread thread = new HandlerThread("random thread");
+        thread.start();
+        Handler handler = new Handler(thread.getLooper());
+        thread.quitSafely();
+        when(mockEditText.getHandler()).thenReturn(handler);
+        EmojiTextWatcher subject = new EmojiTextWatcher(mockEditText);
+        EmojiTextWatcher.InitCallbackImpl initCallback =
+                (EmojiTextWatcher.InitCallbackImpl) subject.getInitCallback();
+        initCallback.onInitialized();
+
+        handler.hasCallbacks(initCallback);
+    }
 }
+
diff --git a/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiInputFilter.java b/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiInputFilter.java
index 27d1aa0..25f9478 100644
--- a/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiInputFilter.java
+++ b/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiInputFilter.java
@@ -15,8 +15,10 @@
  */
 package androidx.emoji.widget;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
 
+import android.os.Handler;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.Spanned;
@@ -88,14 +90,16 @@
         }
     }
 
-    private InitCallback getInitCallback() {
+    @RestrictTo(LIBRARY)
+    InitCallback getInitCallback() {
         if (mInitCallback == null) {
             mInitCallback = new InitCallbackImpl(mTextView);
         }
         return mInitCallback;
     }
 
-    private static class InitCallbackImpl extends InitCallback {
+    @RestrictTo(LIBRARY)
+    static class InitCallbackImpl extends InitCallback implements Runnable {
         private final Reference<TextView> mViewRef;
 
         InitCallbackImpl(TextView textView) {
@@ -106,7 +110,23 @@
         public void onInitialized() {
             super.onInitialized();
             final TextView textView = mViewRef.get();
-            if (textView != null && textView.isAttachedToWindow()) {
+            if (textView == null) {
+                return;
+            }
+            // we need to move to the actual thread this view is using as main
+            Handler handler = textView.getHandler();
+            if (handler != null) {
+                handler.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
+            final TextView textView = mViewRef.get();
+            if (textView == null) {
+                return;
+            }
+            if (textView.isAttachedToWindow()) {
                 final CharSequence result = EmojiCompat.get().process(textView.getText());
 
                 final int selectionStart = Selection.getSelectionStart(result);
diff --git a/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiTextWatcher.java b/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiTextWatcher.java
index 2a4572f..6e6fcf3 100644
--- a/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiTextWatcher.java
+++ b/emoji/emoji/src/main/java/androidx/emoji/widget/EmojiTextWatcher.java
@@ -15,8 +15,10 @@
  */
 package androidx.emoji.widget;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
 
+import android.os.Handler;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.Spannable;
@@ -99,14 +101,16 @@
         // do nothing
     }
 
-    private InitCallback getInitCallback() {
+    @RestrictTo(LIBRARY)
+    InitCallback getInitCallback() {
         if (mInitCallback == null) {
             mInitCallback = new InitCallbackImpl(mEditText);
         }
         return mInitCallback;
     }
 
-    private static class InitCallbackImpl extends InitCallback {
+    @RestrictTo(LIBRARY)
+    static class InitCallbackImpl extends InitCallback implements Runnable {
         private final Reference<EditText> mViewRef;
 
         InitCallbackImpl(EditText editText) {
@@ -117,6 +121,19 @@
         public void onInitialized() {
             super.onInitialized();
             final EditText editText = mViewRef.get();
+            if (editText == null) {
+                return;
+            }
+            Handler handler = editText.getHandler();
+            if (handler == null) {
+                return;
+            }
+            handler.post(this);
+        }
+
+        @Override
+        public void run() {
+            final EditText editText = mViewRef.get();
             if (editText != null && editText.isAttachedToWindow()) {
                 final Editable text = editText.getEditableText();
 
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java
index 3349e6d..a21adf7 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiInputFilterTest.java
@@ -30,9 +30,13 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.text.InputFilter;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.widget.EditText;
 import android.widget.TextView;
 
 import androidx.emoji2.text.EmojiCompat;
@@ -40,6 +44,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -191,7 +196,7 @@
 
         when(mEmojiCompat.getLoadState()).thenReturn(EmojiCompat.LOAD_STATE_SUCCEEDED);
         // trigger initialized
-        captor.getValue().onInitialized();
+        ((Runnable) captor.getValue()).run();
 
         verify(mEmojiCompat).process(eq(testString));
     }
@@ -223,7 +228,7 @@
         when(mEmojiCompat.process(eq(testString))).thenReturn(testString);
         when(mEmojiCompat.getLoadState()).thenReturn(EmojiCompat.LOAD_STATE_SUCCEEDED);
         // trigger initialized
-        captor.getValue().onInitialized();
+        ((Runnable) captor.getValue()).run();
 
         // validate interactions don't do anything except check for update
         verify(mTextView).getFilters();
@@ -233,4 +238,31 @@
         // if you add a safe interaction please update test
         verifyNoMoreInteractions(mTextView);
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void initCallback_doesntCrashWhenNotAttached() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        EditText editText = new EditText(context);
+        EmojiInputFilter subject = new EmojiInputFilter(editText);
+        subject.getInitCallback().onInitialized();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    public void initCallback_sendsToNonMainHandler_beforeSetText() {
+        // this is just testing that onInitialized dispatches to editText.getHandler before setText
+        EditText mockEditText = mock(EditText.class);
+        HandlerThread thread = new HandlerThread("random thread");
+        thread.start();
+        Handler handler = new Handler(thread.getLooper());
+        thread.quitSafely();
+        when(mockEditText.getHandler()).thenReturn(handler);
+        EmojiInputFilter subject = new EmojiInputFilter(mockEditText);
+        EmojiInputFilter.InitCallbackImpl initCallback =
+                (EmojiInputFilter.InitCallbackImpl) subject.getInitCallback();
+        initCallback.onInitialized();
+
+        handler.hasCallbacks(initCallback);
+    }
 }
diff --git a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java
index 8f149da..5c3c5de 100644
--- a/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java
+++ b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/viewsintegration/EmojiTextWatcherTest.java
@@ -27,6 +27,9 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
@@ -37,6 +40,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -149,4 +153,30 @@
         mTextWatcher.onTextChanged(expected, 0, 0, 1);
         assertTrue(TextUtils.equals(expected, "abc"));
     }
+
+    @Test
+    public void initCallback_doesntCrashWhenNotAttached() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        EditText editText = new EditText(context);
+        EmojiTextWatcher subject = new EmojiTextWatcher(editText, false);
+        subject.getInitCallback().onInitialized();
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    public void initCallback_sendsToNonMainHandler_beforeSetText() {
+        // this is just testing that onInitialized dispatches to editText.getHandler before setText
+        EditText mockEditText = mock(EditText.class);
+        HandlerThread thread = new HandlerThread("random thread");
+        thread.start();
+        Handler handler = new Handler(thread.getLooper());
+        thread.quitSafely();
+        when(mockEditText.getHandler()).thenReturn(handler);
+        EmojiTextWatcher subject = new EmojiTextWatcher(mockEditText, false);
+        EmojiTextWatcher.InitCallbackImpl initCallback =
+                (EmojiTextWatcher.InitCallbackImpl) subject.getInitCallback();
+        initCallback.onInitialized();
+
+        handler.hasCallbacks(initCallback);
+    }
 }
diff --git a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiInputFilter.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiInputFilter.java
index 530fc52..9c9b485 100644
--- a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiInputFilter.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiInputFilter.java
@@ -15,6 +15,9 @@
  */
 package androidx.emoji2.viewsintegration;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.os.Handler;
 import android.text.InputFilter;
 import android.text.Selection;
 import android.text.Spannable;
@@ -39,7 +42,7 @@
  * effects.
  *
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
+@RestrictTo(LIBRARY)
 @RequiresApi(19)
 final class EmojiInputFilter implements android.text.InputFilter {
     private final TextView mTextView;
@@ -88,15 +91,17 @@
         }
     }
 
-    private InitCallback getInitCallback() {
+    @RestrictTo(LIBRARY)
+    InitCallback getInitCallback() {
         if (mInitCallback == null) {
             mInitCallback = new InitCallbackImpl(mTextView, this);
         }
         return mInitCallback;
     }
 
+    @RestrictTo(LIBRARY)
     @RequiresApi(19)
-    private static class InitCallbackImpl extends InitCallback {
+    static class InitCallbackImpl extends InitCallback implements Runnable  {
         private final Reference<TextView> mViewRef;
         private final Reference<EmojiInputFilter> mEmojiInputFilterReference;
 
@@ -109,6 +114,19 @@
         @Override
         public void onInitialized() {
             super.onInitialized();
+            final TextView textView = mViewRef.get();
+            if (textView == null) {
+                return;
+            }
+            // we need to move to the actual thread this view is using as main
+            Handler handler = textView.getHandler();
+            if (handler != null) {
+                handler.post(this);
+            }
+        }
+
+        @Override
+        public void run() {
             @Nullable final TextView textView = mViewRef.get();
             @Nullable final InputFilter myInputFilter = mEmojiInputFilterReference.get();
             if (!isInputFilterCurrentlyRegisteredOnTextView(textView, myInputFilter)) return;
diff --git a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextWatcher.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextWatcher.java
index 55c37da..691f995 100644
--- a/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextWatcher.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/viewsintegration/EmojiTextWatcher.java
@@ -15,6 +15,9 @@
  */
 package androidx.emoji2.viewsintegration;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.os.Handler;
 import android.text.Editable;
 import android.text.Selection;
 import android.text.Spannable;
@@ -34,7 +37,7 @@
  * TextWatcher used for an EditText.
  *
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
+@RestrictTo(LIBRARY)
 @RequiresApi(19)
 final class EmojiTextWatcher implements android.text.TextWatcher {
     private final EditText mEditText;
@@ -107,7 +110,11 @@
         // do nothing
     }
 
-    private InitCallback getInitCallback() {
+    /**
+     * @return
+     */
+    @RestrictTo(LIBRARY)
+    InitCallback getInitCallback() {
         if (mInitCallback == null) {
             mInitCallback = new InitCallbackImpl(mEditText);
         }
@@ -130,8 +137,9 @@
         }
     }
 
+    @RestrictTo(LIBRARY)
     @RequiresApi(19)
-    private static class InitCallbackImpl extends InitCallback {
+    static class InitCallbackImpl extends InitCallback implements Runnable {
         private final Reference<EditText> mViewRef;
 
         InitCallbackImpl(EditText editText) {
@@ -142,6 +150,19 @@
         public void onInitialized() {
             super.onInitialized();
             final EditText editText = mViewRef.get();
+            if (editText == null) {
+                return;
+            }
+            Handler handler = editText.getHandler();
+            if (handler == null) {
+                return;
+            }
+            handler.post(this);
+        }
+
+        @Override
+        public void run() {
+            final EditText editText = mViewRef.get();
             processTextOnEnablingEvent(editText, EmojiCompat.LOAD_STATE_SUCCEEDED);
         }
     }
diff --git a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
index 8a7e238..495d89f 100644
--- a/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
+++ b/javascriptengine/javascriptengine/src/androidTest/java/androidx/javascriptengine/WebViewJavaScriptSandboxTest.java
@@ -17,9 +17,12 @@
 package androidx.javascriptengine;
 
 import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.webkit.WebView;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
+import androidx.core.content.pm.PackageInfoCompat;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
@@ -53,6 +56,18 @@
         Assume.assumeTrue(JavaScriptSandbox.isSupported());
     }
 
+    // Get the current WebView provider version. In a versionCode of AAAABBBCD, AAAA is the build
+    // number and BBB is the patch number. C and D may usually be ignored.
+    //
+    // Strongly prefer using feature flags over version checks if possible.
+    public long getWebViewVersion() {
+        PackageInfo systemWebViewPackage = WebView.getCurrentWebViewPackage();
+        if (systemWebViewPackage == null) {
+            Assert.fail("No current WebView provider");
+        }
+        return PackageInfoCompat.getLongVersionCode(systemWebViewPackage);
+    }
+
     @Test
     @MediumTest
     public void testSimpleJsEvaluation() throws Throwable {
@@ -571,6 +586,14 @@
     @Test
     @LargeTest
     public void testHeapSizeEnforced() throws Throwable {
+        // WebView versions < 110.0.5438.0 do not contain OOM crashes to a single isolate and
+        // instead crash the whole sandbox process. This change is not tracked in a feature flag.
+        // Versions < 110.0.5438.0 are not considered to be broken, but their behavior is not
+        // of interest for this test.
+        // See Chromium change: https://chromium-review.googlesource.com/c/chromium/src/+/4047785
+        Assume.assumeTrue("WebView version does not support per-isolate OOM handling",
+                getWebViewVersion() >= 5438_000_00L);
+
         final long maxHeapSize = REASONABLE_HEAP_SIZE;
         // We need to beat the v8 optimizer to ensure it really allocates the required memory. Note
         // that we're allocating an array of elements - not bytes. Filling will ensure that the
@@ -587,6 +610,7 @@
         try (JavaScriptSandbox jsSandbox = jsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
             Assume.assumeTrue(jsSandbox.isFeatureSupported(
                     JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
+
             Assume.assumeTrue(
                     jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
             IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
@@ -657,6 +681,14 @@
     @Test
     @LargeTest
     public void testIsolateCreationAfterCrash() throws Throwable {
+        // WebView versions < 110.0.5438.0 do not contain OOM crashes to a single isolate and
+        // instead crash the whole sandbox process. This change is not tracked in a feature flag.
+        // Versions < 110.0.5438.0 are not considered to be broken, but their behavior is not
+        // of interest for this test.
+        // See Chromium change: https://chromium-review.googlesource.com/c/chromium/src/+/4047785
+        Assume.assumeTrue("WebView version does not support per-isolate OOM handling",
+                getWebViewVersion() >= 5438_000_00L);
+
         final long maxHeapSize = REASONABLE_HEAP_SIZE;
         // We need to beat the v8 optimizer to ensure it really allocates the required memory. Note
         // that we're allocating an array of elements - not bytes. Filling will ensure that the
diff --git a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
index 00afeb8..a4432f0 100644
--- a/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
+++ b/javascriptengine/javascriptengine/src/main/java/androidx/javascriptengine/JavaScriptIsolate.java
@@ -162,13 +162,23 @@
         @Override
         public void reportResult(String result) {
             Objects.requireNonNull(result);
-            handleEvaluationResult(mCompleter, result);
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                handleEvaluationResult(mCompleter, result);
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
         }
 
         @Override
         public void reportError(@ExecutionErrorTypes int type, String error) {
             Objects.requireNonNull(error);
-            handleEvaluationError(mCompleter, type, error);
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                handleEvaluationError(mCompleter, type, error);
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
         }
     }
 
diff --git a/lifecycle/lifecycle-runtime-compose/api/current.txt b/lifecycle/lifecycle-runtime-compose/api/current.txt
index 5b19f2b..41aee87c 100644
--- a/lifecycle/lifecycle-runtime-compose/api/current.txt
+++ b/lifecycle/lifecycle-runtime-compose/api/current.txt
@@ -10,11 +10,31 @@
 
   public final class LifecycleEffectKt {
     method @androidx.compose.runtime.Composable public static void LifecycleEventEffect(androidx.lifecycle.Lifecycle.Event event, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> onEvent);
+    method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseEffectResult> effects);
+    method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopEffectResult> effects);
   }
 
   public final class LifecycleExtKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.lifecycle.Lifecycle.State> currentStateAsState(androidx.lifecycle.Lifecycle);
   }
 
+  public interface LifecyclePauseEffectResult {
+    method public void runPauseEffect();
+  }
+
+  public final class LifecycleResumePauseEffectScope {
+    ctor public LifecycleResumePauseEffectScope();
+    method public inline androidx.lifecycle.compose.LifecyclePauseEffectResult onPause(kotlin.jvm.functions.Function0<kotlin.Unit> onPauseEffect);
+  }
+
+  public final class LifecycleStartStopEffectScope {
+    ctor public LifecycleStartStopEffectScope();
+    method public inline androidx.lifecycle.compose.LifecycleStopEffectResult onStop(kotlin.jvm.functions.Function0<kotlin.Unit> onStopEffect);
+  }
+
+  public interface LifecycleStopEffectResult {
+    method public void runStopEffect();
+  }
+
 }
 
diff --git a/lifecycle/lifecycle-runtime-compose/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-runtime-compose/api/public_plus_experimental_current.txt
index 5b19f2b..41aee87c 100644
--- a/lifecycle/lifecycle-runtime-compose/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-runtime-compose/api/public_plus_experimental_current.txt
@@ -10,11 +10,31 @@
 
   public final class LifecycleEffectKt {
     method @androidx.compose.runtime.Composable public static void LifecycleEventEffect(androidx.lifecycle.Lifecycle.Event event, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> onEvent);
+    method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseEffectResult> effects);
+    method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopEffectResult> effects);
   }
 
   public final class LifecycleExtKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.lifecycle.Lifecycle.State> currentStateAsState(androidx.lifecycle.Lifecycle);
   }
 
+  public interface LifecyclePauseEffectResult {
+    method public void runPauseEffect();
+  }
+
+  public final class LifecycleResumePauseEffectScope {
+    ctor public LifecycleResumePauseEffectScope();
+    method public inline androidx.lifecycle.compose.LifecyclePauseEffectResult onPause(kotlin.jvm.functions.Function0<kotlin.Unit> onPauseEffect);
+  }
+
+  public final class LifecycleStartStopEffectScope {
+    ctor public LifecycleStartStopEffectScope();
+    method public inline androidx.lifecycle.compose.LifecycleStopEffectResult onStop(kotlin.jvm.functions.Function0<kotlin.Unit> onStopEffect);
+  }
+
+  public interface LifecycleStopEffectResult {
+    method public void runStopEffect();
+  }
+
 }
 
diff --git a/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt b/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt
index 5b19f2b..41aee87c 100644
--- a/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime-compose/api/restricted_current.txt
@@ -10,11 +10,31 @@
 
   public final class LifecycleEffectKt {
     method @androidx.compose.runtime.Composable public static void LifecycleEventEffect(androidx.lifecycle.Lifecycle.Event event, optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function0<kotlin.Unit> onEvent);
+    method @androidx.compose.runtime.Composable public static void LifecycleResumeEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleResumePauseEffectScope,? extends androidx.lifecycle.compose.LifecyclePauseEffectResult> effects);
+    method @androidx.compose.runtime.Composable public static void LifecycleStartEffect(optional androidx.lifecycle.LifecycleOwner lifecycleOwner, kotlin.jvm.functions.Function1<? super androidx.lifecycle.compose.LifecycleStartStopEffectScope,? extends androidx.lifecycle.compose.LifecycleStopEffectResult> effects);
   }
 
   public final class LifecycleExtKt {
     method @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<androidx.lifecycle.Lifecycle.State> currentStateAsState(androidx.lifecycle.Lifecycle);
   }
 
+  public interface LifecyclePauseEffectResult {
+    method public void runPauseEffect();
+  }
+
+  public final class LifecycleResumePauseEffectScope {
+    ctor public LifecycleResumePauseEffectScope();
+    method public inline androidx.lifecycle.compose.LifecyclePauseEffectResult onPause(kotlin.jvm.functions.Function0<kotlin.Unit> onPauseEffect);
+  }
+
+  public final class LifecycleStartStopEffectScope {
+    ctor public LifecycleStartStopEffectScope();
+    method public inline androidx.lifecycle.compose.LifecycleStopEffectResult onStop(kotlin.jvm.functions.Function0<kotlin.Unit> onStopEffect);
+  }
+
+  public interface LifecycleStopEffectResult {
+    method public void runStopEffect();
+  }
+
 }
 
diff --git a/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt b/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt
index 3d7e9fe..482c851 100644
--- a/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/androidTest/java/androidx/lifecycle/compose/LifecycleEffectTest.kt
@@ -110,4 +110,78 @@
                 .isEqualTo(1)
         }
     }
+
+    @Test
+    fun lifecycleStartEffectTest() {
+        lifecycleOwner = TestLifecycleOwner(
+            Lifecycle.State.INITIALIZED,
+            dispatcher
+        )
+        var startCount = 0
+        var stopCount = 0
+
+        composeTestRule.waitForIdle()
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
+                LifecycleStartEffect {
+                    startCount++
+
+                    onStop {
+                        stopCount++
+                    }
+                }
+            }
+        }
+
+        composeTestRule.runOnIdle {
+            assertWithMessage("Lifecycle should not be started (or stopped)")
+                .that(startCount)
+                .isEqualTo(0)
+
+            lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START)
+            assertWithMessage("Lifecycle should have been started")
+                .that(startCount)
+                .isEqualTo(1)
+
+            lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+            assertWithMessage("Lifecycle should have been stopped")
+                .that(stopCount)
+                .isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun lifecycleResumeEffectTest() {
+        var resumeCount = 0
+        var pauseCount = 0
+
+        composeTestRule.waitForIdle()
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
+                LifecycleResumeEffect {
+                    resumeCount++
+
+                    onPause {
+                        pauseCount++
+                    }
+                }
+            }
+        }
+
+        composeTestRule.runOnIdle {
+            assertWithMessage("Lifecycle should not be resumed (or paused)")
+                .that(resumeCount)
+                .isEqualTo(0)
+
+            lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+            assertWithMessage("Lifecycle should have been resumed")
+                .that(resumeCount)
+                .isEqualTo(1)
+
+            lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+            assertWithMessage("Lifecycle should have been paused")
+                .that(pauseCount)
+                .isEqualTo(1)
+        }
+    }
 }
\ No newline at end of file
diff --git a/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt b/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt
index c9e47ae..245b1a5 100644
--- a/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt
+++ b/lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/LifecycleEffect.kt
@@ -75,4 +75,176 @@
             lifecycleOwner.lifecycle.removeObserver(observer)
         }
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Schedule a pair of effects to run when the [Lifecycle] receives either a
+ * [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP]. The ON_START effect will
+ * be the body of the [effects] block and the ON_STOP effect will be within the
+ * (onStop clause)[LifecycleStartStopEffectScope.onStop]:
+ *
+ * LifecycleStartEffect(lifecycleOwner) {
+ *     // add ON_START effect work here
+ *
+ *     onStop {
+ *         // add ON_STOP effect work here
+ *     }
+ * }
+ *
+ * A [LifecycleStartEffect] **must** include an [onStop][LifecycleStartStopEffectScope.onStop]
+ * clause as the final statement in its [effects] block. If your operation does not require
+ * an effect for both ON_START and ON_STOP, a [LifecycleEventEffect] should be used instead.
+ *
+ * This function uses a [LifecycleEventObserver] to listen for when [LifecycleStartEffect]
+ * enters the composition and the effects will be launched when receiving a
+ * [Lifecycle.Event.ON_START] or [Lifecycle.Event.ON_STOP] event, respectively.
+ *
+ * This function should **not** be used to launch tasks in response to callback
+ * events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
+ * Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
+ * that may be used to launch jobs in response to state changes.
+ *
+ * @param lifecycleOwner The lifecycle owner to attach an observer
+ * @param effects The effects to be launched when we receive the respective event callbacks
+ */
+@Composable
+fun LifecycleStartEffect(
+    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+    effects: LifecycleStartStopEffectScope.() -> LifecycleStopEffectResult
+) {
+    val lifecycleStartStopEffectScope = LifecycleStartStopEffectScope()
+    // Safely update the current `onStart` lambda when a new one is provided
+    val currentEffects by rememberUpdatedState(effects)
+    DisposableEffect(lifecycleOwner) {
+        val observer = LifecycleEventObserver { _, event ->
+            when (event) {
+                Lifecycle.Event.ON_START ->
+                    lifecycleStartStopEffectScope.currentEffects()
+
+                Lifecycle.Event.ON_STOP ->
+                    lifecycleStartStopEffectScope.currentEffects().runStopEffect()
+
+                else -> {}
+            }
+        }
+
+        lifecycleOwner.lifecycle.addObserver(observer)
+
+        onDispose {
+            lifecycleOwner.lifecycle.removeObserver(observer)
+        }
+    }
+}
+
+/**
+ * Interface used for [LifecycleStartEffect] to run the effect within the onStop
+ * clause when an (ON_STOP)[Lifecycle.Event.ON_STOP] event is received.
+ */
+interface LifecycleStopEffectResult {
+    fun runStopEffect()
+}
+
+/**
+ * Receiver scope for [LifecycleStartEffect] that offers the [onStop] clause to
+ * couple the ON_START effect. This should be the last statement in any call to
+ * [LifecycleStartEffect].
+ */
+class LifecycleStartStopEffectScope {
+    /**
+     * Provide the [onStopEffect] to the [LifecycleStartEffect] to run when the observer
+     * receives an (ON_STOP)[Lifecycle.Event.ON_STOP] event.
+     */
+    inline fun onStop(
+        crossinline onStopEffect: () -> Unit
+    ): LifecycleStopEffectResult = object : LifecycleStopEffectResult {
+        override fun runStopEffect() {
+            onStopEffect()
+        }
+    }
+}
+
+/**
+ * Schedule a pair of effects to run when the [Lifecycle] receives either a
+ * [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE]. The ON_RESUME effect
+ * will be the body of the [effects] block and the ON_PAUSE effect will be within the
+ * (onPause clause)[LifecycleResumePauseEffectScope.onPause]:
+ *
+ * LifecycleResumeEffect(lifecycleOwner) {
+ *     // add ON_RESUME effect work here
+ *
+ *     onPause {
+ *         // add ON_PAUSE effect work here
+ *     }
+ * }
+ *
+ * A [LifecycleResumeEffect] **must** include an [onPause][LifecycleResumePauseEffectScope.onPause]
+ * clause as the final statement in its [effects] block. If your operation does not require
+ * an effect for both ON_RESUME and ON_PAUSE, a [LifecycleEventEffect] should be used instead.
+ *
+ * This function uses a [LifecycleEventObserver] to listen for when [LifecycleResumeEffect]
+ * enters the composition and the effects will be launched when receiving a
+ * [Lifecycle.Event.ON_RESUME] or [Lifecycle.Event.ON_PAUSE] event, respectively.
+ *
+ * This function should **not** be used to launch tasks in response to callback
+ * events by way of storing callback data as a [Lifecycle.State] in a [MutableState].
+ * Instead, see [currentStateAsState] to obtain a [State<Lifecycle.State>][State]
+ * that may be used to launch jobs in response to state changes.
+ *
+ * @param lifecycleOwner The lifecycle owner to attach an observer
+ * @param effects The effects to be launched when we receive the respective event callbacks
+ */
+@Composable
+fun LifecycleResumeEffect(
+    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
+    effects: LifecycleResumePauseEffectScope.() -> LifecyclePauseEffectResult
+) {
+    val lifecycleResumePauseEffectScope = LifecycleResumePauseEffectScope()
+    // Safely update the current `onResume` lambda when a new one is provided
+    val currentEffects by rememberUpdatedState(effects)
+    DisposableEffect(lifecycleOwner) {
+        val observer = LifecycleEventObserver { _, event ->
+            when (event) {
+                Lifecycle.Event.ON_RESUME ->
+                    lifecycleResumePauseEffectScope.currentEffects()
+
+                Lifecycle.Event.ON_PAUSE ->
+                    lifecycleResumePauseEffectScope.currentEffects().runPauseEffect()
+
+                else -> {}
+            }
+        }
+
+        lifecycleOwner.lifecycle.addObserver(observer)
+
+        onDispose {
+            lifecycleOwner.lifecycle.removeObserver(observer)
+        }
+    }
+}
+
+/**
+ * Interface used for [LifecycleResumeEffect] to run the effect within the onPause
+ * clause when an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event is received.
+ */
+interface LifecyclePauseEffectResult {
+    fun runPauseEffect()
+}
+
+/**
+ * Receiver scope for [LifecycleResumeEffect] that offers the [onPause] clause to
+ * couple the ON_RESUME effect. This should be the last statement in any call to
+ * [LifecycleResumeEffect].
+ */
+class LifecycleResumePauseEffectScope {
+    /**
+     * Provide the [onPauseEffect] to the [LifecycleResumeEffect] to run when the observer
+     * receives an (ON_PAUSE)[Lifecycle.Event.ON_PAUSE] event.
+     */
+    inline fun onPause(
+        crossinline onPauseEffect: () -> Unit
+    ): LifecyclePauseEffectResult = object : LifecyclePauseEffectResult {
+        override fun runPauseEffect() {
+            onPauseEffect()
+        }
+    }
+}
diff --git a/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt b/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
index a365b00..16e56b7 100644
--- a/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/aidl/AidlDefinitionDetector.kt
@@ -44,19 +44,32 @@
     override fun getApplicableFiles() = Scope.OTHER_SCOPE
 
     override fun beforeCheckEachProject(context: Context) {
-        LanguageParserDefinitions.INSTANCE.apply {
+        // Neither LanguageParserDefinitions nor CoreFileTypeRegistry are thread-safe, so this is a
+        // best-effort to avoid a race condition during our own access across multiple lint worker
+        // threads.
+        synchronized(intellijCoreLock) {
+            val aidlFileType = AidlFileType.INSTANCE
+
             // When we run from CLI, the IntelliJ parser (which does not support lexing AIDL) will
             // already be set. Only the first parser will be used, so we need to remove that parser
             // before we add our own.
-            allForLanguage(AidlFileType.INSTANCE.language).forEach { parser ->
-                removeExplicitExtension(AidlFileType.INSTANCE.language, parser)
+            val languageParserDefinitions = LanguageParserDefinitions.INSTANCE
+            languageParserDefinitions.apply {
+                allForLanguage(aidlFileType.language).forEach { parser ->
+                    removeExplicitExtension(aidlFileType.language, parser)
+                }
+                addExplicitExtension(aidlFileType.language, AidlParserDefinition())
             }
-            addExplicitExtension(AidlFileType.INSTANCE.language, AidlParserDefinition())
+
+            // Register our parser for the AIDL file type. Files may be registered more than once to
+            // overwrite the associated extension, but only the first call to `registerFileType`
+            // will associate the file with the name returned by `FileType.getName()`.
+            val coreFileTypeRegistry = CoreFileTypeRegistry.getInstance() as CoreFileTypeRegistry
+            coreFileTypeRegistry.registerFileType(
+                aidlFileType,
+                aidlFileType.defaultExtension
+            )
         }
-        (CoreFileTypeRegistry.getInstance() as CoreFileTypeRegistry).registerFileType(
-            AidlFileType.INSTANCE,
-            AidlFileType.INSTANCE.defaultExtension
-        )
     }
 
     override fun run(context: Context) {
@@ -125,4 +138,10 @@
     containingFile.text,
     textRange.startOffset,
     textRange.endOffset
-)
\ No newline at end of file
+)
+
+/**
+ * Lock object used to synchronize access to IntelliJ registries which are not thread-safe,
+ * including [LanguageParserDefinitions] and [CoreFileTypeRegistry].
+ */
+private val intellijCoreLock = Any()
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.kt b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.kt
index 083d17d..e820bfe 100644
--- a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.kt
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Color
 import android.os.Bundle
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -27,6 +28,7 @@
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.FragmentNavigatorExtras
 import androidx.navigation.fragment.findNavController
+import androidx.transition.Slide
 
 /**
  * Fragment used to show how to navigate to another destination
@@ -38,6 +40,8 @@
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
+        enterTransition = Slide(Gravity.RIGHT)
+        exitTransition = Slide(Gravity.LEFT)
         return inflater.inflate(R.layout.main_fragment, container, false)
     }
 
diff --git a/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml b/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
index cea98bd..4db4d1d 100644
--- a/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
+++ b/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
@@ -23,31 +23,19 @@
                   android:name=".MainFragment"
                   android:label="@string/home">
             <argument android:name="myarg" android:defaultValue="Home" />
-            <action android:id="@+id/next" app:destination="@+id/first_screen"
-                app:enterAnim="@anim/nav_default_enter_anim"
-                app:exitAnim="@anim/nav_default_exit_anim"
-                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-                app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+            <action android:id="@+id/next" app:destination="@+id/first_screen"/>
         </fragment>
         <fragment android:id="@+id/first_screen"
             android:name="androidx.navigation.testapp.MainFragment"
             android:label="@string/first">
             <argument android:name="myarg" android:defaultValue="one" />
-            <action android:id="@+id/next" app:destination="@+id/next_fragment"
-                app:enterAnim="@anim/nav_default_enter_anim"
-                app:exitAnim="@anim/nav_default_exit_anim"
-                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-                app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+            <action android:id="@+id/next" app:destination="@+id/next_fragment"/>
         </fragment>
         <fragment android:id="@+id/next_fragment"
             android:name="androidx.navigation.testapp.MainFragment"
             android:label="@string/second">
             <argument android:name="myarg" android:defaultValue="two" />
-            <action android:id="@+id/next" app:destination="@+id/first_screen"
-                app:enterAnim="@anim/nav_default_enter_anim"
-                app:exitAnim="@anim/nav_default_exit_anim"
-                app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-                app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+            <action android:id="@+id/next" app:destination="@+id/first_screen"/>
         </fragment>
     </navigation>
     <dialog
diff --git a/navigation/integration-tests/testapp/src/main/res/navigation/two_pane_navigation.xml b/navigation/integration-tests/testapp/src/main/res/navigation/two_pane_navigation.xml
index f7eb4da..63261cbf 100644
--- a/navigation/integration-tests/testapp/src/main/res/navigation/two_pane_navigation.xml
+++ b/navigation/integration-tests/testapp/src/main/res/navigation/two_pane_navigation.xml
@@ -20,51 +20,31 @@
         android:name="androidx.navigation.testapp.MainFragment"
         android:label="@string/first">
         <argument android:name="myarg" android:defaultValue="one" />
-        <action android:id="@+id/next" app:destination="@+id/second_fragment"
-            app:enterAnim="@anim/nav_default_enter_anim"
-            app:exitAnim="@anim/nav_default_exit_anim"
-            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-            app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+        <action android:id="@+id/next" app:destination="@+id/second_fragment"/>
     </fragment>
     <fragment android:id="@+id/second_fragment"
         android:name="androidx.navigation.testapp.MainFragment"
         android:label="@string/second">
         <argument android:name="myarg" android:defaultValue="two" />
-        <action android:id="@+id/next" app:destination="@+id/third_fragment"
-            app:enterAnim="@anim/nav_default_enter_anim"
-            app:exitAnim="@anim/nav_default_exit_anim"
-            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-            app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+        <action android:id="@+id/next" app:destination="@+id/third_fragment"/>
     </fragment>
     <fragment android:id="@+id/third_fragment"
         android:name="androidx.navigation.testapp.MainFragment"
         android:label="@string/third">
         <argument android:name="myarg" android:defaultValue="three" />
-        <action android:id="@+id/next" app:destination="@+id/fourth_fragment"
-            app:enterAnim="@anim/nav_default_enter_anim"
-            app:exitAnim="@anim/nav_default_exit_anim"
-            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-            app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+        <action android:id="@+id/next" app:destination="@+id/fourth_fragment"/>
     </fragment>
     <fragment android:id="@+id/fourth_fragment"
         android:name="androidx.navigation.testapp.MainFragment"
         android:label="@string/fourth">
         <argument android:name="myarg" android:defaultValue="four" />
-        <action android:id="@+id/next" app:destination="@+id/fifth_fragment"
-            app:enterAnim="@anim/nav_default_enter_anim"
-            app:exitAnim="@anim/nav_default_exit_anim"
-            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-            app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+        <action android:id="@+id/next" app:destination="@+id/fifth_fragment"/>
     </fragment>
     <fragment android:id="@+id/fifth_fragment"
         android:name="androidx.navigation.testapp.MainFragment"
         android:label="@string/fifth">
         <argument android:name="myarg" android:defaultValue="five" />
-        <action android:id="@+id/next" app:destination="@+id/first_fragment"
-            app:enterAnim="@anim/nav_default_enter_anim"
-            app:exitAnim="@anim/nav_default_exit_anim"
-            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
-            app:popExitAnim="@anim/nav_default_pop_exit_anim"/>
+        <action android:id="@+id/next" app:destination="@+id/first_fragment"/>
     </fragment>
     <dialog
         android:id="@+id/learn_more"
diff --git a/paging/paging-common/api/current.txt b/paging/paging-common/api/current.txt
index 10ec13b..2070710 100644
--- a/paging/paging-common/api/current.txt
+++ b/paging/paging-common/api/current.txt
@@ -46,7 +46,7 @@
     method @AnyThread public void onInvalidated();
   }
 
-  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements androidx.paging.PagingSourceFactory<Key,Value> {
     ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
     method public void invalidate();
     method public androidx.paging.PagingSource<Key,Value> invoke();
@@ -411,6 +411,10 @@
   public static final class PagingSource.LoadResult.Page.Companion {
   }
 
+  public fun interface PagingSourceFactory<Key, Value> extends kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    method public operator androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
   public final class PagingState<Key, Value> {
     ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0L) int leadingPlaceholderCount);
     method public Value? closestItemToPosition(int anchorPosition);
diff --git a/paging/paging-common/api/public_plus_experimental_current.txt b/paging/paging-common/api/public_plus_experimental_current.txt
index 07e6471..a5d3d01 100644
--- a/paging/paging-common/api/public_plus_experimental_current.txt
+++ b/paging/paging-common/api/public_plus_experimental_current.txt
@@ -49,7 +49,7 @@
   @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalPagingApi {
   }
 
-  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements androidx.paging.PagingSourceFactory<Key,Value> {
     ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
     method public void invalidate();
     method public androidx.paging.PagingSource<Key,Value> invoke();
@@ -415,6 +415,10 @@
   public static final class PagingSource.LoadResult.Page.Companion {
   }
 
+  public fun interface PagingSourceFactory<Key, Value> extends kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    method public operator androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
   public final class PagingState<Key, Value> {
     ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0L) int leadingPlaceholderCount);
     method public Value? closestItemToPosition(int anchorPosition);
diff --git a/paging/paging-common/api/restricted_current.txt b/paging/paging-common/api/restricted_current.txt
index 10ec13b..2070710 100644
--- a/paging/paging-common/api/restricted_current.txt
+++ b/paging/paging-common/api/restricted_current.txt
@@ -46,7 +46,7 @@
     method @AnyThread public void onInvalidated();
   }
 
-  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements androidx.paging.PagingSourceFactory<Key,Value> {
     ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
     method public void invalidate();
     method public androidx.paging.PagingSource<Key,Value> invoke();
@@ -411,6 +411,10 @@
   public static final class PagingSource.LoadResult.Page.Companion {
   }
 
+  public fun interface PagingSourceFactory<Key, Value> extends kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    method public operator androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
   public final class PagingState<Key, Value> {
     ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0L) int leadingPlaceholderCount);
     method public Value? closestItemToPosition(int anchorPosition);
diff --git a/paging/paging-common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt b/paging/paging-common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
index 93ddb2d..3f74d54 100644
--- a/paging/paging-common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
+++ b/paging/paging-common/src/main/kotlin/androidx/paging/InvalidatingPagingSourceFactory.kt
@@ -32,7 +32,7 @@
  */
 public class InvalidatingPagingSourceFactory<Key : Any, Value : Any>(
     private val pagingSourceFactory: () -> PagingSource<Key, Value>
-) : () -> PagingSource<Key, Value> {
+) : PagingSourceFactory<Key, Value> {
 
     @VisibleForTesting
     internal val pagingSources = CopyOnWriteArrayList<PagingSource<Key, Value>>()
diff --git a/paging/paging-common/src/main/kotlin/androidx/paging/PagingSourceFactory.kt b/paging/paging-common/src/main/kotlin/androidx/paging/PagingSourceFactory.kt
new file mode 100644
index 0000000..4ed3c7f
--- /dev/null
+++ b/paging/paging-common/src/main/kotlin/androidx/paging/PagingSourceFactory.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.paging
+
+/**
+ * Interface for a factory that generates [PagingSource].
+ *
+ * The factory extending this interface can be used to instantiate a [Pager] as the
+ * pagingSourceFactory.
+ */
+public fun interface PagingSourceFactory<Key : Any, Value : Any> : () -> PagingSource<Key, Value> {
+    /**
+     * Returns a new PagingSource instance.
+     *
+     * This function can be invoked by calling pagingSourceFactory() or pagingSourceFactory::invoke.
+     */
+    public override operator fun invoke(): PagingSource<Key, Value>
+}
\ No newline at end of file
diff --git a/paging/paging-testing/api/current.txt b/paging/paging-testing/api/current.txt
index 066939a..91eeba55 100644
--- a/paging/paging-testing/api/current.txt
+++ b/paging/paging-testing/api/current.txt
@@ -26,7 +26,8 @@
   }
 
   public final class StaticListPagingSourceFactoryKt {
-    method public static <Value> kotlin.jvm.functions.Function0<androidx.paging.PagingSource<java.lang.Integer,Value>> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+    method public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+    method public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(java.util.List<? extends Value>);
   }
 
   public final class TestPager<Key, Value> {
diff --git a/paging/paging-testing/api/public_plus_experimental_current.txt b/paging/paging-testing/api/public_plus_experimental_current.txt
index 066939a..91eeba55 100644
--- a/paging/paging-testing/api/public_plus_experimental_current.txt
+++ b/paging/paging-testing/api/public_plus_experimental_current.txt
@@ -26,7 +26,8 @@
   }
 
   public final class StaticListPagingSourceFactoryKt {
-    method public static <Value> kotlin.jvm.functions.Function0<androidx.paging.PagingSource<java.lang.Integer,Value>> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+    method public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+    method public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(java.util.List<? extends Value>);
   }
 
   public final class TestPager<Key, Value> {
diff --git a/paging/paging-testing/api/restricted_current.txt b/paging/paging-testing/api/restricted_current.txt
index 066939a..91eeba55 100644
--- a/paging/paging-testing/api/restricted_current.txt
+++ b/paging/paging-testing/api/restricted_current.txt
@@ -26,7 +26,8 @@
   }
 
   public final class StaticListPagingSourceFactoryKt {
-    method public static <Value> kotlin.jvm.functions.Function0<androidx.paging.PagingSource<java.lang.Integer,Value>> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+    method public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(kotlinx.coroutines.flow.Flow<java.util.List<Value>>, kotlinx.coroutines.CoroutineScope coroutineScope);
+    method public static <Value> androidx.paging.PagingSourceFactory<java.lang.Integer,Value> asPagingSourceFactory(java.util.List<? extends Value>);
   }
 
   public final class TestPager<Key, Value> {
diff --git a/paging/paging-testing/src/main/java/androidx/paging/testing/StaticListPagingSourceFactory.kt b/paging/paging-testing/src/main/java/androidx/paging/testing/StaticListPagingSourceFactory.kt
index e44216e..2a719a6 100644
--- a/paging/paging-testing/src/main/java/androidx/paging/testing/StaticListPagingSourceFactory.kt
+++ b/paging/paging-testing/src/main/java/androidx/paging/testing/StaticListPagingSourceFactory.kt
@@ -17,23 +17,24 @@
 package androidx.paging.testing
 
 import androidx.paging.InvalidatingPagingSourceFactory
+import androidx.paging.LoadType.REFRESH
 import androidx.paging.PagingSource
 import androidx.paging.Pager
+import androidx.paging.PagingSourceFactory
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.launch
 
 /**
- * Returns a factory that creates [PagingSource] instances.
+ * Returns a [PagingSourceFactory] that creates [PagingSource] instances.
  *
- * Since this method returns a lambda, call [this.invoke] to create a new PagingSource. Can be
- * used as the pagingSourceFactory when constructing a [Pager]. The same factory should be reused
- * within the lifetime of a ViewModel.
+ * Can be used as the pagingSourceFactory when constructing a [Pager] in tests. The same factory
+ * should be reused within the lifetime of a ViewModel.
  *
  * Extension method on a [Flow] of list that represents the data source, with each static list
  * representing a generation of data from which a [PagingSource] will load from. With every
- * emission, the current [PagingSource] will be invalidated, thereby triggering a new generation
- * of Paged data.
+ * emission to the flow, the current [PagingSource] will be invalidated, thereby triggering
+ * a new generation of Paged data.
  *
  * Supports multiple factories and thus multiple collection on the same flow.
  *
@@ -41,7 +42,7 @@
  */
 public fun <Value : Any> Flow<@JvmSuppressWildcards List<Value>>.asPagingSourceFactory(
     coroutineScope: CoroutineScope
-): () -> PagingSource<Int, Value> {
+): PagingSourceFactory<Int, Value> {
 
     var data: List<Value>? = null
 
@@ -60,4 +61,18 @@
     }
 
     return factory
-}
\ No newline at end of file
+}
+
+/**
+ * Returns a [PagingSourceFactory] that creates [PagingSource] instances.
+ *
+ * Can be used as the pagingSourceFactory when constructing a [Pager] in tests. The same factory
+ * should be reused within the lifetime of a ViewModel.
+ *
+ * Extension method on a [List] of data from which a [PagingSource] will load from. While this
+ * factory supports multi-generational operations such as [REFRESH], it does not support updating
+ * the data source. This means any PagingSources generated by the same factory will load from
+ * the exact same list of data.
+ */
+public fun <Value : Any> List<Value>.asPagingSourceFactory(): PagingSourceFactory<Int, Value> =
+    PagingSourceFactory { StaticListPagingSource(this) }
diff --git a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
index 42aaa0b5..ca7351a 100644
--- a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
+++ b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
@@ -20,6 +20,7 @@
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource
 import androidx.paging.PagingSource.LoadParams
+import androidx.paging.PagingSourceFactory
 import androidx.paging.PagingState
 import androidx.paging.cachedIn
 import androidx.paging.insertSeparators
@@ -62,6 +63,11 @@
         loadDelay
     )
 
+    private fun createSingleGenFactory(data: List<Int>) = WrappedPagingSourceFactory(
+        data.asPagingSourceFactory(),
+        loadDelay
+    )
+
     @Before
     fun init() {
         Dispatchers.setMain(UnconfinedTestDispatcher())
@@ -72,7 +78,20 @@
         val dataFlow = flowOf(List(30) { it })
         val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this)
+            // first page + prefetched page
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7)
+            )
+        }
+    }
+
+    @Test
+    fun initialRefreshSingleGen() {
+        val data = List(30) { it }
+        val pager = createPager(data)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this)
             // first page + prefetched page
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4, 5, 6, 7)
@@ -85,7 +104,7 @@
         val dataFlow = flowOf(List(30) { it })
         val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.asSnapshot(this)
+            val snapshot = pager.asSnapshot(this) {}
             // first page + prefetched page
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4, 5, 6, 7)
@@ -102,7 +121,7 @@
             }
         }
         testScope.runTest {
-            val snapshot = pager.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this)
             // loads 8[initial 5 + prefetch 3] items total, including separators
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, "sep", 1, "sep", 2, "sep", 3, "sep", 4)
@@ -115,7 +134,7 @@
         val dataFlow = flowOf(List(30) { it })
         val pager = createPagerNoPrefetch(dataFlow)
         testScope.runTest {
-            val snapshot = pager.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this)
 
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4)
@@ -128,7 +147,7 @@
         val dataFlow = flowOf(List(30) { it })
         val pager = createPager(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this)
 
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
@@ -141,7 +160,7 @@
         val dataFlow = flowOf(List(30) { it })
         val pager = createPagerNoPrefetch(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this)
 
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(10, 11, 12, 13, 14)
@@ -154,7 +173,20 @@
         val dataFlow = emptyFlow<List<Int>>()
         val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this)
+
+            assertThat(snapshot).containsExactlyElementsIn(
+                emptyList<Int>()
+            )
+        }
+    }
+
+    @Test
+    fun emptyInitialRefreshSingleGen() {
+        val data = emptyList<Int>()
+        val pager = createPager(data)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this)
 
             assertThat(snapshot).containsExactlyElementsIn(
                 emptyList<Int>()
@@ -190,6 +222,37 @@
     }
 
     @Test
+    fun manualRefreshSingleGen() {
+        val data = List(30) { it }
+        val pager = createPager(data)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                refresh()
+            }
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7),
+            )
+        }
+    }
+
+    @Test
+    fun manualRefreshSingleGen_pagingSourceInvalidated() {
+        val data = List(30) { it }
+        val sources = mutableListOf<PagingSource<Int, Int>>()
+        val factory = data.asPagingSourceFactory()
+        val pager = Pager(
+            config = PagingConfig(pageSize = 3, initialLoadSize = 5),
+            pagingSourceFactory = { factory().also { sources.add(it) } },
+        ).flow
+        testScope.runTest {
+            pager.asSnapshot(this) {
+                refresh()
+            }
+            assertThat(sources.first().invalid).isTrue()
+        }
+    }
+
+    @Test
     fun manualEmptyRefresh() {
         val dataFlow = emptyFlow<List<Int>>()
         val pager = createPagerNoPrefetch(dataFlow)
@@ -2311,6 +2374,13 @@
             initialKey
         )
 
+    private fun createPager(data: List<Int>, initialKey: Int = 0) =
+        Pager(
+            PagingConfig(pageSize = 3, initialLoadSize = 5),
+            initialKey,
+            createSingleGenFactory(data),
+        ).flow
+
     private fun createPagerNoPlaceholders(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
         createPager(
             dataFlow,
@@ -2355,9 +2425,9 @@
 }
 
 private class WrappedPagingSourceFactory(
-    private val factory: () -> PagingSource<Int, Int>,
+    private val factory: PagingSourceFactory<Int, Int>,
     private val loadDelay: Long,
-) : () -> PagingSource<Int, Int> {
+) : PagingSourceFactory<Int, Int> {
     override fun invoke(): PagingSource<Int, Int> = TestPagingSource(factory(), loadDelay)
 }
 
diff --git a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
index 6a2f2c1..c293ed0 100644
--- a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
+++ b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
@@ -17,8 +17,8 @@
 package androidx.paging.testing
 
 import androidx.paging.PagingConfig
-import androidx.paging.PagingSource
 import androidx.paging.PagingSource.LoadResult.Page
+import androidx.paging.PagingSourceFactory
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
@@ -47,7 +47,7 @@
 
     @Test
     fun emptyFlow() {
-        val factory: () -> PagingSource<Int, Int> =
+        val factory: PagingSourceFactory<Int, Int> =
             flowOf<List<Int>>().asPagingSourceFactory(testScope)
         val pagingSource = factory()
         val pager = TestPager(pagingSource, CONFIG)
@@ -64,7 +64,7 @@
             List(20) { it }
         )
 
-        val factory: () -> PagingSource<Int, Int> =
+        val factory: PagingSourceFactory<Int, Int> =
             flow.asPagingSourceFactory(testScope)
         val pagingSource = factory()
         val pager = TestPager(pagingSource, CONFIG)
@@ -85,7 +85,7 @@
             emit(List(15) { it + 30 }) // second gen
         }
 
-        val factory: () -> PagingSource<Int, Int> =
+        val factory: PagingSourceFactory<Int, Int> =
             flow.asPagingSourceFactory(testScope)
 
         advanceTimeBy(1000)
@@ -117,7 +117,7 @@
         val mutableFlow = MutableSharedFlow<List<Int>>()
         val collectionScope = this.backgroundScope
 
-        val factory: () -> PagingSource<Int, Int> =
+        val factory: PagingSourceFactory<Int, Int> =
             mutableFlow.asPagingSourceFactory(collectionScope)
 
         mutableFlow.emit(List(10) { it })
@@ -146,10 +146,10 @@
     fun multipleFactories_fromSameFlow() = testScope.runTest {
         val mutableFlow = MutableSharedFlow<List<Int>>()
 
-        val factory1: () -> PagingSource<Int, Int> =
+        val factory1: PagingSourceFactory<Int, Int> =
             mutableFlow.asPagingSourceFactory(testScope.backgroundScope)
 
-        val factory2: () -> PagingSource<Int, Int> =
+        val factory2: PagingSourceFactory<Int, Int> =
             mutableFlow.asPagingSourceFactory(testScope.backgroundScope)
 
         mutableFlow.emit(List(10) { it })
@@ -196,4 +196,78 @@
             listOf(30, 31, 32, 33, 34)
         )
     }
+
+    @Test
+    fun singleListFactory_refresh() = testScope.runTest {
+        val data = List(20) { it }
+        val factory = data.asPagingSourceFactory()
+
+        val pagingSource1 = factory()
+        val pager1 = TestPager(pagingSource1, CONFIG)
+        val refresh1 = pager1.refresh() as Page
+        assertThat(refresh1.data).containsExactlyElementsIn(
+            listOf(0, 1, 2, 3, 4)
+        )
+
+        val pagingSource2 = factory()
+        val pager2 = TestPager(pagingSource2, CONFIG)
+        val refresh2 = pager2.refresh() as Page
+        assertThat(refresh2.data).containsExactlyElementsIn(
+            listOf(0, 1, 2, 3, 4)
+        )
+    }
+
+    @Test
+    fun singleListFactory_empty() = testScope.runTest {
+        val data = emptyList<Int>()
+        val factory = data.asPagingSourceFactory()
+
+        val pagingSource1 = factory()
+        val pager1 = TestPager(pagingSource1, CONFIG)
+        val refresh1 = pager1.refresh() as Page
+        assertThat(refresh1.data).isEmpty()
+
+        val pagingSource2 = factory()
+        val pager2 = TestPager(pagingSource2, CONFIG)
+        val refresh2 = pager2.refresh() as Page
+        assertThat(refresh2.data).isEmpty()
+    }
+
+    @Test
+    fun singleListFactory_append() = testScope.runTest {
+        val data = List(20) { it }
+        val factory = data.asPagingSourceFactory()
+        val pagingSource1 = factory()
+        val pager1 = TestPager(pagingSource1, CONFIG)
+
+        pager1.refresh() as Page
+        pager1.append()
+        assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
+            listOf(0, 1, 2, 3, 4, 5, 6, 7)
+        )
+
+        pager1.append()
+        assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
+            listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+        )
+    }
+
+    @Test
+    fun singleListFactory_prepend() = testScope.runTest {
+        val data = List(20) { it }
+        val factory = data.asPagingSourceFactory()
+        val pagingSource1 = factory()
+        val pager1 = TestPager(pagingSource1, CONFIG)
+
+        pager1.refresh(initialKey = 10) as Page
+        pager1.prepend()
+        assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
+            listOf(7, 8, 9, 10, 11, 12, 13, 14)
+        )
+
+        pager1.prepend()
+        assertThat(pager1.getPages().flatMap { it.data }).containsExactlyElementsIn(
+            listOf(4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
+        )
+    }
 }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index 470c9af..213eeb9 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -22,10 +22,10 @@
 import com.google.devtools.ksp.symbol.KSDeclaration
 import com.google.devtools.ksp.symbol.KSNode
 import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeAlias
 import com.google.devtools.ksp.symbol.KSTypeArgument
 import com.google.devtools.ksp.symbol.KSTypeParameter
 import com.google.devtools.ksp.symbol.KSTypeReference
-import com.google.devtools.ksp.symbol.Modifier
 
 /**
  * Root package comes as <root> instead of "" so we work around it here.
@@ -47,10 +47,12 @@
 }
 
 internal fun KSTypeReference.isTypeParameterReference(): Boolean {
-    return this.resolve().declaration is KSTypeParameter
+    return this.resolve().isTypeParameter()
 }
 
-fun KSType.isInline() = declaration.modifiers.contains(Modifier.INLINE)
+internal fun KSType.isTypeParameter(): Boolean {
+    return declaration is KSTypeParameter
+}
 
 internal fun KSType.withNullability(nullability: XNullability) = when (nullability) {
     XNullability.NULLABLE -> makeNullable()
@@ -59,14 +61,15 @@
 }
 
 private fun KSAnnotated.hasAnnotation(qName: String) =
-    annotations.any { it.hasQualifiedName(qName) }
+    annotations.any { it.hasQualifiedNameOrAlias(qName) }
 
-private fun KSAnnotation.hasQualifiedName(qName: String): Boolean {
-    return annotationType.resolve().hasQualifiedName(qName)
+private fun KSAnnotation.hasQualifiedNameOrAlias(qName: String): Boolean {
+    return annotationType.resolve().hasQualifiedNameOrAlias(qName)
 }
 
-private fun KSType.hasQualifiedName(qName: String): Boolean {
-    return declaration.qualifiedName?.asString() == qName
+private fun KSType.hasQualifiedNameOrAlias(qName: String): Boolean {
+    return declaration.qualifiedName?.asString() == qName ||
+        (declaration as? KSTypeAlias)?.type?.resolve()?.hasQualifiedNameOrAlias(qName) ?: false
 }
 
 internal fun KSAnnotated.hasJvmWildcardAnnotation() =
@@ -75,20 +78,55 @@
 internal fun KSAnnotated.hasSuppressJvmWildcardAnnotation() =
     hasAnnotation(JvmSuppressWildcards::class.java.canonicalName!!)
 
-private fun KSType.hasAnnotation(qName: String) = annotations.any { it.hasQualifiedName(qName) }
-
-internal fun KSType.hasJvmWildcardAnnotation() =
-    hasAnnotation(JvmWildcard::class.java.canonicalName!!)
+// TODO(bcorso): There's a bug in KSP where, after using KSType#asMemberOf() or KSType#replace(),
+//  the annotations are removed from the resulting type. However, it turns out that the annotation
+//  information is still available in the underlying KotlinType, so we use reflection to get them.
+//  See https://github.com/google/ksp/issues/1376.
+private fun KSType.hasAnnotation(qName: String): Boolean {
+    fun String.toFqName(): Any {
+        return Class.forName("org.jetbrains.kotlin.name.FqName")
+            .getConstructor(String::class.java)
+            .newInstance(this)
+    }
+    fun hasAnnotationViaReflection(qName: String): Boolean {
+        val ksType = if (
+            // Note: Technically, we could just make KSTypeWrapper internal and cast to get the
+            // delegate, but since we need to use reflection anyway, just get it via reflection.
+            this.javaClass.canonicalName == "androidx.room.compiler.processing.ksp.KSTypeWrapper") {
+            this.javaClass.methods.find { it.name == "getDelegate" }?.invoke(this)
+        } else {
+            this
+        }
+        val kotlinType =
+            ksType?.javaClass?.methods?.find { it.name == "getKotlinType" }?.invoke(ksType)
+        val kotlinAnnotations =
+            kotlinType?.javaClass
+                ?.methods
+                ?.find { it.name == "getAnnotations" }
+                ?.invoke(kotlinType)
+        return kotlinAnnotations?.javaClass
+            ?.methods
+            ?.find { it.name == "hasAnnotation" }
+            ?.invoke(kotlinAnnotations, qName.toFqName()) == true
+    }
+    return if (annotations.toList().isEmpty()) {
+        // If there are no annotations but KSType#toString() shows annotations, check the underlying
+        // KotlinType for annotations using reflection.
+        toString().startsWith("[") && hasAnnotationViaReflection(qName)
+    } else {
+        annotations.any { it.annotationType.resolve().hasQualifiedNameOrAlias(qName) }
+    }
+}
 
 internal fun KSType.hasSuppressJvmWildcardAnnotation() =
     hasAnnotation(JvmSuppressWildcards::class.java.canonicalName!!)
 
  internal fun KSNode.hasSuppressWildcardsAnnotationInHierarchy(): Boolean {
-     (this as? KSAnnotated)?.let {
-         if (hasSuppressJvmWildcardAnnotation()) {
-             return true
-         }
-     }
-     val parent = parent ?: return false
-     return parent.hasSuppressWildcardsAnnotationInHierarchy()
- }
\ No newline at end of file
+    (this as? KSAnnotated)?.let {
+        if (hasSuppressJvmWildcardAnnotation()) {
+            return true
+        }
+    }
+    val parent = parent ?: return false
+    return parent.hasSuppressWildcardsAnnotationInHierarchy()
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolver.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolver.kt
index bdade61..d797d50 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolver.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolver.kt
@@ -25,6 +25,7 @@
 import com.google.devtools.ksp.symbol.KSTypeAlias
 import com.google.devtools.ksp.symbol.KSTypeArgument
 import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.google.devtools.ksp.symbol.KSTypeReference
 import com.google.devtools.ksp.symbol.Modifier
 import com.google.devtools.ksp.symbol.Variance
 
@@ -59,21 +60,57 @@
             return type
         }
 
-        val resolvedType = if (hasTypeVariables(scope.declarationType())) {
+        // First, replace any type aliases in the type with their actual types
+        return type.replaceTypeAliases()
+            // Next, resolve wildcards based on the scope of the type
+            .resolveWildcards(scope)
+            // Next, apply any additional variance changes based on the @JvmSuppressWildcards or
+            // @JvmWildcard annotations on the resolved type.
+            .applyJvmWildcardAnnotations()
+            // Finally, unwrap any delegate types. (Note: as part of resolving wildcards, we wrap
+            // types/type arguments in delegates to avoid loosing annotation information. However,
+            // those delegates may cause issues later if KSP tries to cast the type/argument to a
+            // particular implementation, so we unwrap them here.
+            .removeWrappers()
+    }
+
+    private fun KSType.replaceTypeAliases(typeStack: ReferenceStack = ReferenceStack()): KSType {
+        if (isError || typeStack.queue.contains(this)) {
+            return this
+        }
+        if (declaration is KSTypeAlias) {
+            return (declaration as KSTypeAlias).type.resolve().replaceTypeAliases(typeStack)
+        }
+        return typeStack.withReference(this) {
+            createWrapper(arguments.map { it.replaceTypeAliases(typeStack) })
+        }
+    }
+
+    private fun KSTypeArgument.replaceTypeAliases(typeStack: ReferenceStack): KSTypeArgument {
+        val type = type?.resolve()
+        if (
+            type == null ||
+            type.isError ||
+            variance == Variance.STAR ||
+            typeStack.queue.contains(type)
+        ) {
+            return this
+        }
+        return createWrapper(type.replaceTypeAliases(typeStack), variance)
+    }
+
+    private fun KSType.resolveWildcards(scope: KSTypeVarianceResolverScope): KSType {
+        return if (hasTypeVariables(scope.declarationType())) {
             // If the associated declared type contains type variables that were resolved, e.g.
             // using "asMemberOf", then it has special rules about how to resolve the types.
             getJavaWildcardWithTypeVariables(
-                type = type,
+                type = this,
                 declarationType = getJavaWildcard(scope.declarationType(), scope),
                 scope = scope,
             )
         } else {
-            getJavaWildcard(type, scope)
+            getJavaWildcard(this, scope)
         }
-
-        // As a final pass, we apply variance from any @JvmSuppressWildcards or @JvmWildcard
-        // annotations on the resolved type.
-        return applyJvmWildcardAnnotations(resolvedType)
     }
 
     private fun hasTypeVariables(
@@ -99,15 +136,6 @@
         if (type.isError || typeStack.queue.contains(type)) {
             return type
         }
-        if (type.declaration is KSTypeAlias) {
-            return getJavaWildcard(
-                type = (type.declaration as KSTypeAlias).type.resolve(),
-                scope = scope,
-                typeStack = typeStack,
-                typeArgStack = typeArgStack,
-                typeParamStack = typeParamStack,
-            )
-        }
         return typeStack.withReference(type) {
             val resolvedTypeArgs =
                 type.arguments.indices.map { i ->
@@ -120,7 +148,7 @@
                         typeParamStack = typeParamStack,
                     )
                 }
-            type.replace(resolvedTypeArgs)
+            type.createWrapper(resolvedTypeArgs)
         }
     }
 
@@ -158,10 +186,10 @@
                 if (typeParamStack.indices.none { i ->
                         (typeParamStack[i].variance == Variance.CONTRAVARIANT ||
                             typeArgStack[i].variance == Variance.CONTRAVARIANT) &&
-                        // The declaration and use site variance is ignored when using @JvmWildcard
-                        // explicitly on a type.
-                        !typeArgStack[i].hasJvmWildcardAnnotation()
-                }) {
+                            // The declaration and use site variance is ignored when using @JvmWildcard
+                            // explicitly on a type.
+                            !typeArgStack[i].hasJvmWildcardAnnotation()
+                    }) {
                     return false
                 }
             } else {
@@ -217,7 +245,7 @@
         } else {
             typeArg.variance
         }
-        return createTypeArgument(resolvedType, resolvedVariance)
+        return typeArg.createWrapper(resolvedType, resolvedVariance)
     }
 
     private fun getJavaWildcardWithTypeVariables(
@@ -252,7 +280,7 @@
                         )
                     }
                 }
-            type.replace(resolvedTypeArgs)
+            type.createWrapper(resolvedTypeArgs)
         }
     }
 
@@ -293,7 +321,7 @@
         } else {
             typeArg.variance
         }
-        return createTypeArgument(resolvedType, resolvedVariance)
+        return typeArg.createWrapper(resolvedType, resolvedVariance)
     }
 
     private fun getJavaWildcardWithTypeVariablesForOuterType(
@@ -322,26 +350,27 @@
         } else {
             typeArg.variance
         }
-        return createTypeArgument(resolvedType, resolvedVariance)
+        return typeArg.createWrapper(resolvedType, resolvedVariance)
     }
 
-    private fun applyJvmWildcardAnnotations(
-        type: KSType,
+    private fun KSType.applyJvmWildcardAnnotations(
         typeStack: ReferenceStack = ReferenceStack(),
+        typeArgStack: List<KSTypeArgument> = emptyList(),
     ): KSType {
-        if (type.isError || typeStack.queue.contains(type)) {
-            return type
+        if (isError || typeStack.queue.contains(this)) {
+            return this
         }
-        return typeStack.withReference(type) {
+        return typeStack.withReference(this) {
             val resolvedTypeArgs =
-                type.arguments.indices.map { i ->
+                arguments.indices.map { i ->
                     applyJvmWildcardAnnotations(
-                        typeArg = type.arguments[i],
-                        typeParameter = type.declaration.typeParameters[i],
+                        typeArg = arguments[i],
+                        typeParameter = declaration.typeParameters[i],
+                        typeArgStack = typeArgStack,
                         typeStack = typeStack,
                     )
                 }
-            type.replace(resolvedTypeArgs)
+            createWrapper(resolvedTypeArgs)
         }
     }
 
@@ -349,6 +378,7 @@
         typeArg: KSTypeArgument,
         typeParameter: KSTypeParameter,
         typeStack: ReferenceStack,
+        typeArgStack: List<KSTypeArgument>,
     ): KSTypeArgument {
         val type = typeArg.type?.resolve()
         if (
@@ -359,28 +389,107 @@
         ) {
             return typeArg
         }
-        val resolvedType = applyJvmWildcardAnnotations(
-            type = type,
-            typeStack = typeStack,
-        )
+        val resolvedType = type.applyJvmWildcardAnnotations(typeStack, typeArgStack + typeArg)
         val resolvedVariance = when {
             typeParameter.variance == Variance.INVARIANT &&
                 typeArg.variance != Variance.INVARIANT -> typeArg.variance
             typeArg.hasJvmWildcardAnnotation() -> typeParameter.variance
-                typeStack.queue.any { it.hasSuppressJvmWildcardAnnotation() } ||
+            // We only need to check the first type in the stack for @JvmSuppressWildcards.
+            // Any other @JvmSuppressWildcards usages will be placed on the type arguments rather
+            // than the types, so no need to check the rest of the types.
+            typeStack.queue.first().hasSuppressJvmWildcardAnnotation() ||
                 typeArg.hasSuppressWildcardsAnnotationInHierarchy() ||
+                typeArgStack.any { it.hasSuppressJvmWildcardAnnotation() } ||
                 typeParameter.hasSuppressWildcardsAnnotationInHierarchy() -> Variance.INVARIANT
             else -> typeArg.variance
         }
-        return createTypeArgument(resolvedType, resolvedVariance)
+        return typeArg.createWrapper(resolvedType, resolvedVariance)
     }
 
-    private fun KSType.isTypeParameter(): Boolean {
-        return createTypeReference().isTypeParameterReference()
+    private fun KSTypeArgument.createWrapper(
+        newType: KSType,
+        newVariance: Variance
+    ): KSTypeArgument {
+        return KSTypeArgumentWrapper(
+            delegate = (this as? KSTypeArgumentWrapper)?.delegate ?: this,
+            type = newType.createTypeReference(),
+            variance = newVariance
+        )
     }
 
-    private fun createTypeArgument(type: KSType, variance: Variance): KSTypeArgument {
-        return resolver.getTypeArgument(type.createTypeReference(), variance)
+    private fun KSType.createWrapper(newArguments: List<KSTypeArgument>): KSType {
+        return KSTypeWrapper(
+            delegate = (this as? KSTypeWrapper)?.delegate ?: this,
+            arguments = newArguments
+        )
+    }
+
+    private fun KSType.removeWrappers(typeStack: ReferenceStack = ReferenceStack()): KSType {
+        if (isError || typeStack.queue.contains(this)) {
+            return this
+        }
+        return typeStack.withReference(this) {
+            val delegateType = (this as? KSTypeWrapper)?.delegate ?: this
+            delegateType.replace(arguments.map { it.removeWrappers(typeStack) })
+        }
+    }
+
+    private fun KSTypeArgument.removeWrappers(
+        typeStack: ReferenceStack = ReferenceStack()
+    ): KSTypeArgument {
+        val type = type?.resolve()
+        if (
+            type == null ||
+            type.isError ||
+            variance == Variance.STAR ||
+            typeStack.queue.contains(type)
+        ) {
+            return this
+        }
+        return resolver.getTypeArgument(
+            type.removeWrappers(typeStack).createTypeReference(),
+            variance
+        )
+    }
+}
+
+/**
+ * A wrapper for creating a new [KSType] that allows arguments of type [KSTypeArgumentWrapper].
+ *
+ * Note: This wrapper acts similar to [KSType#replace(KSTypeArgument)]. However, we can't call
+ * [KSType#replace(KSTypeArgument)] directly when using [KSTypeArgumentWrapper] or we'll get an
+ * [IllegalStateException] since KSP tries to cast to its own implementation of [KSTypeArgument].
+ */
+private class KSTypeWrapper(
+    val delegate: KSType,
+    override val arguments: List<KSTypeArgument>
+) : KSType by delegate {
+    override fun toString() = if (arguments.isNotEmpty()) {
+        "${delegate.toString().substringBefore("<")}<${arguments.joinToString(",")}>"
+    } else {
+        delegate.toString()
+    }
+}
+
+/**
+ * A wrapper for creating a new [KSTypeArgument] that delegates to the original argument for
+ * annotations.
+ *
+ * Note: This wrapper acts similar to [Resolver#getTypeArgument(KSTypeReference, Variance)].
+ * However, we can't call [Resolver#getTypeArgument(KSTypeReference, Variance)] directly because
+ * we'll lose information about annotations (e.g. `@JvmSuppressWildcards`) that were on the original
+ * type argument.
+ */
+private class KSTypeArgumentWrapper(
+    val delegate: KSTypeArgument,
+    override val type: KSTypeReference,
+    override val variance: Variance,
+) : KSTypeArgument by delegate {
+    override fun toString() = when (variance) {
+        Variance.INVARIANT -> "${type.resolve()}"
+        Variance.CONTRAVARIANT -> "in ${type.resolve()}"
+        Variance.COVARIANT -> "out ${type.resolve()}"
+        Variance.STAR -> "*"
     }
 }
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolverScope.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolverScope.kt
index f39ab11..19f546a 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolverScope.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeVarianceResolverScope.kt
@@ -27,7 +27,7 @@
  * Provides KSType resolution scope for a type.
  */
 internal sealed class KSTypeVarianceResolverScope(
-    private val annotated: KSAnnotated,
+    val annotated: KSAnnotated,
     private val container: KSDeclaration?,
     private val asMemberOf: KspType?
 ) {
@@ -36,8 +36,15 @@
      * parameter is in kotlin or the containing class, which inherited the method, is in kotlin.
      */
     val needsWildcardResolution: Boolean by lazy {
+        fun nodeForSuppressionCheck(): KSAnnotated? = when (this) {
+            // For property setter and getter methods skip to the enclosing class to check for
+            // suppression annotations to match KAPT.
+            is PropertySetterParameterType,
+            is PropertyGetterMethodReturnType -> annotated.parent?.parent as? KSAnnotated
+            else -> annotated
+        }
         (annotated.isInKotlinCode() || container?.isInKotlinCode() == true) &&
-            !annotated.hasSuppressWildcardsAnnotationInHierarchy()
+            nodeForSuppressionCheck()?.hasSuppressWildcardsAnnotationInHierarchy() != true
     }
 
     private fun KSAnnotated.isInKotlinCode(): Boolean {
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
index 2bd5ed4..be3f4bf 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeNamesGoldenTest.kt
@@ -159,6 +159,11 @@
                 class MyGenericIn<in T>
                 class MyGenericOut<out T>
                 class MyGenericMultipleParameters<T1: MyGeneric<*>, T2: MyGeneric<T1>>
+                interface MyInterface
+                typealias MyInterfaceAlias = MyInterface
+                typealias MyGenericAlias = MyGenericIn<MyGenericOut<MyGenericOut<MyType>>>
+                typealias JSW = JvmSuppressWildcards
+                typealias JW = JvmWildcard
                 typealias MyLambdaTypeAlias = (@JvmWildcard MyType) -> @JvmWildcard MyType
                 enum class MyEnum {
                     VAL1,
@@ -341,6 +346,14 @@
                     sealedListChild: List<GrandParentSealed.Parent2.Child1>,
                     jvmWildcard: List<@JvmWildcard String>,
                     suppressJvmWildcard: List<@JvmSuppressWildcards Number>,
+                    suppressJvmWildcardsGeneric1: @JvmSuppressWildcards List<MyGenericOut<MyGenericIn<MyGeneric<MyType>>>>,
+                    suppressJvmWildcardsGeneric2: List<@JvmSuppressWildcards MyGenericOut<MyGenericIn<MyGeneric<MyType>>>>,
+                    suppressJvmWildcardsGeneric3: List<MyGenericOut<@JvmSuppressWildcards MyGenericIn<MyGeneric<MyType>>>>,
+                    suppressJvmWildcardsGeneric4: List<MyGenericOut<MyGenericIn<@JvmSuppressWildcards MyGeneric<MyType>>>>,
+                    interfaceAlias: List<MyInterfaceAlias>,
+                    genericAlias: List<MyGenericAlias>,
+                    jvmWildcardTypeAlias: List<@JW String>,
+                    suppressJvmWildcardTypeAlias: List<@JSW Number>,
                 ) {
                     var propWithFinalType: String = ""
                     var propWithOpenType: Number = 3
@@ -353,6 +366,16 @@
                     var propSealedListChild: List<GrandParentSealed.Parent2.Child1> = TODO()
                     @JvmSuppressWildcards
                     var propWithOpenTypeButSuppressAnnotation: Number = 3
+                    var genericVar: List<MyGenericIn<MyGenericOut<MyGenericOut<MyType>>>> = TODO()
+                    @JvmSuppressWildcards var suppressJvmWildcardsGenericVar1: List<MyGenericIn<MyGenericOut<MyGenericOut<MyType>>>> = TODO()
+                    var suppressJvmWildcardsGenericVar2: @JvmSuppressWildcards List<MyGenericIn<MyGenericOut<MyGenericOut<MyType>>>> = TODO()
+                    var suppressJvmWildcardsGenericVar3: List<@JvmSuppressWildcards MyGenericIn<MyGenericOut<MyGenericOut<MyType>>>> = TODO()
+                    var suppressJvmWildcardsGenericVar4: List<MyGenericIn<@JvmSuppressWildcards MyGenericOut<MyGenericOut<MyType>>>> = TODO()
+                    var suppressJvmWildcardsGenericVar5: List<MyGenericIn<MyGenericOut<@JvmSuppressWildcards MyGenericOut<MyType>>>> = TODO()
+                    var interfaceAlias: List<MyInterfaceAlias> = TODO()
+                    var genericAlias: List<MyGenericAlias> = TODO()
+                    var jvmWildcardTypeAlias: List<@JW String> = TODO()
+                    var suppressJvmWildcardTypeAlias: List<@JSW Number> = TODO()
                     fun list(list: List<*>): List<*> { TODO() }
                     fun listTypeArg(list: List<R>): List<R> { TODO() }
                     fun listTypeArgNumber(list: List<Number>): List<Number> { TODO() }
@@ -393,6 +416,43 @@
                     fun suspendExplicitJvmSuppressWildcard_OnType2(
                         list: @JvmSuppressWildcards List<Number>
                     ): @JvmSuppressWildcards List<Number> { TODO() }
+                    fun interfaceAlias(
+                        param: List<MyInterfaceAlias>
+                    ): List<MyInterfaceAlias> = TODO()
+                    fun explicitJvmSuppressWildcardsOnAlias(
+                        param: List<@JvmSuppressWildcards MyInterfaceAlias>,
+                    ): List<@JvmSuppressWildcards MyInterfaceAlias> = TODO()
+                    fun genericAlias(param: List<MyGenericAlias>): List<MyGenericAlias> = TODO()
+                    fun explicitJvmSuppressWildcardsOnGenericAlias(
+                        param: List<@JvmSuppressWildcards MyGenericAlias>,
+                    ): List<@JvmSuppressWildcards MyGenericAlias> = TODO()
+                    fun explicitOutOnInvariant_onType1_WithExplicitJvmSuppressWildcardAlias(
+                        list: @JSW MyGeneric<out MyGeneric<MyType>>
+                    ): @JSW MyGeneric<out MyGeneric<MyType>> { TODO() }
+                    fun explicitOutOnInvariant_onType2_WithExplicitJvmSuppressWildcardAlias(
+                        list: @JSW MyGeneric<MyGeneric<out MyType>>
+                    ): @JSW MyGeneric<MyGeneric<out MyType>> { TODO() }
+                    fun explicitOutOnVariant_onType1(
+                        list: List<out List<Number>>
+                    ): List<out List<Number>> { TODO() }
+                    fun explicitOutOnVariant_onType2(
+                        list: List<List<out Number>>
+                    ): List<List<out Number>> { TODO() }
+                    fun explicitOutOnVariant_onType1_WithExplicitJvmSuppressWildcardAlias(
+                        list: @JSW List<out List<Number>>
+                    ): @JSW List<out List<Number>> { TODO() }
+                    fun explicitOutOnVariant_onType2_WithExplicitJvmSuppressWildcardAlias(
+                        list: @JSW List<List<out Number>>
+                    ): @JSW List<List<out Number>> { TODO() }
+                    fun explicitJvmWildcardTypeAlias(
+                        list: List<@JW String>
+                    ): List<@JW String> { TODO() }
+                    fun explicitJvmSuppressWildcardTypeAlias_OnType(
+                        list: List<@JSW Number>
+                    ): List<@JSW Number> { TODO() }
+                    fun explicitJvmSuppressWildcardTypeAlias_OnType2(
+                        list: @JSW List<Number>
+                    ): @JSW List<Number> { TODO() }
                 }
                 """.trimIndent()
             ), listOf(className)
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
index 54717f6..aa246e1 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
@@ -59,7 +59,6 @@
 public class UiDeviceTest extends BaseTest {
 
     private static final long TIMEOUT_MS = 5_000;
-    private static final int GESTURE_MARGIN = 50;
     private static final String PACKAGE_NAME = "androidx.test.uiautomator.testapp";
     // Defined in 'AndroidManifest.xml'.
     private static final String APP_NAME = "UiAutomator Test App";
@@ -300,7 +299,6 @@
         assertEquals("I've been clicked!", button.getText());
     }
 
-    @Ignore // b/266617096
     @Test
     public void testSwipe() {
         launchTestActivity(SwipeTestActivity.class);
@@ -309,7 +307,7 @@
 
         int width = mDevice.getDisplayWidth();
         int height = mDevice.getDisplayHeight();
-        mDevice.swipe(GESTURE_MARGIN, height / 2, width - GESTURE_MARGIN, height / 2, 10);
+        mDevice.swipe(width / 10, height / 2, 9 * width / 10, height / 2, 10);
 
         assertTrue(swipeRegion.wait(Until.textEquals("swipe_right"), TIMEOUT_MS));
     }
@@ -330,7 +328,6 @@
         assertTrue(dragDestination.wait(Until.textEquals("drag_received"), TIMEOUT_MS));
     }
 
-    @Ignore // b/266617096
     @Test
     public void testSwipe_withPointArray() {
         launchTestActivity(SwipeTestActivity.class);
@@ -340,9 +337,9 @@
         int width = mDevice.getDisplayWidth();
         int height = mDevice.getDisplayHeight();
 
-        Point point1 = new Point(GESTURE_MARGIN, height / 2);
+        Point point1 = new Point(width / 10, height / 2);
         Point point2 = new Point(width / 2, height / 2);
-        Point point3 = new Point(width - GESTURE_MARGIN, height / 2);
+        Point point3 = new Point(9 * width / 10, height / 2);
 
         mDevice.swipe(new Point[]{point1, point2, point3}, 10);
 
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
index c91c791..bb9f70b 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
@@ -551,7 +551,6 @@
                 + "but got [%f]", scaleValueAfterPinch), scaleValueAfterPinch > 1f);
     }
 
-    @Ignore // b/266617335
     @Test
     public void testSwipe() {
         launchTestActivity(SwipeTestActivity.class);
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java
index 36aaeb6..f74a13b 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java
@@ -24,12 +24,10 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.uiautomator.UiObject;
 import androidx.test.uiautomator.UiSelector;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class UiObjectTest extends BaseTest {
@@ -119,7 +117,6 @@
         assertTrue(expectedDragDest.waitForExists(TIMEOUT_MS));
     }
 
-    @Ignore // b/266617747
     @Test
     public void testSwipeUp() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -141,7 +138,6 @@
         assertTrue(expectedSwipeRegion.waitForExists(TIMEOUT_MS));
     }
 
-    @Ignore // b/266617747
     @Test
     public void testSwipeDown() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -161,7 +157,6 @@
         assertTrue(expectedSwipeRegion.waitForExists(TIMEOUT_MS));
     }
 
-    @FlakyTest(bugId = 242761733)
     @Test
     public void testSwipeLeft() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -181,7 +176,6 @@
         assertTrue(expectedSwipeRegion.waitForExists(TIMEOUT_MS));
     }
 
-    @Ignore // b/266617747
     @Test
     public void testSwipeRight() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java
index 765aab1..3384042 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java
@@ -273,7 +273,6 @@
         assertUiObjectNotFound(noNode::flingBackward);
     }
 
-    @Ignore // b/266965027
     @Test
     public void testScrollBackward_vertical() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -285,7 +284,6 @@
         assertEquals("swipe_down", scrollRegion.getText());
     }
 
-    @Ignore // b/266965027
     @Test
     public void testScrollBackward_horizontal() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
diff --git a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/SwipeTestActivity.java b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/SwipeTestActivity.java
index fda840d..9a1fe18 100644
--- a/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/SwipeTestActivity.java
+++ b/test/uiautomator/integration-tests/testapp/src/main/java/androidx/test/uiautomator/testapp/SwipeTestActivity.java
@@ -22,6 +22,7 @@
 import android.view.MotionEvent;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 public class SwipeTestActivity extends Activity {
@@ -31,23 +32,20 @@
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
         setContentView(R.layout.swipe_test_activity);
 
         TextView swipeRegion = findViewById(R.id.swipe_region);
 
         mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
             @Override
-            public boolean onFling(MotionEvent e1, MotionEvent e2, float vX, float vY) {
-                // Swipe is using the same logic as fling, except that their directions are
-                // opposite under the same finger movement.
-                boolean horizontal = Math.abs(vX) > Math.abs(vY);
+            public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2,
+                    float distanceX, float distanceY) {
+                boolean horizontal = Math.abs(distanceX) > Math.abs(distanceY);
                 if (horizontal) {
-                    swipeRegion.setText(vX < 0 ? "swipe_left" : "swipe_right");
+                    swipeRegion.setText(distanceX > 0 ? "swipe_left" : "swipe_right");
                 } else {
-                    swipeRegion.setText(vY < 0 ? "swipe_up" : "swipe_down");
+                    swipeRegion.setText(distanceY > 0 ? "swipe_up" : "swipe_down");
                 }
-
                 return true;
             }
         });
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java
index 63a0a49..b818481 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/BySelector.java
@@ -16,6 +16,8 @@
 
 package androidx.test.uiautomator;
 
+import static java.util.Objects.requireNonNull;
+
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 
@@ -106,8 +108,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector clazz(@NonNull String className) {
-        checkNotNull(className, "className cannot be null");
-
+        requireNonNull(className, "className cannot be null");
         // If className starts with a period, assume the package is 'android.widget'
         if (className.charAt(0) == '.') {
             return clazz("android.widget", className.substring(1));
@@ -126,9 +127,8 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector clazz(@NonNull String packageName, @NonNull String className) {
-        checkNotNull(packageName, "packageName cannot be null");
-        checkNotNull(className, "className cannot be null");
-
+        requireNonNull(packageName, "packageName cannot be null");
+        requireNonNull(className, "className cannot be null");
         return clazz(Pattern.compile(Pattern.quote(
                 String.format("%s.%s", packageName, className))));
     }
@@ -141,8 +141,7 @@
      * @return A reference to this {@link BySelector}
      */
     public @NonNull BySelector clazz(@NonNull Class clazz) {
-        checkNotNull(clazz, "clazz cannot be null");
-
+        requireNonNull(clazz, "clazz cannot be null");
         return clazz(Pattern.compile(Pattern.quote(clazz.getName())));
     }
 
@@ -155,8 +154,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector clazz(@NonNull Pattern className) {
-        checkNotNull(className, "className cannot be null");
-
+        requireNonNull(className, "className cannot be null");
         if (mClazz != null) {
             throw new IllegalStateException("Class selector is already defined");
         }
@@ -173,8 +171,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector desc(@NonNull String contentDescription) {
-        checkNotNull(contentDescription, "contentDescription cannot be null");
-
+        requireNonNull(contentDescription, "contentDescription cannot be null");
         return desc(Pattern.compile(Pattern.quote(contentDescription)));
     }
 
@@ -187,8 +184,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector descContains(@NonNull String substring) {
-        checkNotNull(substring, "substring cannot be null");
-
+        requireNonNull(substring, "substring cannot be null");
         return desc(RegexHelper.getPatternContains(substring));
     }
 
@@ -201,8 +197,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector descStartsWith(@NonNull String substring) {
-        checkNotNull(substring, "substring cannot be null");
-
+        requireNonNull(substring, "substring cannot be null");
         return desc(RegexHelper.getPatternStartsWith(substring));
     }
 
@@ -215,8 +210,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector descEndsWith(@NonNull String substring) {
-        checkNotNull(substring, "substring cannot be null");
-
+        requireNonNull(substring, "substring cannot be null");
         return desc(RegexHelper.getPatternEndsWith(substring));
     }
 
@@ -229,8 +223,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector desc(@NonNull Pattern contentDescription) {
-        checkNotNull(contentDescription, "contentDescription cannot be null");
-
+        requireNonNull(contentDescription, "contentDescription cannot be null");
         if (mDesc != null) {
             throw new IllegalStateException("Description selector is already defined");
         }
@@ -247,8 +240,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector pkg(@NonNull String applicationPackage) {
-        checkNotNull(applicationPackage, "applicationPackage cannot be null");
-
+        requireNonNull(applicationPackage, "applicationPackage cannot be null");
         return pkg(Pattern.compile(Pattern.quote(applicationPackage)));
     }
 
@@ -261,8 +253,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector pkg(@NonNull Pattern applicationPackage) {
-        checkNotNull(applicationPackage, "applicationPackage cannot be null");
-
+        requireNonNull(applicationPackage, "applicationPackage cannot be null");
         if (mPkg != null) {
             throw new IllegalStateException("Package selector is already defined");
         }
@@ -279,8 +270,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector res(@NonNull String resourceName) {
-        checkNotNull(resourceName, "resourceName cannot be null");
-
+        requireNonNull(resourceName, "resourceName cannot be null");
         return res(Pattern.compile(Pattern.quote(resourceName)));
     }
 
@@ -294,9 +284,8 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector res(@NonNull String resourcePackage, @NonNull String resourceId) {
-        checkNotNull(resourcePackage, "resourcePackage cannot be null");
-        checkNotNull(resourceId, "resourceId cannot be null");
-
+        requireNonNull(resourcePackage, "resourcePackage cannot be null");
+        requireNonNull(resourceId, "resourceId cannot be null");
         return res(Pattern.compile(Pattern.quote(
                 String.format("%s:id/%s", resourcePackage, resourceId))));
     }
@@ -310,8 +299,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector res(@NonNull Pattern resourceName) {
-        checkNotNull(resourceName, "resourceName cannot be null");
-
+        requireNonNull(resourceName, "resourceName cannot be null");
         if (mRes != null) {
             throw new IllegalStateException("Resource name selector is already defined");
         }
@@ -328,8 +316,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector text(@NonNull String textValue) {
-        checkNotNull(textValue, "textValue cannot be null");
-
+        requireNonNull(textValue, "textValue cannot be null");
         return text(Pattern.compile(Pattern.quote(textValue)));
     }
 
@@ -342,8 +329,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector textContains(@NonNull String substring) {
-        checkNotNull(substring, "substring cannot be null");
-
+        requireNonNull(substring, "substring cannot be null");
         return text(RegexHelper.getPatternContains(substring));
     }
 
@@ -356,8 +342,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector textStartsWith(@NonNull String substring) {
-        checkNotNull(substring, "substring cannot be null");
-
+        requireNonNull(substring, "substring cannot be null");
         return text(RegexHelper.getPatternStartsWith(substring));
     }
 
@@ -370,8 +355,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector textEndsWith(@NonNull String substring) {
-        checkNotNull(substring, "substring cannot be null");
-
+        requireNonNull(substring, "substring cannot be null");
         return text(RegexHelper.getPatternEndsWith(substring));
     }
 
@@ -383,8 +367,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector text(@NonNull Pattern textValue) {
-        checkNotNull(textValue, "textValue cannot be null");
-
+        requireNonNull(textValue, "textValue cannot be null");
         if (mText != null) {
             throw new IllegalStateException("Text selector is already defined");
         }
@@ -581,7 +564,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector hasParent(@NonNull BySelector parentSelector) {
-        checkNotNull(parentSelector, "parentSelector cannot be null");
+        requireNonNull(parentSelector, "parentSelector cannot be null");
         return hasAncestor(parentSelector, 1);
     }
 
@@ -594,7 +577,7 @@
      * @return A reference to this {@link BySelector}.
      */
     public @NonNull BySelector hasAncestor(@NonNull BySelector ancestorSelector) {
-        checkNotNull(ancestorSelector, "ancestorSelector cannot be null");
+        requireNonNull(ancestorSelector, "ancestorSelector cannot be null");
         if (mAncestorSelector != null) {
             throw new IllegalStateException("Parent/ancestor selector is already defined");
         }
@@ -631,7 +614,7 @@
      * @throws IllegalArgumentException if the selector has a parent/ancestor selector
      */
     public @NonNull BySelector hasChild(@NonNull BySelector childSelector) {
-        checkNotNull(childSelector, "childSelector cannot be null");
+        requireNonNull(childSelector, "childSelector cannot be null");
         return hasDescendant(childSelector, 1);
     }
 
@@ -646,7 +629,7 @@
      * @throws IllegalArgumentException if the selector has a parent/ancestor selector
      */
     public @NonNull BySelector hasDescendant(@NonNull BySelector descendantSelector) {
-        checkNotNull(descendantSelector, "descendantSelector cannot be null");
+        requireNonNull(descendantSelector, "descendantSelector cannot be null");
         if (descendantSelector.mAncestorSelector != null) {
             // Search root is ambiguous with nested parent selectors.
             throw new IllegalArgumentException(
@@ -735,11 +718,4 @@
         builder.append("]");
         return builder.toString();
     }
-
-    private static <T> T checkNotNull(T value, @NonNull String message) {
-        if (value == null) {
-            throw new NullPointerException(message);
-        }
-        return value;
-    }
 }
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java
index 5cb978a..0eda6d6 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiSelector.java
@@ -16,6 +16,8 @@
 
 package androidx.test.uiautomator;
 
+import static java.util.Objects.requireNonNull;
+
 import android.util.SparseArray;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -109,7 +111,7 @@
      */
     @NonNull
     public UiSelector text(@NonNull String text) {
-        checkNotNull(text, "text cannot be null");
+        requireNonNull(text, "text cannot be null");
         return buildSelector(SELECTOR_TEXT, text);
     }
 
@@ -125,7 +127,7 @@
      */
     @NonNull
     public UiSelector textMatches(@NonNull String regex) {
-        checkNotNull(regex, "regex cannot be null");
+        requireNonNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_TEXT_REGEX, Pattern.compile(regex, Pattern.DOTALL));
     }
 
@@ -140,7 +142,7 @@
      */
     @NonNull
     public UiSelector textStartsWith(@NonNull String text) {
-        checkNotNull(text, "text cannot be null");
+        requireNonNull(text, "text cannot be null");
         return buildSelector(SELECTOR_START_TEXT, text);
     }
 
@@ -155,7 +157,7 @@
      */
     @NonNull
     public UiSelector textContains(@NonNull String text) {
-        checkNotNull(text, "text cannot be null");
+        requireNonNull(text, "text cannot be null");
         return buildSelector(SELECTOR_CONTAINS_TEXT, text);
     }
 
@@ -168,7 +170,7 @@
      */
     @NonNull
     public UiSelector className(@NonNull String className) {
-        checkNotNull(className, "className cannot be null");
+        requireNonNull(className, "className cannot be null");
         return buildSelector(SELECTOR_CLASS, className);
     }
 
@@ -181,7 +183,7 @@
      */
     @NonNull
     public UiSelector classNameMatches(@NonNull String regex) {
-        checkNotNull(regex, "regex cannot be null");
+        requireNonNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_CLASS_REGEX, Pattern.compile(regex));
     }
 
@@ -194,7 +196,7 @@
      */
     @NonNull
     public <T> UiSelector className(@NonNull Class<T> type) {
-        checkNotNull(type, "type cannot be null");
+        requireNonNull(type, "type cannot be null");
         return buildSelector(SELECTOR_CLASS, type.getName());
     }
 
@@ -216,7 +218,7 @@
      */
     @NonNull
     public UiSelector description(@NonNull String desc) {
-        checkNotNull(desc, "desc cannot be null");
+        requireNonNull(desc, "desc cannot be null");
         return buildSelector(SELECTOR_DESCRIPTION, desc);
     }
 
@@ -236,7 +238,7 @@
      */
     @NonNull
     public UiSelector descriptionMatches(@NonNull String regex) {
-        checkNotNull(regex, "regex cannot be null");
+        requireNonNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_DESCRIPTION_REGEX, Pattern.compile(regex, Pattern.DOTALL));
     }
 
@@ -258,7 +260,7 @@
      */
     @NonNull
     public UiSelector descriptionStartsWith(@NonNull String desc) {
-        checkNotNull(desc, "desc cannot be null");
+        requireNonNull(desc, "desc cannot be null");
         return buildSelector(SELECTOR_START_DESCRIPTION, desc);
     }
 
@@ -280,7 +282,7 @@
      */
     @NonNull
     public UiSelector descriptionContains(@NonNull String desc) {
-        checkNotNull(desc, "desc cannot be null");
+        requireNonNull(desc, "desc cannot be null");
         return buildSelector(SELECTOR_CONTAINS_DESCRIPTION, desc);
     }
 
@@ -292,7 +294,7 @@
      */
     @NonNull
     public UiSelector resourceId(@NonNull String id) {
-        checkNotNull(id, "id cannot be null");
+        requireNonNull(id, "id cannot be null");
         return buildSelector(SELECTOR_RESOURCE_ID, id);
     }
 
@@ -305,7 +307,7 @@
      */
     @NonNull
     public UiSelector resourceIdMatches(@NonNull String regex) {
-        checkNotNull(regex, "regex cannot be null");
+        requireNonNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_RESOURCE_ID_REGEX, Pattern.compile(regex));
     }
 
@@ -537,7 +539,7 @@
      */
     @NonNull
     public UiSelector childSelector(@NonNull UiSelector selector) {
-        checkNotNull(selector, "selector cannot be null");
+        requireNonNull(selector, "selector cannot be null");
         return buildSelector(SELECTOR_CHILD, selector);
     }
 
@@ -561,7 +563,7 @@
      */
     @NonNull
     public UiSelector fromParent(@NonNull UiSelector selector) {
-        checkNotNull(selector, "selector cannot be null");
+        requireNonNull(selector, "selector cannot be null");
         return buildSelector(SELECTOR_PARENT, selector);
     }
 
@@ -574,7 +576,7 @@
      */
     @NonNull
     public UiSelector packageName(@NonNull String name) {
-        checkNotNull(name, "name cannot be null");
+        requireNonNull(name, "name cannot be null");
         return buildSelector(SELECTOR_PACKAGE_NAME, name);
     }
 
@@ -587,7 +589,7 @@
      */
     @NonNull
     public UiSelector packageNameMatches(@NonNull String regex) {
-        checkNotNull(regex, "regex cannot be null");
+        requireNonNull(regex, "regex cannot be null");
         return buildSelector(SELECTOR_PACKAGE_NAME_REGEX, Pattern.compile(regex));
     }
 
@@ -1022,11 +1024,4 @@
         builder.append("]");
         return builder.toString();
     }
-
-    private static <T> T checkNotNull(T value, @NonNull String message) {
-        if (value == null) {
-            throw new NullPointerException(message);
-        }
-        return value;
-    }
 }
diff --git a/testutils/testutils-fonts/build.gradle b/testutils/testutils-fonts/build.gradle
index dffd039..911cf9b 100644
--- a/testutils/testutils-fonts/build.gradle
+++ b/testutils/testutils-fonts/build.gradle
@@ -14,12 +14,20 @@
  * limitations under the License.
  */
 
+
+import androidx.build.KmpPlatformsKt
 import androidx.build.LibraryType
 
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
-    id("kotlin-android")
+}
+
+def desktopEnabled = KmpPlatformsKt.enableDesktop(project)
+
+androidXMultiplatform {
+    android()
+    if (desktopEnabled) desktop()
 }
 
 dependencies {
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/current.txt b/wear/protolayout/protolayout-expression-pipeline/api/current.txt
index 814cdba..18e01b6 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/current.txt
@@ -56,6 +56,7 @@
   public class StateStore {
     method public static androidx.wear.protolayout.expression.pipeline.StateStore create(java.util.Map<java.lang.String!,androidx.wear.protolayout.expression.StateEntryBuilders.StateEntryValue!>);
     method @UiThread public void setStateEntryValues(java.util.Map<java.lang.String!,androidx.wear.protolayout.expression.StateEntryBuilders.StateEntryValue!>);
+    field public static final int MAX_STATE_ENTRY_COUNT = 100; // 0x64
   }
 
   public interface TimeGateway {
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt
index 814cdba..18e01b6 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/public_plus_experimental_current.txt
@@ -56,6 +56,7 @@
   public class StateStore {
     method public static androidx.wear.protolayout.expression.pipeline.StateStore create(java.util.Map<java.lang.String!,androidx.wear.protolayout.expression.StateEntryBuilders.StateEntryValue!>);
     method @UiThread public void setStateEntryValues(java.util.Map<java.lang.String!,androidx.wear.protolayout.expression.StateEntryBuilders.StateEntryValue!>);
+    field public static final int MAX_STATE_ENTRY_COUNT = 100; // 0x64
   }
 
   public interface TimeGateway {
diff --git a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
index 62aa8aa..6dd8857 100644
--- a/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-expression-pipeline/api/restricted_current.txt
@@ -58,6 +58,7 @@
     method public static androidx.wear.protolayout.expression.pipeline.StateStore create(java.util.Map<java.lang.String!,androidx.wear.protolayout.expression.StateEntryBuilders.StateEntryValue!>);
     method @UiThread public void setStateEntryValues(java.util.Map<java.lang.String!,androidx.wear.protolayout.expression.StateEntryBuilders.StateEntryValue!>);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @UiThread public void setStateEntryValuesProto(java.util.Map<java.lang.String!,androidx.wear.protolayout.expression.proto.StateEntryProto.StateEntryValue!>);
+    field public static final int MAX_STATE_ENTRY_COUNT = 100; // 0x64
   }
 
   public interface TimeGateway {
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
index 068ce4b..3deccea 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
@@ -250,8 +250,8 @@
 
         /**
          * Gets the quota manager used for limiting the total number of dynamic types in the
-         * pipeline, or {@code null} if there are no restriction on the number of dynamic types.
-         * If present, the quota manager is used to prevent unreasonably expensive expressions.
+         * pipeline, or {@code null} if there are no restriction on the number of dynamic types. If
+         * present, the quota manager is used to prevent unreasonably expensive expressions.
          */
         @Nullable
         public QuotaManager getDynamicTypesQuotaManager() {
@@ -303,8 +303,8 @@
         MainThreadExecutor uiExecutor = new MainThreadExecutor(uiHandler);
         TimeGateway timeGateway = config.getTimeGateway();
         if (timeGateway == null) {
-                timeGateway = new TimeGatewayImpl(uiHandler);
-                ((TimeGatewayImpl) timeGateway).enableUpdates();
+            timeGateway = new TimeGatewayImpl(uiHandler);
+            ((TimeGatewayImpl) timeGateway).enableUpdates();
         }
         this.mTimeDataSource = new EpochTimePlatformDataSource(uiExecutor, timeGateway);
         if (config.getSensorGateway() != null) {
@@ -331,7 +331,7 @@
         if (!mDynamicTypesQuotaManager.tryAcquireQuota(boundDynamicType.getDynamicNodeCount())) {
             throw new EvaluationException(
                     "Dynamic type expression limit reached. Try making the dynamic type expression"
-                        + " shorter or reduce the number of dynamic type expressions.");
+                            + " shorter or reduce the number of dynamic type expressions.");
         }
         return boundDynamicType;
     }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java
index c2ce371..f722bbb 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/StateStore.java
@@ -18,6 +18,8 @@
 
 import static java.util.stream.Collectors.toMap;
 
+import android.annotation.SuppressLint;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
@@ -41,13 +43,27 @@
  * must only be used from the UI thread.
  */
 public class StateStore {
+    /**
+     * Maximum number for state entries allowed for this {@link StateStore}.
+     *
+     * <p>The ProtoLayout state model is not designed to handle large volumes of layout provided
+     * state. So we limit the number of state entries to keep the on-the-wire size and state
+     * store update times manageable.
+     */
+    @SuppressLint("MinMaxConstant")
+    public static final int MAX_STATE_ENTRY_COUNT = 100;
     @NonNull private final Map<String, StateEntryValue> mCurrentState = new ArrayMap<>();
 
     @NonNull
     private final Map<String, Set<DynamicTypeValueReceiverWithPreUpdate<StateEntryValue>>>
             mRegisteredCallbacks = new ArrayMap<>();
 
-    /** Creates a {@link StateStore}. */
+    /**
+     * Creates a {@link StateStore}.
+     *
+     * @throws IllegalStateException if number of initialState entries is greater than
+     * {@link StateStore#MAX_STATE_ENTRY_COUNT}.
+     */
     @NonNull
     public static StateStore create(
             @NonNull Map<String, StateEntryBuilders.StateEntryValue> initialState) {
@@ -56,6 +72,9 @@
 
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     public StateStore(@NonNull Map<String, StateEntryValue> initialState) {
+        if (initialState.size() > MAX_STATE_ENTRY_COUNT) {
+            throw stateTooLargeException(initialState.size());
+        }
         mCurrentState.putAll(initialState);
     }
 
@@ -63,6 +82,10 @@
      * Sets the given state, replacing the current state.
      *
      * <p>Informs registered listeners of changed values, invalidates removed values.
+     *
+     * @throws IllegalStateException if number of state entries is greater than
+     * {@link StateStore#MAX_STATE_ENTRY_COUNT}. The state will not update and old state entries
+     * will stay in place.
      */
     @UiThread
     public void setStateEntryValues(
@@ -74,10 +97,18 @@
      * Sets the given state, replacing the current state.
      *
      * <p>Informs registered listeners of changed values, invalidates removed values.
+     *
+     * @throws IllegalStateException if number of state entries is larger than
+     * {@link StateStore#MAX_STATE_ENTRY_COUNT}. The state will not update and old state entries
+     * will stay in place.
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
     @UiThread
     public void setStateEntryValuesProto(@NonNull Map<String, StateEntryValue> newState) {
+        if (newState.size() > MAX_STATE_ENTRY_COUNT) {
+            throw stateTooLargeException(newState.size());
+        }
+
         // Figure out which nodes have actually changed.
         Set<String> removedKeys = getRemovedKeys(newState);
         Map<String, StateEntryValue> changedEntries = getChangedEntries(newState);
@@ -85,10 +116,9 @@
         Stream.concat(removedKeys.stream(), changedEntries.keySet().stream())
                 .forEach(
                         key -> {
-                            for (DynamicTypeValueReceiverWithPreUpdate<StateEntryValue>
-                                    callback :
-                                            mRegisteredCallbacks.getOrDefault(
-                                                    key, Collections.emptySet())) {
+                            for (DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> callback :
+                                    mRegisteredCallbacks.getOrDefault(
+                                            key, Collections.emptySet())) {
                                 callback.onPreUpdate();
                             }
                         });
@@ -168,4 +198,12 @@
         }
         return result;
     }
+
+    static IllegalStateException stateTooLargeException(int stateSize) {
+        return new IllegalStateException(
+                String.format(
+                        "Too many state entries: %d. The maximum number of allowed state entries "
+                                + "is %d.",
+                        stateSize, MAX_STATE_ENTRY_COUNT));
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java
index d3a2bb4..ae1630d 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/StateStoreTest.java
@@ -16,6 +16,7 @@
 
 package androidx.wear.protolayout.expression.pipeline;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -38,6 +39,9 @@
 import org.mockito.InOrder;
 import org.mockito.Mockito;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @RunWith(AndroidJUnit4.class)
 public class StateStoreTest {
     @Rule public Expect mExpect = Expect.create();
@@ -48,6 +52,8 @@
                             "foo", buildStateEntry("bar"),
                             "baz", buildStateEntry("foobar")));
 
+    public StateStoreTest() {}
+
     @Test
     public void setBuilderApi() {
         mStateStoreUnderTest.setStateEntryValues(
@@ -58,6 +64,25 @@
     }
 
     @Test
+    public void initState_largeNumberOfEntries_throws() {
+        Map<String, StateEntryBuilders.StateEntryValue> state = new HashMap<>();
+        for (int i = 0; i < StateStore.MAX_STATE_ENTRY_COUNT + 10; i++) {
+            state.put(Integer.toString(i), StateEntryBuilders.StateEntryValue.fromString("baz"));
+        }
+        assertThrows(IllegalStateException.class, () -> StateStore.create(state));
+    }
+
+    @Test
+    public void newState_largeNumberOfEntries_throws() {
+        Map<String, StateEntryBuilders.StateEntryValue> state = new HashMap<>();
+        for (int i = 0; i < StateStore.MAX_STATE_ENTRY_COUNT + 10; i++) {
+            state.put(Integer.toString(i), StateEntryBuilders.StateEntryValue.fromString("baz"));
+        }
+        assertThrows(
+                IllegalStateException.class, () -> mStateStoreUnderTest.setStateEntryValues(state));
+    }
+
+    @Test
     public void canReadInitialState() {
         mExpect.that(mStateStoreUnderTest.getStateEntryValuesProto("foo"))
                 .isEqualTo(buildStateEntry("bar"));
@@ -88,8 +113,7 @@
 
     @Test
     public void setStateFiresListeners() {
-        DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> cb =
-                buildStateUpdateCallbackMock();
+        DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> cb = buildStateUpdateCallbackMock();
         mStateStoreUnderTest.registerCallback("foo", cb);
 
         mStateStoreUnderTest.setStateEntryValuesProto(
@@ -101,8 +125,7 @@
 
     @Test
     public void setStateFiresOnPreStateUpdateFirst() {
-        DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> cb =
-                buildStateUpdateCallbackMock();
+        DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> cb = buildStateUpdateCallbackMock();
 
         InOrder inOrder = Mockito.inOrder(cb);
 
@@ -166,8 +189,7 @@
     @SuppressWarnings("unchecked")
     @Test
     public void canUnregisterListeners() {
-        DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> cb =
-                buildStateUpdateCallbackMock();
+        DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> cb = buildStateUpdateCallbackMock();
         mStateStoreUnderTest.registerCallback("foo", cb);
 
         mStateStoreUnderTest.setStateEntryValuesProto(
@@ -183,8 +205,7 @@
     }
 
     @SuppressWarnings("unchecked")
-    private DynamicTypeValueReceiverWithPreUpdate<StateEntryValue>
-            buildStateUpdateCallbackMock() {
+    private DynamicTypeValueReceiverWithPreUpdate<StateEntryValue> buildStateUpdateCallbackMock() {
         // This needs an unchecked cast because of the generic; this method just centralizes the
         // warning suppression.
         return mock(DynamicTypeValueReceiverWithPreUpdate.class);
diff --git a/wear/protolayout/protolayout-proto/src/main/proto/action.proto b/wear/protolayout/protolayout-proto/src/main/proto/action.proto
index 72e7e95..589a31c 100644
--- a/wear/protolayout/protolayout-proto/src/main/proto/action.proto
+++ b/wear/protolayout/protolayout-proto/src/main/proto/action.proto
@@ -54,11 +54,11 @@
 
 // A launch action to send an intent to an Android activity.
 message AndroidActivity {
-  // The package name to send the intent to, for example, "com.google.weather".
+  // The package name to send the intent to, for example, "com.example.weather".
   string package_name = 1;
 
   // The fully qualified class name (including the package) to send the intent
-  // to, for example, "com.google.weather.WeatherOverviewActivity".
+  // to, for example, "com.example.weather.WeatherOverviewActivity".
   string class_name = 2;
 
   // The extras to be included in the intent.
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
index 9b7d7ac..d89a564 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
@@ -168,7 +168,8 @@
     }
 
     @Test
-    public void buildPipeline_dpProp_animatable_animationsDisabled_hasStaticValue_assignsEndVal() {
+    public void
+            buildPipeline_dpProp_animatable_animationsDisabled_hasStaticValue_assignsEndValue() {
         List<Float> results = new ArrayList<>();
         float endValue = 10.0f;
         DynamicFloat dynamicFloat = animatableFixedFloat(5.0f, endValue);
@@ -183,7 +184,7 @@
 
     @Test
     public void
-            buildPipeline_degreesProp_animatable_animationsDisabled_hasStaticValue_assignsEndVal() {
+            buildPipeline_degreesProp_animatable_animationsDisabled_hasStaticValue_assignsEndValue() {
         List<Float> results = new ArrayList<>();
         float endValue = 10.0f;
         DynamicFloat dynamicFloat = animatableFixedFloat(5.0f, endValue);
@@ -217,7 +218,7 @@
 
     @Test
     public void
-            buildPipeline_colorProp_animatable_animationsDisabled_noStaticValueSet_assignsEndVal() {
+            buildPipeline_colorProp_animatable_animationsDisabled_noStaticValueSet_assignsEndValue() {
         List<Integer> results = new ArrayList<>();
         DynamicColor dynamicColor = animatableFixedColor(0, 1);
         ColorProp colorProp = ColorProp.newBuilder().setDynamicValue(dynamicColor).build();
@@ -1719,8 +1720,7 @@
                                                         Repeatable.newBuilder()
                                                                 .setRepeatMode(
                                                                         RepeatMode
-                                                                                .REPEAT_MODE_REVERSE
-                                                                )
+                                                                                .REPEAT_MODE_REVERSE)
                                                                 .setIterations(iterations)
                                                                 .setForwardRepeatOverride(
                                                                         alternateParameters)
@@ -1813,13 +1813,12 @@
         ProtoLayoutDynamicDataPipeline pipeline =
                 enableAnimations
                         ? new ProtoLayoutDynamicDataPipeline(
-                        /* sensorGateway= */ null,
+                                /* sensorGateway= */ null,
                                 mStateStore,
                                 new FixedQuotaManagerImpl(MAX_VALUE),
                                 new FixedQuotaManagerImpl(MAX_VALUE))
                         : new ProtoLayoutDynamicDataPipeline(
-                                /* sensorGateway= */ null,
-                                mStateStore);
+                                /* sensorGateway= */ null, mStateStore);
         shadowOf(getMainLooper()).idle();
 
         pipeline.setFullyVisible(true);
@@ -1842,7 +1841,7 @@
         AddToListCallback<Float> receiver =
                 new AddToListCallback<>(results, /* invalidList= */ null);
         ProtoLayoutDynamicDataPipeline pipeline =
-                new ProtoLayoutDynamicDataPipeline(  /* sensorGateway= */ null, mStateStore);
+                new ProtoLayoutDynamicDataPipeline(/* sensorGateway= */ null, mStateStore);
         shadowOf(getMainLooper()).idle();
 
         pipeline.setFullyVisible(true);
@@ -1860,7 +1859,7 @@
         AddToListCallback<Integer> receiver =
                 new AddToListCallback<>(results, /* invalidList= */ null);
         ProtoLayoutDynamicDataPipeline pipeline =
-                new ProtoLayoutDynamicDataPipeline(  /* sensorGateway= */ null, mStateStore);
+                new ProtoLayoutDynamicDataPipeline(/* sensorGateway= */ null, mStateStore);
         shadowOf(getMainLooper()).idle();
 
         pipeline.setFullyVisible(true);
@@ -1878,7 +1877,7 @@
         AddToListCallback<Float> receiver =
                 new AddToListCallback<>(results, /* invalidList= */ null);
         ProtoLayoutDynamicDataPipeline pipeline =
-                new ProtoLayoutDynamicDataPipeline(  /* sensorGateway= */ null, mStateStore);
+                new ProtoLayoutDynamicDataPipeline(/* sensorGateway= */ null, mStateStore);
         shadowOf(getMainLooper()).idle();
 
         pipeline.setFullyVisible(true);
@@ -1896,7 +1895,7 @@
         AddToListCallback<Float> receiver =
                 new AddToListCallback<>(results, /* invalidList= */ null);
         ProtoLayoutDynamicDataPipeline pipeline =
-                new ProtoLayoutDynamicDataPipeline(  /* sensorGateway= */ null, mStateStore);
+                new ProtoLayoutDynamicDataPipeline(/* sensorGateway= */ null, mStateStore);
         shadowOf(getMainLooper()).idle();
 
         pipeline.setFullyVisible(true);
@@ -1914,7 +1913,7 @@
         AddToListCallback<Integer> receiver =
                 new AddToListCallback<>(results, /* invalidList= */ null);
         ProtoLayoutDynamicDataPipeline pipeline =
-                new ProtoLayoutDynamicDataPipeline(  /* sensorGateway= */ null, mStateStore);
+                new ProtoLayoutDynamicDataPipeline(/* sensorGateway= */ null, mStateStore);
         shadowOf(getMainLooper()).idle();
 
         pipeline.setFullyVisible(true);
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 9e263bf..9a529c2 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
@@ -4205,8 +4205,9 @@
     private static FadeInTransition.Builder fadeIn(int delay) {
         return FadeInTransition.newBuilder()
                 .setAnimationSpec(
-                        AnimationSpec.newBuilder().setAnimationParameters(
-                                AnimationParameters.newBuilder().setDelayMillis(delay)));
+                        AnimationSpec.newBuilder()
+                                .setAnimationParameters(
+                                        AnimationParameters.newBuilder().setDelayMillis(delay)));
     }
 
     private LayoutElement textFadeInSlideIn(String text) {
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/StateBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/StateBuilders.java
index 8889ec0..0fb8101 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/StateBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/StateBuilders.java
@@ -149,9 +149,17 @@
         return this;
       }
 
+      private static final int MAX_STATE_SIZE = 30;
+
       /** Builds an instance from accumulated values. */
       @NonNull
       public State build() {
+        if (mImpl.getIdToValueMap().size() > MAX_STATE_SIZE) {
+          throw new IllegalStateException(
+                  String.format(
+                          "State size is too large: %d. Maximum " + "allowed state size is %d.",
+                          mImpl.getIdToValueMap().size(), MAX_STATE_SIZE));
+        }
         return new State(mImpl.build(), mFingerprint);
       }
     }
diff --git a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
index 184aa81..3fb2267 100644
--- a/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
+++ b/wear/tiles/tiles-renderer/src/main/java/androidx/wear/tiles/renderer/TileRenderer.java
@@ -202,15 +202,18 @@
     public View inflate(@NonNull ViewGroup parent) {
         String errorMessage =
                 "This method only works with the deprecated constructors that accept Layout and"
-                    + " Resources.";
+                        + " Resources.";
         try {
             // Waiting for the result from future for backwards compatibility.
             return inflateLayout(
-                    checkNotNull(mLayout, errorMessage),
-                    checkNotNull(mResources, errorMessage),
-                    parent).get(10, TimeUnit.SECONDS);
-        } catch (ExecutionException | InterruptedException | CancellationException |
-                 TimeoutException e) {
+                            checkNotNull(mLayout, errorMessage),
+                            checkNotNull(mResources, errorMessage),
+                            parent)
+                    .get(10, TimeUnit.SECONDS);
+        } catch (ExecutionException
+                | InterruptedException
+                | CancellationException
+                | TimeoutException e) {
             // Wrap checked exceptions to avoid changing the method signature.
             throw new RuntimeException("Rendering tile has not successfully finished.", e);
         }
@@ -219,13 +222,12 @@
     /**
      * Inflates a Tile into {@code parent}.
      *
-     * @param layout    The portion of the Tile to render.
+     * @param layout The portion of the Tile to render.
      * @param resources The resources for the Tile.
-     * @param parent    The view to attach the tile into.
+     * @param parent The view to attach the tile into.
      * @return The future with the first child that was inflated. This may be null if the Layout is
-     * empty or the top-level LayoutElement has no inner set, or the top-level LayoutElement
-     * contains an
-     * unsupported inner type.
+     *     empty or the top-level LayoutElement has no inner set, or the top-level LayoutElement
+     *     contains an unsupported inner type.
      */
     @NonNull
     public ListenableFuture<View> inflateAsync(
@@ -241,7 +243,6 @@
             @NonNull ResourceProto.Resources resources,
             @NonNull ViewGroup parent) {
         ListenableFuture<Void> result = mInstance.renderAndAttach(layout, resources, parent);
-            return FluentFuture.from(result)
-                    .transform(ignored -> parent.getChildAt(0), mUiExecutor);
+        return FluentFuture.from(result).transform(ignored -> parent.getChildAt(0), mUiExecutor);
     }
 }
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
index 8887171d..949c589 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
@@ -1836,13 +1836,13 @@
          *
          * Returns this Builder to allow chaining.
          */
-        fun setListEntryCollection(timelineEntries: Collection<ComplicationData>?) = apply {
-            if (timelineEntries == null) {
+        fun setListEntryCollection(listEntries: Collection<ComplicationData>?) = apply {
+            if (listEntries == null) {
                 fields.remove(EXP_FIELD_LIST_ENTRIES)
             } else {
                 fields.putParcelableArray(
                     EXP_FIELD_LIST_ENTRIES,
-                    timelineEntries
+                    listEntries
                         .map { data ->
                             data.fields.putInt(EXP_FIELD_LIST_ENTRY_TYPE, data.type)
                             data.fields
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
index 94db677..ddf43de 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
@@ -18,7 +18,7 @@
 
 import android.icu.util.ULocale
 import android.support.wearable.complications.ComplicationData as WireComplicationData
-import android.support.wearable.complications.ComplicationData
+import android.support.wearable.complications.ComplicationData.Companion.TYPE_NO_DATA
 import android.support.wearable.complications.ComplicationText as WireComplicationText
 import androidx.annotation.MainThread
 import androidx.annotation.RestrictTo
@@ -41,17 +41,19 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.flow.updateAndGet
 import kotlinx.coroutines.invoke
 import kotlinx.coroutines.launch
 
 /**
  * Evaluates a [WireComplicationData] with
  * [androidx.wear.protolayout.expression.DynamicBuilders.DynamicType] within its fields.
- *
- * Due to [WireComplicationData]'s shallow copy strategy the input is modified in-place.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 class ComplicationDataExpressionEvaluator(
@@ -65,27 +67,101 @@
      *
      * The expression is evaluated _separately_ on each flow collection.
      */
-    fun evaluate(unevaluatedData: WireComplicationData) =
-        flow<WireComplicationData> {
-            val state: MutableStateFlow<State> = unevaluatedData.buildState()
-            state.value.use {
-                val evaluatedData: Flow<WireComplicationData> =
-                    state
-                        .mapNotNull {
-                            when {
-                                // Emitting INVALID_DATA if there's an invalid receiver.
-                                it.invalidReceivers.isNotEmpty() -> INVALID_DATA
-                                // Emitting the data if all pending receivers are done and all
-                                // pre-updates are satisfied.
-                                it.pendingReceivers.isEmpty() -> it.data
-                                // Skipping states that are not ready for be emitted.
-                                else -> null
-                            }
-                        }
-                        .distinctUntilChanged()
-                emitAll(evaluatedData)
+    fun evaluate(unevaluatedData: WireComplicationData): Flow<WireComplicationData> =
+        evaluateTopLevelFields(unevaluatedData)
+            // Combining with fields that are made of WireComplicationData.
+            .combineWithDataList(unevaluatedData.timelineEntries) { entries ->
+                // Timeline entries are set on the built WireComplicationData.
+                WireComplicationData.Builder(
+                    this@combineWithDataList.build().apply { setTimelineEntryCollection(entries) }
+                )
             }
+            .combineWithDataList(unevaluatedData.listEntries) { setListEntryCollection(it) }
+            // Must be last, as it overwrites INVALID_DATA.
+            .combineWithEvaluatedPlaceholder(unevaluatedData.placeholder)
+            .distinctUntilChanged()
+
+    /** Evaluates "local" fields, excluding fields of type WireComplicationData. */
+    private fun evaluateTopLevelFields(
+        unevaluatedData: WireComplicationData
+    ): Flow<WireComplicationData> = flow {
+        val state: MutableStateFlow<State> = unevaluatedData.buildState()
+        state.value.use {
+            val evaluatedData: Flow<WireComplicationData> =
+                state.mapNotNull {
+                    when {
+                        // Emitting INVALID_DATA if there's an invalid receiver.
+                        it.invalidReceivers.isNotEmpty() -> INVALID_DATA
+                        // Emitting the data if all pending receivers are done and all
+                        // pre-updates are satisfied.
+                        it.pendingReceivers.isEmpty() -> it.data
+                        // Skipping states that are not ready for be emitted.
+                        else -> null
+                    }
+                }
+            emitAll(evaluatedData)
         }
+    }
+
+    /**
+     * Combines the receiver with the evaluated version of the provided list.
+     *
+     * If the receiver [Flow] emits [INVALID_DATA] or the input list is null or empty, this does not
+     * mutate the flow and does not wait for the entries to finish evaluating.
+     *
+     * If even one [WireComplicationData] within the provided list is evaluated to [INVALID_DATA],
+     * the output [Flow] becomes [INVALID_DATA] (the receiver [Flow] is ignored).
+     */
+    private fun Flow<WireComplicationData>.combineWithDataList(
+        unevaluatedEntries: List<WireComplicationData>?,
+        setter:
+            WireComplicationData.Builder.(
+                List<WireComplicationData>
+            ) -> WireComplicationData.Builder,
+    ): Flow<WireComplicationData> {
+        if (unevaluatedEntries.isNullOrEmpty()) return this
+        val evaluatedEntriesFlow: Flow<List<WireComplicationData>> =
+            combine(unevaluatedEntries.map { evaluate(it) })
+
+        return this.combine(evaluatedEntriesFlow).map {
+            (data: WireComplicationData, evaluatedEntries: List<WireComplicationData>?) ->
+            // Not mutating if invalid.
+            if (data === INVALID_DATA) return@map data
+            // An entry is invalid, emitting invalid.
+            if (evaluatedEntries.any { it === INVALID_DATA }) return@map INVALID_DATA
+            // All is well, mutating the input.
+            return@map WireComplicationData.Builder(data).setter(evaluatedEntries).build()
+        }
+    }
+
+    /**
+     * Same as [combineWithDataList], but sets the evaluated placeholder ONLY when the receiver
+     * [Flow] emits [TYPE_NO_DATA], or [keepExpression] is true, otherwise clears it and does not
+     * wait for the placeholder to finish evaluating.
+     *
+     * If the placeholder is not required (per the above paragraph), this doesn't wait for it.
+     */
+    private fun Flow<WireComplicationData>.combineWithEvaluatedPlaceholder(
+        unevaluatedPlaceholder: WireComplicationData?
+    ): Flow<WireComplicationData> {
+        if (unevaluatedPlaceholder == null) return this
+        val evaluatedPlaceholderFlow: Flow<WireComplicationData> = evaluate(unevaluatedPlaceholder)
+
+        return this.combine(evaluatedPlaceholderFlow).map {
+            (data: WireComplicationData, evaluatedPlaceholder: WireComplicationData?) ->
+            if (!keepExpression && data.type != TYPE_NO_DATA) {
+                // Clearing the placeholder when data is not TYPE_NO_DATA (it was meant as an
+                // expression fallback).
+                return@map WireComplicationData.Builder(data).setPlaceholder(null).build()
+            }
+            // Placeholder required but invalid, emitting invalid.
+            if (evaluatedPlaceholder === INVALID_DATA) return@map INVALID_DATA
+            // All is well, mutating the input.
+            return@map WireComplicationData.Builder(data)
+                .setPlaceholder(evaluatedPlaceholder)
+                .build()
+        }
+    }
 
     private suspend fun WireComplicationData.buildState() =
         MutableStateFlow(State(this)).apply {
@@ -177,7 +253,7 @@
      * [ComplicationEvaluationResultReceiver] that are evaluating it.
      */
     private inner class State(
-        val data: ComplicationData,
+        val data: WireComplicationData,
         val pendingReceivers: Set<ComplicationEvaluationResultReceiver<out Any>> = setOf(),
         val invalidReceivers: Set<ComplicationEvaluationResultReceiver<out Any>> = setOf(),
         val completeReceivers: Set<ComplicationEvaluationResultReceiver<out Any>> = setOf(),
@@ -317,3 +393,35 @@
         runnable.run()
     }
 }
+
+/** Replacement of [kotlinx.coroutines.flow.combine], which doesn't seem to work. */
+internal fun <T> combine(flows: List<Flow<T>>): Flow<List<T>> = flow {
+    data class ValueExists(val value: T?, val exists: Boolean)
+    val latest = MutableStateFlow(List(flows.size) { ValueExists(null, false) })
+    @Suppress("UNCHECKED_CAST") // Flow<List<T?>> -> Flow<List<T>> safe after filtering exists.
+    emitAll(
+        flows
+            .mapIndexed { i, flow -> flow.map { i to it } } // List<Flow<Int, T>> (indexed flows)
+            .merge() // Flow<Int, T>
+            .map { (i, value) ->
+                // Updating latest and returning the current latest.
+                latest.updateAndGet {
+                    val newLatest = it.toMutableList()
+                    newLatest[i] = ValueExists(value, true)
+                    newLatest
+                }
+            } // Flow<List<ValueExists>>
+            // Filtering emissions until we have all values.
+            .filter { values -> values.all { it.exists } }
+            // Flow<List<T>> + defensive copy.
+            .map { values -> values.map { it.value } } as Flow<List<T>>
+    )
+}
+
+/**
+ * Another replacement of [kotlinx.coroutines.flow.combine] which is similar to
+ * `combine(List<Flow<T>>)` but allows different types for each flow.
+ */
+@Suppress("UNCHECKED_CAST")
+internal fun <T1, T2> Flow<T1>.combine(other: Flow<T2>): Flow<Pair<T1, T2>> =
+    combine(listOf(this as Flow<*>, other as Flow<*>)).map { (a, b) -> (a as T1) to (b as T2) }
diff --git a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluatorTest.kt b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluatorTest.kt
index c913c25..3a22917 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluatorTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluatorTest.kt
@@ -17,6 +17,8 @@
 package androidx.wear.watchface.complications.data
 
 import android.support.wearable.complications.ComplicationData as WireComplicationData
+import android.support.wearable.complications.ComplicationData.Companion.TYPE_NO_DATA
+import android.support.wearable.complications.ComplicationData.Companion.TYPE_SHORT_TEXT
 import android.support.wearable.complications.ComplicationText as WireComplicationText
 import android.util.Log
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
@@ -58,10 +60,7 @@
 
     @Test
     fun evaluate_noExpression_returnsUnevaluated() = runBlocking {
-        val data =
-            WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
-                .setRangedValue(10f)
-                .build()
+        val data = WireComplicationData.Builder(TYPE_NO_DATA).setRangedValue(10f).build()
 
         val evaluator = ComplicationDataExpressionEvaluator()
 
@@ -81,7 +80,7 @@
     ) {
         SET_IMMEDIATELY_WHEN_ALL_DATA_AVAILABLE(
             expressed =
-                WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+                WireComplicationData.Builder(TYPE_NO_DATA)
                     .setRangedValueExpression(DynamicFloat.constant(1f))
                     .setLongText(WireComplicationText(DynamicString.constant("Long Text")))
                     .setLongTitle(WireComplicationText(DynamicString.constant("Long Title")))
@@ -90,23 +89,29 @@
                     .setContentDescription(
                         WireComplicationText(DynamicString.constant("Description"))
                     )
-                    .build(),
+                    .setPlaceholder(constantData("Placeholder"))
+                    .setListEntryCollection(listOf(constantData("List")))
+                    .build()
+                    .also { it.setTimelineEntryCollection(listOf(constantData("Timeline"))) },
             states = listOf(),
             evaluated =
                 listOf(
-                    WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+                    WireComplicationData.Builder(TYPE_NO_DATA)
                         .setRangedValue(1f)
                         .setLongText(WireComplicationText("Long Text"))
                         .setLongTitle(WireComplicationText("Long Title"))
                         .setShortText(WireComplicationText("Short Text"))
                         .setShortTitle(WireComplicationText("Short Title"))
                         .setContentDescription(WireComplicationText("Description"))
+                        .setPlaceholder(evaluatedData("Placeholder"))
+                        .setListEntryCollection(listOf(evaluatedData("List")))
                         .build()
+                        .also { it.setTimelineEntryCollection(listOf(evaluatedData("Timeline"))) },
                 ),
         ),
         SET_ONLY_AFTER_ALL_FIELDS_EVALUATED(
             expressed =
-                WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+                WireComplicationData.Builder(TYPE_NO_DATA)
                     .setRangedValueExpression(DynamicFloat.fromState("ranged_value"))
                     .setLongText(WireComplicationText(DynamicString.fromState("long_text")))
                     .setLongTitle(WireComplicationText(DynamicString.fromState("long_title")))
@@ -115,7 +120,10 @@
                     .setContentDescription(
                         WireComplicationText(DynamicString.fromState("description"))
                     )
-                    .build(),
+                    .setPlaceholder(stateData("placeholder"))
+                    .setListEntryCollection(listOf(stateData("list")))
+                    .build()
+                    .also { it.setTimelineEntryCollection(listOf(stateData("timeline"))) },
             states =
                 aggregate(
                     // Each map piles on top of the previous ones.
@@ -124,25 +132,38 @@
                     mapOf("long_title" to StateEntryValue.fromString("Long Title")),
                     mapOf("short_text" to StateEntryValue.fromString("Short Text")),
                     mapOf("short_title" to StateEntryValue.fromString("Short Title")),
-                    // Only the last one will trigger an evaluated data.
                     mapOf("description" to StateEntryValue.fromString("Description")),
+                    mapOf("placeholder" to StateEntryValue.fromString("Placeholder")),
+                    mapOf("list" to StateEntryValue.fromString("List")),
+                    mapOf("timeline" to StateEntryValue.fromString("Timeline")),
+                    // Only the last one will trigger an evaluated data.
                 ),
             evaluated =
                 listOf(
-                    INVALID_DATA, // Before state is available.
-                    WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+                    // Before any state is available.
+                    INVALID_DATA,
+                    // INVALID_DATA with placeholder, after it's available (and others aren't).
+                    WireComplicationData.Builder(INVALID_DATA)
+                        .setPlaceholder(evaluatedData("Placeholder"))
+                        .build(),
+                    // Evaluated data with after everything is available.
+                    WireComplicationData.Builder(TYPE_NO_DATA)
                         .setRangedValue(1f)
                         .setLongText(WireComplicationText("Long Text"))
                         .setLongTitle(WireComplicationText("Long Title"))
                         .setShortText(WireComplicationText("Short Text"))
                         .setShortTitle(WireComplicationText("Short Title"))
                         .setContentDescription(WireComplicationText("Description"))
+                        // Not trimmed for TYPE_NO_DATA.
+                        .setPlaceholder(evaluatedData("Placeholder"))
+                        .setListEntryCollection(listOf(evaluatedData("List")))
                         .build()
+                        .also { it.setTimelineEntryCollection(listOf(evaluatedData("Timeline"))) },
                 ),
         ),
         SET_TO_EVALUATED_IF_ALL_FIELDS_VALID(
             expressed =
-                WireComplicationData.Builder(WireComplicationData.TYPE_SHORT_TEXT)
+                WireComplicationData.Builder(TYPE_SHORT_TEXT)
                     .setShortTitle(WireComplicationText(DynamicString.fromState("valid")))
                     .setShortText(WireComplicationText(DynamicString.fromState("valid")))
                     .build(),
@@ -153,7 +174,7 @@
             evaluated =
                 listOf(
                     INVALID_DATA, // Before state is available.
-                    WireComplicationData.Builder(WireComplicationData.TYPE_SHORT_TEXT)
+                    WireComplicationData.Builder(TYPE_SHORT_TEXT)
                         .setShortTitle(WireComplicationText("Valid"))
                         .setShortText(WireComplicationText("Valid"))
                         .build(),
@@ -161,7 +182,7 @@
         ),
         SET_TO_NO_DATA_IF_FIRST_STATE_IS_INVALID(
             expressed =
-                WireComplicationData.Builder(WireComplicationData.TYPE_SHORT_TEXT)
+                WireComplicationData.Builder(TYPE_SHORT_TEXT)
                     .setShortTitle(WireComplicationText(DynamicString.fromState("valid")))
                     .setShortText(WireComplicationText(DynamicString.fromState("invalid")))
                     .build(),
@@ -177,7 +198,7 @@
         ),
         SET_TO_NO_DATA_IF_LAST_STATE_IS_INVALID(
             expressed =
-                WireComplicationData.Builder(WireComplicationData.TYPE_SHORT_TEXT)
+                WireComplicationData.Builder(TYPE_SHORT_TEXT)
                     .setShortTitle(WireComplicationText(DynamicString.fromState("valid")))
                     .setShortText(WireComplicationText(DynamicString.fromState("invalid")))
                     .build(),
@@ -192,13 +213,43 @@
             evaluated =
                 listOf(
                     INVALID_DATA, // Before state is available.
-                    WireComplicationData.Builder(WireComplicationData.TYPE_SHORT_TEXT)
+                    WireComplicationData.Builder(TYPE_SHORT_TEXT)
                         .setShortTitle(WireComplicationText("Valid"))
                         .setShortText(WireComplicationText("Valid"))
                         .build(),
                     INVALID_DATA, // After it was invalidated.
                 ),
         ),
+        SET_TO_EVALUATED_WITHOUT_PLACEHOLDER_IF_NOT_NO_DATA(
+            expressed =
+                WireComplicationData.Builder(TYPE_SHORT_TEXT)
+                    .setShortText(WireComplicationText("Text"))
+                    .setPlaceholder(evaluatedData("Placeholder"))
+                    .build(),
+            states = listOf(),
+            evaluated =
+                listOf(
+                    // No placeholder.
+                    WireComplicationData.Builder(TYPE_SHORT_TEXT)
+                        .setShortText(WireComplicationText("Text"))
+                        .build(),
+                )
+        ),
+        SET_TO_EVALUATED_WITHOUT_PLACEHOLDER_EVEN_IF_PLACEHOLDER_INVALID_IF_NOT_NO_DATA(
+            expressed =
+            WireComplicationData.Builder(TYPE_SHORT_TEXT)
+                .setShortText(WireComplicationText("Text"))
+                .setPlaceholder(stateData("placeholder"))
+                .build(),
+            states = listOf(), // placeholder state not set.
+            evaluated =
+            listOf(
+                // No placeholder.
+                WireComplicationData.Builder(TYPE_SHORT_TEXT)
+                    .setShortText(WireComplicationText("Text"))
+                    .build(),
+            )
+        ),
     }
 
     @Test
@@ -231,7 +282,7 @@
     @Test
     fun evaluate_cancelled_cleansUp() = runBlocking {
         val expressed =
-            WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+            WireComplicationData.Builder(TYPE_NO_DATA)
                 .setRangedValueExpression(
                     // Uses TimeGateway, which needs cleaning up.
                     DynamicInstant.withSecondsPrecision(Instant.EPOCH)
@@ -262,19 +313,22 @@
     @Test
     fun evaluate_keepExpression_doesNotTrimUnevaluatedExpression() = runBlocking {
         val expressed =
-            WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+            WireComplicationData.Builder(TYPE_NO_DATA)
                 .setRangedValueExpression(DynamicFloat.constant(1f))
                 .setLongText(WireComplicationText(DynamicString.constant("Long Text")))
                 .setLongTitle(WireComplicationText(DynamicString.constant("Long Title")))
                 .setShortText(WireComplicationText(DynamicString.constant("Short Text")))
                 .setShortTitle(WireComplicationText(DynamicString.constant("Short Title")))
                 .setContentDescription(WireComplicationText(DynamicString.constant("Description")))
+                .setPlaceholder(constantData("Placeholder"))
+                .setListEntryCollection(listOf(constantData("List")))
                 .build()
+                .also { it.setTimelineEntryCollection(listOf(constantData("Timeline"))) }
         val evaluator = ComplicationDataExpressionEvaluator(keepExpression = true)
 
         assertThat(evaluator.evaluate(expressed).firstOrNull())
             .isEqualTo(
-                WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+                WireComplicationData.Builder(TYPE_NO_DATA)
                     .setRangedValue(1f)
                     .setRangedValueExpression(DynamicFloat.constant(1f))
                     .setLongText(
@@ -292,6 +346,29 @@
                     .setContentDescription(
                         WireComplicationText("Description", DynamicString.constant("Description"))
                     )
+                    .setPlaceholder(evaluatedWithConstantData("Placeholder"))
+                    .setListEntryCollection(listOf(evaluatedWithConstantData("List")))
+                    .build()
+                    .also {
+                        it.setTimelineEntryCollection(listOf(evaluatedWithConstantData("Timeline")))
+                    },
+            )
+    }
+
+    @Test
+    fun evaluate_keepExpressionNotNoData_doesNotTrimPlaceholder() = runBlocking {
+        val expressed =
+            WireComplicationData.Builder(TYPE_SHORT_TEXT)
+                .setShortText(WireComplicationText("Text"))
+                .setPlaceholder(evaluatedData("Placeholder"))
+                .build()
+        val evaluator = ComplicationDataExpressionEvaluator(keepExpression = true)
+
+        assertThat(evaluator.evaluate(expressed).firstOrNull())
+            .isEqualTo(
+                WireComplicationData.Builder(TYPE_SHORT_TEXT)
+                    .setShortText(WireComplicationText("Text"))
+                    .setPlaceholder(evaluatedData("Placeholder"))
                     .build()
             )
     }
@@ -300,5 +377,25 @@
         /** Converts `[{a: A}, {b: B}, {c: C}]` to `[{a: A}, {a: A, b: B}, {a: A, b: B, c: C}]`. */
         fun <K, V> aggregate(vararg maps: Map<K, V>): List<Map<K, V>> =
             maps.fold(listOf()) { acc, map -> acc + ((acc.lastOrNull() ?: mapOf()) + map) }
+
+        fun constantData(value: String) =
+            WireComplicationData.Builder(TYPE_NO_DATA)
+                .setLongText(WireComplicationText(DynamicString.constant(value)))
+                .build()
+
+        fun stateData(value: String) =
+            WireComplicationData.Builder(TYPE_NO_DATA)
+                .setLongText(WireComplicationText(DynamicString.fromState(value)))
+                .build()
+
+        fun evaluatedData(value: String) =
+            WireComplicationData.Builder(TYPE_NO_DATA)
+                .setLongText(WireComplicationText(value))
+                .build()
+
+        fun evaluatedWithConstantData(value: String) =
+            WireComplicationData.Builder(TYPE_NO_DATA)
+                .setLongText(WireComplicationText(value, DynamicString.constant(value)))
+                .build()
     }
 }
diff --git a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
index 639d5b1..0660ca2 100644
--- a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
+++ b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/GcmTaskConverterTest.kt
@@ -23,6 +23,7 @@
 import androidx.work.NetworkType
 import androidx.work.OneTimeWorkRequestBuilder
 import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.SystemClock
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.background.gcm.GcmTaskConverter.EXECUTION_WINDOW_SIZE_IN_SECONDS
 import com.google.android.gms.gcm.Task
@@ -45,7 +46,7 @@
 
     @Before
     fun setUp() {
-        mTaskConverter = spy(GcmTaskConverter())
+        mTaskConverter = spy(GcmTaskConverter(SystemClock()))
     }
 
     @Test
diff --git a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmScheduler.java b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmScheduler.java
index f8ffaba..71f595e 100644
--- a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmScheduler.java
+++ b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmScheduler.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import androidx.annotation.NonNull;
+import androidx.work.Clock;
 import androidx.work.Logger;
 import androidx.work.impl.Scheduler;
 import androidx.work.impl.model.WorkSpec;
@@ -38,14 +39,14 @@
     private final GcmNetworkManager mNetworkManager;
     private final GcmTaskConverter mTaskConverter;
 
-    public GcmScheduler(@NonNull Context context) {
+    public GcmScheduler(@NonNull Context context, @NonNull Clock clock) {
         boolean isPlayServicesAvailable = GoogleApiAvailability.getInstance()
                 .isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
         if (!isPlayServicesAvailable) {
             throw new IllegalStateException("Google Play Services not available");
         }
         mNetworkManager = GcmNetworkManager.getInstance(context);
-        mTaskConverter = new GcmTaskConverter();
+        mTaskConverter = new GcmTaskConverter(clock);
     }
 
     @Override
diff --git a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java
index 6dec44e..fb6e1ae 100644
--- a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java
+++ b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java
@@ -27,6 +27,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.work.Clock;
 import androidx.work.Constraints;
 import androidx.work.NetworkType;
 import androidx.work.impl.model.WorkSpec;
@@ -40,7 +41,6 @@
  * Converts a {@link androidx.work.impl.model.WorkSpec} to a {@link Task}.
  */
 public class GcmTaskConverter {
-
     /**
      * This is referring to the size of the execution window in seconds. {@link GcmNetworkManager}
      * requires that we specify a window of time relative to {@code now} where a {@link Task}
@@ -53,6 +53,11 @@
     public static final long EXECUTION_WINDOW_SIZE_IN_SECONDS = 5L;
 
     static final String EXTRA_WORK_GENERATION = "androidx.work.impl.background.gcm.GENERATION";
+    private final Clock mClock;
+
+    public GcmTaskConverter(@NonNull Clock clock) {
+        mClock = clock;
+    }
 
     OneoffTask convert(@NonNull WorkSpec workSpec) {
         Bundle extras = new Bundle();
@@ -81,7 +86,7 @@
      */
     @VisibleForTesting
     public long now() {
-        return System.currentTimeMillis();
+        return mClock.currentTimeMillis();
     }
 
     private static Task.Builder applyConstraints(
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/DelayedWorkTrackerTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/DelayedWorkTrackerTest.kt
index ffce534..5cba6e9 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/DelayedWorkTrackerTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/DelayedWorkTrackerTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.MediumTest
 import androidx.work.OneTimeWorkRequest
 import androidx.work.RunnableScheduler
+import androidx.work.SystemClock
 import androidx.work.worker.TestWorker
 import org.junit.Before
 import org.junit.Test
@@ -40,7 +41,7 @@
     fun setUp() {
         mScheduler = mock(GreedyScheduler::class.java)
         mRunnableScheduler = mock(RunnableScheduler::class.java)
-        mDelayedWorkTracker = DelayedWorkTracker(mScheduler, mRunnableScheduler)
+        mDelayedWorkTracker = DelayedWorkTracker(mScheduler, mRunnableScheduler, SystemClock())
     }
 
     @Test
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
index 2b97041..d000c32 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
@@ -44,6 +44,7 @@
 import androidx.work.NetworkType;
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.PeriodicWorkRequest;
+import androidx.work.SystemClock;
 import androidx.work.WorkManagerTest;
 import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.model.WorkSpec;
@@ -70,7 +71,7 @@
     @Before
     public void setUp() {
         mConverter = new SystemJobInfoConverter(
-                ApplicationProvider.getApplicationContext());
+                ApplicationProvider.getApplicationContext(), new SystemClock());
     }
 
     @Test
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
index 305ccf2..164b411 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
@@ -126,7 +126,7 @@
                         workDatabase,
                         configuration,
                         mJobScheduler,
-                        new SystemJobInfoConverter(context)));
+                        new SystemJobInfoConverter(context, configuration.getClock())));
 
         doNothing().when(mSystemJobScheduler).scheduleInternal(any(WorkSpec.class), anyInt());
     }
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt b/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
index 60dd8fe..27d7f35 100644
--- a/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
+++ b/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
@@ -224,7 +224,7 @@
          * Sets an initial delay for the [WorkRequest].
          *
          * @param duration The length of the delay
-         * @return The current [Builder]         *
+         * @return The current [Builder]
          * @throws IllegalArgumentException if the given initial delay will push the execution time
          * past `Long.MAX_VALUE` and cause an overflow
          */
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/Schedulers.java b/work/work-runtime/src/main/java/androidx/work/impl/Schedulers.java
index 6a1ed29..b0786fe 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/Schedulers.java
@@ -26,6 +26,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.work.Clock;
 import androidx.work.Configuration;
 import androidx.work.Logger;
 import androidx.work.impl.background.systemalarm.SystemAlarmScheduler;
@@ -100,13 +101,13 @@
             List<WorkSpec> contentUriWorkSpecs = null;
             if (Build.VERSION.SDK_INT >= CONTENT_URI_TRIGGER_API_LEVEL) {
                 contentUriWorkSpecs = workSpecDao.getEligibleWorkForSchedulingWithContentUris();
-                markScheduled(workSpecDao, contentUriWorkSpecs);
+                markScheduled(workSpecDao, configuration.getClock(), contentUriWorkSpecs);
             }
 
             // Enqueued workSpecs when scheduling limits are applicable.
             eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(
                     configuration.getMaxSchedulerLimit());
-            markScheduled(workSpecDao, eligibleWorkSpecsForLimitedSlots);
+            markScheduled(workSpecDao, configuration.getClock(), eligibleWorkSpecsForLimitedSlots);
             if (contentUriWorkSpecs != null) {
                 eligibleWorkSpecsForLimitedSlots.addAll(contentUriWorkSpecs);
             }
@@ -157,7 +158,7 @@
             setComponentEnabled(context, SystemJobService.class, true);
             Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
         } else {
-            scheduler = tryCreateGcmBasedScheduler(context);
+            scheduler = tryCreateGcmBasedScheduler(context, configuration.getClock());
             if (scheduler == null) {
                 scheduler = new SystemAlarmScheduler(context);
                 setComponentEnabled(context, SystemAlarmService.class, true);
@@ -168,11 +169,12 @@
     }
 
     @Nullable
-    private static Scheduler tryCreateGcmBasedScheduler(@NonNull Context context) {
+    private static Scheduler tryCreateGcmBasedScheduler(@NonNull Context context, Clock clock) {
         try {
             Class<?> klass = Class.forName(GCM_SCHEDULER);
             Scheduler scheduler =
-                    (Scheduler) klass.getConstructor(Context.class).newInstance(context);
+                    (Scheduler) klass.getConstructor(Context.class, Clock.class)
+                            .newInstance(context, clock);
             Logger.get().debug(TAG, "Created " + GCM_SCHEDULER);
             return scheduler;
         } catch (Throwable throwable) {
@@ -184,9 +186,9 @@
     private Schedulers() {
     }
 
-    private static void markScheduled(WorkSpecDao dao, List<WorkSpec> workSpecs) {
+    private static void markScheduled(WorkSpecDao dao, Clock clock, List<WorkSpec> workSpecs) {
         if (workSpecs.size() > 0) {
-            long now = System.currentTimeMillis();
+            long now = clock.currentTimeMillis();
 
             // Mark all the WorkSpecs as scheduled.
             // Calls to Scheduler#schedule() could potentially result in more schedules
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/DelayedWorkTracker.java b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/DelayedWorkTracker.java
index 01e484a..3ae8711 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/DelayedWorkTracker.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/DelayedWorkTracker.java
@@ -18,6 +18,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.work.Clock;
 import androidx.work.Logger;
 import androidx.work.RunnableScheduler;
 import androidx.work.impl.model.WorkSpec;
@@ -43,14 +44,17 @@
     final GreedyScheduler mGreedyScheduler;
 
     private final RunnableScheduler mRunnableScheduler;
+    private final Clock mClock;
     private final Map<String, Runnable> mRunnables;
 
     public DelayedWorkTracker(
             @NonNull GreedyScheduler scheduler,
-            @NonNull RunnableScheduler runnableScheduler) {
+            @NonNull RunnableScheduler runnableScheduler,
+            @NonNull Clock clock) {
 
         mGreedyScheduler = scheduler;
         mRunnableScheduler = runnableScheduler;
+        mClock = clock;
         mRunnables = new HashMap<>();
     }
 
@@ -77,7 +81,7 @@
         };
 
         mRunnables.put(workSpec.id, runnable);
-        long now = System.currentTimeMillis();
+        long now = mClock.currentTimeMillis();
         long delay = nextRunTime - now;
         mRunnableScheduler.scheduleWithDelay(delay, runnable);
     }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
index b7e8767..bb48033 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
@@ -93,7 +93,8 @@
     ) {
         mContext = context;
         mWorkConstraintsTracker = new WorkConstraintsTrackerImpl(trackers, this);
-        mDelayedWorkTracker = new DelayedWorkTracker(this, configuration.getRunnableScheduler());
+        mDelayedWorkTracker = new DelayedWorkTracker(
+                this, configuration.getRunnableScheduler(), configuration.getClock());
         mConfiguration = configuration;
         mProcessor = processor;
         mWorkLauncher = workLauncher;
@@ -152,7 +153,7 @@
             }
             long throttled = throttleIfNeeded(workSpec);
             long nextRunTime = max(workSpec.calculateNextRunTime(), throttled);
-            long now = System.currentTimeMillis();
+            long now = mConfiguration.getClock().currentTimeMillis();
             if (workSpec.state == WorkInfo.State.ENQUEUED) {
                 if (now < nextRunTime) {
                     // Future work
@@ -290,7 +291,7 @@
             AttemptData firstRunAttempt = mFirstRunAttempts.get(id);
             if (firstRunAttempt == null) {
                 firstRunAttempt = new AttemptData(workSpec.runAttemptCount,
-                        System.currentTimeMillis());
+                        mConfiguration.getClock().currentTimeMillis());
                 mFirstRunAttempts.put(id, firstRunAttempt);
             }
             return firstRunAttempt.mTimeStamp
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
index ade9938..1e73418 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
@@ -24,6 +24,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.WorkerThread;
+import androidx.work.Clock;
 import androidx.work.Logger;
 import androidx.work.impl.ExecutionListener;
 import androidx.work.impl.StartStopToken;
@@ -128,10 +129,13 @@
     private final Context mContext;
     private final Map<WorkGenerationalId, DelayMetCommandHandler> mPendingDelayMet;
     private final Object mLock;
+    private final Clock mClock;
     private final StartStopTokens mStartStopTokens;
 
-    CommandHandler(@NonNull Context context, @NonNull StartStopTokens startStopTokens) {
+    CommandHandler(@NonNull Context context, Clock clock,
+            @NonNull StartStopTokens startStopTokens) {
         mContext = context;
+        mClock = clock;
         mStartStopTokens = startStopTokens;
         mPendingDelayMet = new HashMap<>();
         mLock = new Object();
@@ -332,7 +336,7 @@
         // Constraints changed command handler is synchronous. No cleanup
         // is necessary.
         ConstraintsCommandHandler changedCommandHandler =
-                new ConstraintsCommandHandler(mContext, startId, dispatcher);
+                new ConstraintsCommandHandler(mContext, mClock, startId, dispatcher);
         changedCommandHandler.handleConstraintsChanged();
     }
 
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
index ad3361b..4bd3fbf 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
@@ -24,6 +24,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.WorkerThread;
+import androidx.work.Clock;
 import androidx.work.Logger;
 import androidx.work.impl.constraints.WorkConstraintsTrackerImpl;
 import androidx.work.impl.constraints.trackers.Trackers;
@@ -35,7 +36,6 @@
 /**
  * This is a command handler which handles the constraints changed event.
  * Typically this happens for WorkSpec's for which we have pending alarms.
- *
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 class ConstraintsCommandHandler {
@@ -43,16 +43,18 @@
     private static final String TAG = Logger.tagWithPrefix("ConstraintsCmdHandler");
 
     private final Context mContext;
+    private final Clock mClock;
     private final int mStartId;
     private final SystemAlarmDispatcher mDispatcher;
     private final WorkConstraintsTrackerImpl mWorkConstraintsTracker;
 
     ConstraintsCommandHandler(
             @NonNull Context context,
+            Clock clock,
             int startId,
             @NonNull SystemAlarmDispatcher dispatcher) {
-
         mContext = context;
+        mClock = clock;
         mStartId = startId;
         mDispatcher = dispatcher;
         Trackers trackers = mDispatcher.getWorkManager().getTrackers();
@@ -74,7 +76,7 @@
 
         List<WorkSpec> eligibleWorkSpecs = new ArrayList<>(candidates.size());
         // Filter candidates should have already been scheduled.
-        long now = System.currentTimeMillis();
+        long now = mClock.currentTimeMillis();
         for (WorkSpec workSpec : candidates) {
             String workSpecId = workSpec.id;
             long triggerAt = workSpec.calculateNextRunTime();
@@ -87,7 +89,8 @@
         for (WorkSpec workSpec : eligibleWorkSpecs) {
             String workSpecId = workSpec.id;
             Intent intent = CommandHandler.createDelayMetIntent(mContext, generationalId(workSpec));
-            Logger.get().debug(TAG, "Creating a delay_met command for workSpec with id (" + workSpecId + ")");
+            Logger.get().debug(TAG,
+                    "Creating a delay_met command for workSpec with id (" + workSpecId + ")");
             mDispatcher.getTaskExecutor().getMainThreadExecutor().execute(
                     new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));
         }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
index c92310b..d6a5ef4 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
@@ -90,8 +90,9 @@
     ) {
         mContext = context.getApplicationContext();
         mStartStopTokens = new StartStopTokens();
-        mCommandHandler = new CommandHandler(mContext, mStartStopTokens);
         mWorkManager = workManager != null ? workManager : WorkManagerImpl.getInstance(context);
+        mCommandHandler = new CommandHandler(
+                mContext, mWorkManager.getConfiguration().getClock(), mStartStopTokens);
         mWorkTimer = new WorkTimer(mWorkManager.getConfiguration().getRunnableScheduler());
         mProcessor = processor != null ? processor : mWorkManager.getProcessor();
         mTaskExecutor = mWorkManager.getWorkTaskExecutor();
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
index 61a24d5..f292f3f 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
@@ -29,6 +29,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.work.BackoffPolicy;
+import androidx.work.Clock;
 import androidx.work.Constraints;
 import androidx.work.Logger;
 import androidx.work.NetworkType;
@@ -50,8 +51,10 @@
     static final String EXTRA_WORK_SPEC_GENERATION = "EXTRA_WORK_SPEC_GENERATION";
 
     private final ComponentName mWorkServiceComponent;
+    private final Clock mClock;
 
-    SystemJobInfoConverter(@NonNull Context context) {
+    SystemJobInfoConverter(@NonNull Context context, Clock clock) {
+        mClock = clock;
         Context appContext = context.getApplicationContext();
         mWorkServiceComponent = new ComponentName(appContext, SystemJobService.class);
     }
@@ -86,7 +89,7 @@
         }
 
         long nextRunTime = workSpec.calculateNextRunTime();
-        long now = System.currentTimeMillis();
+        long now = mClock.currentTimeMillis();
         long offset = Math.max(nextRunTime - now, 0);
 
         if (Build.VERSION.SDK_INT <= 28) {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index af76cf1..fc679669 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -78,7 +78,7 @@
                 workDatabase,
                 configuration,
                 (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE),
-                new SystemJobInfoConverter(context)
+                new SystemJobInfoConverter(context, configuration.getClock())
         );
     }
 
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
index 03158ae..71e3ec8 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
@@ -222,7 +222,9 @@
                     }
                     // Update the last cancelled time in Preference.
                     new PreferenceUtils(workManagerImpl.getWorkDatabase())
-                            .setLastCancelAllTimeMillis(System.currentTimeMillis());
+                            .setLastCancelAllTimeMillis(
+                                    workManagerImpl.getConfiguration().getClock()
+                                            .currentTimeMillis());
                     workDatabase.setTransactionSuccessful();
                 } finally {
                     workDatabase.endTransaction();
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt b/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
index 2781cdf..24221a9 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
@@ -38,7 +38,8 @@
         val workNameDao = database.workNameDao()
         val workTagDao = database.workTagDao()
         val systemIdInfoDao = database.systemIdInfoDao()
-        val startAt = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)
+        val startAt =
+            workManager.configuration.clock.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)
         val completed = workSpecDao.getRecentlyCompletedWork(startAt)
         val running = workSpecDao.getRunningWork()
         val enqueued = workSpecDao.getAllEligibleWorkSpecsForScheduling(